<?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: Lahari Tenneti</title>
    <description>The latest articles on DEV Community by Lahari Tenneti (@lahari_tenneti_4a8a082e9c).</description>
    <link>https://dev.to/lahari_tenneti_4a8a082e9c</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%2F3479289%2F25c84943-6853-40b8-83d9-4333147f4b0d.png</url>
      <title>DEV Community: Lahari Tenneti</title>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lahari_tenneti_4a8a082e9c"/>
    <language>en</language>
    <item>
      <title>LLVM #7 — Debugging!</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Tue, 23 Jun 2026 05:51:03 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-7-debugging-51a3</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-7-debugging-51a3</guid>
      <description>&lt;p&gt;We've built a language that lexes, parses, generates IR, optimises, JITs, and now even emits object code. But how do we know when something goes wrong in Kaleidoscope?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built:&lt;/strong&gt; &lt;a href="https://github.com/laharitenneti/Kaleidoscope/commit/41ba81d3a03e4e93e2d4f79452fb117d6418c545" rel="noopener noreferrer"&gt;Commit 41ba81d&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I understood:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;u&gt;The problem:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If we open our compiled binary inside a debugger like &lt;code&gt;lldb&lt;/code&gt; or &lt;code&gt;gdb&lt;/code&gt;, it sees raw machine code bytes sitting at memory addresses.&lt;/li&gt;
&lt;li&gt;It has no idea what line of Kaleidoscope source, or what variable name produced any of it.&lt;/li&gt;
&lt;li&gt;Basically, the debugger is fluent in assembly, but doesn't speak Kaleidoscope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;u&gt;The solution:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source-level debugging! &lt;/li&gt;
&lt;li&gt;It works by creating a mapping (basically metadata) between the machine code and the original source. &lt;/li&gt;
&lt;li&gt;In LLVM, this metadata is formatted using a global standard called DWARF, which includes:

&lt;ul&gt;
&lt;li&gt;A line table: Maps a CPU instruction address back to a specific file and line number.&lt;/li&gt;
&lt;li&gt;A variable map: Maps a memory address or CPU register to a variable name.&lt;/li&gt;
&lt;li&gt;A type registry: Tells the debugger what a chunk of memory actually represents (in our case, a double), so it can format the value sensibly instead of just printing raw bytes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Also, we turn off optimisation.

&lt;ul&gt;
&lt;li&gt;Tracing a bug back to a specific source line while looking at optimised code is difficult, for instructions get merged, reordered, and shared across what used to be separate statements.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;We can't use the JIT/REPL based approach here.

&lt;ul&gt;
&lt;li&gt;Debugging ephemeral code that exists only in memory is difficult, for debuggers like &lt;code&gt;lldb&lt;/code&gt; need a stable amd permanent file on disk they can open, inspect, and step through.&lt;/li&gt;
&lt;li&gt;So we go back to the script-file approach from the object code chapter (our &lt;code&gt;test.k&lt;/code&gt; file) rather than the REPL.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The overall plan is to use &lt;code&gt;DIBuilder&lt;/code&gt; to insert explicit hooks into the lexer, parser, and AST, so that every piece of generated IR carries a tag back to its source location.&lt;/li&gt;
&lt;li&gt;Then we can open the compiled binary in a debugger, set breakpoints, and step through Kaleidoscope code line-by-line, as if it were any other compiled language.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;u&gt;Some key concepts:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DIBuilder&lt;/code&gt;: The engine that creates DWARF debug nodes. It is the debug equivalent of &lt;code&gt;IRBuilder&lt;/code&gt;. Where &lt;code&gt;IRBuilder&lt;/code&gt; emits instructions, &lt;code&gt;DIBuilder&lt;/code&gt; emits metadata describing those instructions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CompileUnit&lt;/code&gt;: A structural node representing the entire source file (&lt;code&gt;test.k&lt;/code&gt;) being compiled. It holds global data like source language, directory path, compiler name, etc. This is DWARF's root.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lexical Blocks:&lt;/strong&gt; This is a scope stack. Functions (and in principle, nested blocks) get pushed here so that variables and instructions know exactly which scope they belong to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I did&lt;/strong&gt;&lt;br&gt;
1) &lt;u&gt;Tracking the lexer's location:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Previously, the lexer just called &lt;code&gt;getchar()&lt;/code&gt; and moved on, with no memory of where it was.&lt;/li&gt;
&lt;li&gt;Now &lt;code&gt;advance()&lt;/code&gt; replaces every &lt;code&gt;getchar()&lt;/code&gt; call inside &lt;code&gt;gettok()&lt;/code&gt;, incrementing line and column counters as it goes.
&lt;code&gt;gettok()&lt;/code&gt; also stamps &lt;code&gt;CurLoc&lt;/code&gt; at the start of each token.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;advance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;LastChar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getchar&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LastChar&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="err"&gt;`\&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;LastChar&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="err"&gt;`\&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="err"&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;LexLoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Line&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;LexLoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Col&lt;/span&gt; &lt;span class="o"&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;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;LexLoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Col&lt;/span&gt;&lt;span class="o"&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;return&lt;/span&gt; &lt;span class="n"&gt;LastChar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;//inside gettok():&lt;/span&gt;
&lt;span class="n"&gt;CurLoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LexLoc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;2) Adding source locations to AST nodes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The&lt;code&gt;ExprAST&lt;/code&gt; base class now stores a &lt;code&gt;SourceLocation&lt;/code&gt;, defaulting to whatever &lt;code&gt;CurLoc&lt;/code&gt; was at construction time, so most subclasses inherit it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VariableExprAST&lt;/code&gt; and &lt;code&gt;CallExprAST&lt;/code&gt; needed explicit locations passed in &lt;code&gt;LitLoc&lt;/code&gt; and &lt;code&gt;BinLoc&lt;/code&gt; respectively, since they're constructed at specific points in parsing where the "current" location matters.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExprAST&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;SourceLocation&lt;/span&gt; &lt;span class="n"&gt;Loc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;public:&lt;/span&gt;
   &lt;span class="n"&gt;ExprAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SourceLocation&lt;/span&gt; &lt;span class="n"&gt;Loc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CurLoc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Loc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Loc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
   &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;getLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;const&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;Loc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Line&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;3) The &lt;code&gt;DebugInfo&lt;/code&gt; struct.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bundles &lt;code&gt;TheCU&lt;/code&gt; (the compile unit), &lt;code&gt;DblTy&lt;/code&gt; (a cached type descriptor for &lt;code&gt;double&lt;/code&gt;), and &lt;code&gt;LexicalBlocks&lt;/code&gt; (the scope stack) together, alongside &lt;code&gt;DBuilder&lt;/code&gt; as a global.&lt;/li&gt;
&lt;li&gt;This is the single source of truth for "what debug state are we in right now?"
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;DebugInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;DICompileUnit&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TheCU&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;DIType&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;DblTy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DIScope&lt;/span&gt; &lt;span class="o"&gt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LexicalBlocks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;KSDbgInfo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;4) &lt;u&gt;Declaring the DWARF versio&lt;/u&gt;n in &lt;code&gt;main()&lt;/code&gt;, and creating the compile unit (&lt;code&gt;test.k&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;TheModule&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addModuleFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Module&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Warning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Debug Info Version"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DEBUG_METADATA_VERSION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;DBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_unique&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DIBuilder&lt;/span&gt;&lt;span class="o"&gt;&amp;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;TheModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;KSDbgInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TheCU&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DBuilder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;createCompileUnit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dwarf&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DW_LANG_C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DBuilder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;createFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test.k"&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="s"&gt;"Kaleidoscope Compiler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;false&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="mi"&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;5) &lt;u&gt;Emitting debug info per function:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inside &lt;code&gt;FunctionAST::codegen()&lt;/code&gt;:

&lt;ul&gt;
&lt;li&gt;We create a &lt;code&gt;DISubprogram&lt;/code&gt;, which is DWARF's description of a function, so the debugger knows it's looking at &lt;code&gt;celsius&lt;/code&gt; or &lt;code&gt;fib&lt;/code&gt;, not just an anonymous block of instructions.&lt;/li&gt;
&lt;li&gt;We push that &lt;code&gt;DISubprogram&lt;/code&gt; onto &lt;code&gt;LexicalBlocks&lt;/code&gt;, so any nested expressions know which function scope they're in.&lt;/li&gt;
&lt;li&gt;We register each argument's location in memory (its &lt;code&gt;alloca&lt;/code&gt;) with the debugger, so it can show argument values when you break inside the function.&lt;/li&gt;
&lt;li&gt;We also perform prologue suppression. A prologue is when we have instructions at the very start of a function and which have no location at all. So the debugger skips past the &lt;code&gt;alloca&lt;/code&gt;/&lt;code&gt;store&lt;/code&gt; boilerplate when we set breakpoints, landing us on the actual first line of logic instead.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;DISubprogram&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;SP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DBuilder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;createFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringRef&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LineNo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CreateFunctionType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TheFunction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;arg_size&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;LineNo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DINode&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;FlagPrototyped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DISubprogram&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SPFlagDefinition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;TheFunction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setSubprogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;KSDbgInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LexicalBlocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;KSDbgInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emitLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//prologue suppression&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6) &lt;code&gt;emitLocation(this)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every &lt;code&gt;codegen()&lt;/code&gt; method on a subclass of &lt;code&gt;ExprAST&lt;/code&gt; calls this before emitting its instructions, tagging the next instruction with that AST node's line and column.&lt;/li&gt;
&lt;li&gt;Only &lt;code&gt;ExprAST&lt;/code&gt; subclasses do this. &lt;code&gt;PrototypeAST&lt;/code&gt; and &lt;code&gt;FunctionAST&lt;/code&gt; are not expressions.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;NumberExprAST&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;codegen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;KSDbgInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emitLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&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;ConstantFP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TheContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;APFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Val&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;7) &lt;code&gt;DBuilder-&amp;gt;finalize()&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This serialises all the DWARF metadata we've built into the module. It has to happen after codegen and before the JIT takes ownership of the module.&lt;/li&gt;
&lt;li&gt;Because once &lt;code&gt;addModule()&lt;/code&gt; moves it, we can't touch it anymore. Each module gets its own finalised debug info, and the next module starts fresh.&lt;/li&gt;
&lt;li&gt;The AOT object emission, by the way, can happen before &lt;code&gt;finalize()&lt;/code&gt; because writing &lt;code&gt;output.o&lt;/code&gt; runs through a completely separate pass pipeline that doesn't care whether the module's DWARF metadata is finalised or not.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;DBuilder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;TSM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ThreadSafeModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TheModule&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TheContext&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;ExitOnErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TheJIT&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TSM&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;RT&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;What I didn't understand:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) Why don't &lt;code&gt;PrototypeAST&lt;/code&gt; and &lt;code&gt;FunctionAST&lt;/code&gt; call &lt;code&gt;emitLocation()&lt;/code&gt; the same way everything else does?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because they aren't expressions. Every other AST node (numbers, variables, calls, if/else, loops) represents something that evaluates to a value at a specific point in the source. &lt;/li&gt;
&lt;li&gt;A prototype doesn't evaluate to anything and is merely a declaration. &lt;/li&gt;
&lt;li&gt;A function definition isn't a single point either, and is a container for a whole sequence of expressions, each with its own location. &lt;/li&gt;
&lt;li&gt;So &lt;code&gt;FunctionAST::codegen()&lt;/code&gt; has to handle location-tagging more deliberately. First suppressing it for the setup code, then explicitly stamping the body's location once real logic starts. &lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;The Debugger in Action&lt;/strong&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%2Ftdoej6esua9zqj2max6k.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%2Ftdoej6esua9zqj2max6k.png" alt=" " width="800" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running &lt;code&gt;dwarfdump output.o&lt;/code&gt; after compiling with &lt;code&gt;-g -O0&lt;/code&gt; shows the DWARF metadata baked into the object file.&lt;/li&gt;
&lt;/ul&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%2Fx781m19zpgq4f2luxnbq.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%2Fx781m19zpgq4f2luxnbq.png" alt=" " width="800" height="504"&gt;&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;DW_TAG_compile_unit
  DW_AT_producer  ("Kaleidoscope Compiler")
  DW_AT_language  (DW_LANG_C)
  DW_AT_name      ("test.k")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The debugger now knows that this binary came from a file called &lt;code&gt;test.k&lt;/code&gt;, compiled by something calling itself the "Kaleidoscope Compiler," using C-style calling conventions.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DW_TAG_subprogram
  DW_AT_name      ("celsius")
  DW_AT_decl_line (1)
  DW_AT_type      (0x0000005e "double")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The debugger knows there's a function named &lt;code&gt;celsius&lt;/code&gt; on line 1, returning a &lt;code&gt;double&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DW_AT_low_pc/DW_AT_high_pc&lt;/code&gt; give the actual machine address range this function occupies, which is how &lt;code&gt;break celsius&lt;/code&gt; knows where to stop.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DW_TAG_formal_parameter
  DW_AT_name      ("fahrenheit")
  DW_AT_decl_line (1)
  DW_AT_type      (0x0000005e "double")
  DW_AT_location  (...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This tells the debugger exactly where to find &lt;code&gt;fahrenheit&lt;/code&gt;'s value at any given point in the function.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[0x0...0,  0x0...10): DW_OP_regx B0
[0x0...10, 0x0...1c): DW_OP_entry_value(DW_OP_regx B0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;For the first 0x10 bytes of machine code, &lt;code&gt;fahrenheit&lt;/code&gt; lives in register B0. After that, the codegen may have reused B0 for something else, so the debugger recovers the entry value (what B0 held when the function started) to still show &lt;code&gt;fahrenheit&lt;/code&gt; correctly. &lt;/li&gt;
&lt;li&gt;That's LLVM automatically being smart about register reuse, which we get for free just by setting up &lt;code&gt;DILocalVariable&lt;/code&gt; correctly.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DW_TAG_base_type
  DW_AT_name      ("double")
  DW_AT_encoding  (DW_ATE_float)
  DW_AT_byte_size (0x08)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This is &lt;code&gt;KSDbgInfo.getDoubleTy()&lt;/code&gt;, the cached &lt;code&gt;DIType*&lt;/code&gt; built once and reused everywhere. &lt;/li&gt;
&lt;li&gt;Only a single &lt;code&gt;DW_TAG_base_type&lt;/code&gt; entry appears in the dump, referenced by both the function's return type and the parameter's type via the &lt;code&gt;0x0000005e&lt;/code&gt; offset.&lt;/li&gt;
&lt;/ul&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%2F73tccpdoqnqxzc38xitq.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%2F73tccpdoqnqxzc38xitq.png" alt=" " width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When I ran the binary under &lt;code&gt;lldb&lt;/code&gt;, set &lt;code&gt;break celsius&lt;/code&gt;, and &lt;code&gt;run&lt;/code&gt;, it stopped at the right place, showing &lt;code&gt;fahrenheit&lt;/code&gt;'s value, and knew it was a &lt;code&gt;double&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; Vectorisation hints.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;br&gt;
Fun fact: the word "debugging" traces back to Grace Hopper's team at Harvard in 1947, when they found a literal moth lodged in a relay of the Mark II computer. Hopper taped it into the logbook with the note "first actual case of bug being found." She didn't coin the term (as bug for a technical glitch predates her) but she gave us the artefact, and the word stuck to software forever after. &lt;/p&gt;

&lt;p&gt;Anyhow, I finished the Kaleidoscope tutorial!!!!! I mean !!!!! &lt;br&gt;
Two years back, I could barely understand what compilers did. Now I (partly) worked on and understood the backend of compilation! &lt;br&gt;
Like I did for the &lt;a href="https://dev.to/lahari_tenneti_4a8a082e9c/series/33287"&gt;jlox interpreter&lt;/a&gt;, I'll be adding some custom extensions to this project so I can understand it even better.&lt;/p&gt;

&lt;p&gt;Time absolutely &lt;em&gt;flies&lt;/em&gt;. Happy to have finished this &lt;em&gt;mam&lt;u&gt;moth&lt;/u&gt;&lt;/em&gt; of a task. It sure won't &lt;em&gt;bug&lt;/em&gt; me anymore. 😌&lt;/p&gt;

&lt;p&gt;(I'll see myself out, bye for now)&lt;/p&gt;

</description>
      <category>llvm</category>
      <category>compilers</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
    <item>
      <title>LLVM #6 — Compiling to Object Code.</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Mon, 08 Jun 2026 05:41:40 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-6-compiling-to-object-code-1a47</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-6-compiling-to-object-code-1a47</guid>
      <description>&lt;p&gt;This is a short chapter but with a very tangible and "objective" payoff. &lt;/p&gt;

&lt;p&gt;Basically, every function we typed into the REPL got compiled and ran inside the same process, in RAM. The JIT took the IR, converted it into machine code, and executed it instantly. When the process exits, it's all gone.&lt;/p&gt;

&lt;p&gt;Now, instead of "running" the code, we write it to the disk as a &lt;code&gt;.o&lt;/code&gt; file, which is compiled into machine code in a format the linker understands. This can be linked with any other C++ program, enabling us to call the Kaleidoscope functions as if they were normal C functions. The code actually outlives the compiler process that produced it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I built:&lt;/strong&gt; &lt;a href="https://github.com/laharitenneti/Kaleidoscope/commit/f229e861d6f1e8d1ca47490f2a9792b730279a81" rel="noopener noreferrer"&gt;Commit f229e86&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I understood:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The process of emitting object code includes picking a target, describing the machine, configuring the module, and emitting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1) &lt;u&gt;The Target Triple:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;So LLVM is designed for cross-compilation, meaning it can target an Intel Mac, an ARM Phone, a Windows PC... anything.&lt;/li&gt;
&lt;li&gt;For this, it needs a complete machine profile encoded as a string called the target triple. &lt;/li&gt;
&lt;li&gt;Format: &lt;code&gt;&amp;lt;architecture&amp;gt;-&amp;lt;vendor&amp;gt;-&amp;lt;operating-system&amp;gt;-&amp;lt;ABI&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Example: &lt;code&gt;x86_64-unknown-linux-gnu&lt;/code&gt; means a 64-bit Intel, unspecified vendor, Linux, and GNU calling conventions. &lt;/li&gt;
&lt;li&gt;It's just that instead of hardcoding a triple, we ask LLVM for the current machine's:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;TargetTriple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;getDefaultTargetTriple&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) &lt;u&gt;Initialising subsystems:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLVM doesn't activate all its backends by default. The JIT only needed the native target. But for writing an object file to the disk, we need everything (hardware platform information, core codegen, machine-code abstractions, and assembly reader and writer).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;InitializeAllTargetInfos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;//registers available hardware platforms so LLVM knows what targets exist&lt;/span&gt;
&lt;span class="n"&gt;InitializeAllTargets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;//loads the code generators; without this, lookupTarget() returns nullptr even with a valid triple&lt;/span&gt;
&lt;span class="n"&gt;InitializeAllTargetMCs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;//handles the MC layer to turn abstract instructions into actual bytes&lt;/span&gt;
&lt;span class="n"&gt;InitializeAllAsmParsers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;//enables reading assembly text as input&lt;/span&gt;
&lt;span class="n"&gt;InitializeAllAsmPrinters&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;//enables writing machine instructions to a file; needed for addPassesToEmitFile()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) &lt;u&gt;Target Machine:&lt;/u&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once the matching target is obtained from the registry, we create the Target Machine, which is a generic CPU with no special features.&lt;/li&gt;
&lt;li&gt;We also mark the data layout and triple onto the module, which tells the optimiser about things like pointer sizes, alignment rules, and the target's memory layout.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;TM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;createTargetMachine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Triple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TargetTriple&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"generic"&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="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Reloc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PIC_&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;TheModule&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setDataLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TM&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;createDataLayout&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;TheModule&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setTargetTriple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Triple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TargetTriple&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4) &lt;u&gt;Emitting: &lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;legacy::PassManager&lt;/code&gt; with one pass runs over the module and writes the object file:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;raw_fd_ostream&lt;/span&gt; &lt;span class="nf"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"output.o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;OF_None&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;legacy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PassManager&lt;/span&gt; &lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;TM&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addPassesToEmitFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CodeGenFileType&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ObjectFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TheModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;What I didn't uderstand:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More like where I faced issues:
1) &lt;u&gt;Empty object file:&lt;/u&gt;
&lt;/li&gt;
&lt;li&gt;The tutorial places all the emit code at the bottom of &lt;code&gt;main()&lt;/code&gt;, after &lt;code&gt;MainLoop()&lt;/code&gt; returns. The idea is to parse everything, and then bake it all to disk. I expected this to work:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./toy &amp;lt; test.k        &lt;span class="c"&gt;#should parse celsius, emit output.o&lt;/span&gt;
nm output.o           &lt;span class="c"&gt;#should show: T celsius&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;But instead got: &lt;code&gt;0000000000000000 t ltmp0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;t&lt;/code&gt; instead of a &lt;code&gt;T&lt;/code&gt; meant it was a local symbol, implying the &lt;code&gt;celsius&lt;/code&gt; function was absent. &lt;/li&gt;
&lt;li&gt;This was because inside &lt;code&gt;HandleDefinition()&lt;/code&gt;, right after codegen, the module moves into the JIT and is reset.&lt;/li&gt;
&lt;li&gt;By the time &lt;code&gt;main()&lt;/code&gt; reached the emit code, &lt;code&gt;TheModule&lt;/code&gt; has nothing inside it. We'd be compiling an empty module to disk, and thus the &lt;code&gt;ltmp0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The answer is to emit inside &lt;code&gt;HandleDefinition()&lt;/code&gt; before the JIT move. The function is still alive in &lt;code&gt;TheModule&lt;/code&gt; at that point, so &lt;code&gt;pass.run(*TheModule)&lt;/code&gt; actually has something to work with.
2) Symbol name mismatch:&lt;/li&gt;
&lt;li&gt;Even after fixing the empty module, the link still failed:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Undefined symbols for architecture arm64:
  "_celsius", referenced from: _main in testing_temp.o
ld: symbol(s) not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The linker is looking for &lt;code&gt;_celsius&lt;/code&gt; (with an underscore). On macOS, C symbols in object files get a &lt;code&gt;_&lt;/code&gt; prefix automatically, so &lt;code&gt;celsius&lt;/code&gt; in the Kaleidoscope source becomes &lt;code&gt;_celsius&lt;/code&gt; in &lt;code&gt;output.o&lt;/code&gt;. But &lt;code&gt;testing_temp.cpp&lt;/code&gt; was declaring it as plain &lt;code&gt;celsius&lt;/code&gt;, so the linker couldn't match them.&lt;/li&gt;
&lt;li&gt;The fix is an asm label that tells the linker the exact symbol name to look for:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&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="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;__asm__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_celsius"&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;strong&gt;Running it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;test.k&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fahrenheit&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;32.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.555556&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;testing_temp.cpp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&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="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;fahrenheit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;__asm__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_celsius"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&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="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;68.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;celsius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;" degrees Fahrenheit is "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;" degrees Celsius!"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&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;ul&gt;
&lt;li&gt;We feed the &lt;code&gt;celsius&lt;/code&gt; function through the compiler, link the object file, and run it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./toy &amp;lt; test.k
clang++ testing_temp.cpp output.o &lt;span class="nt"&gt;-o&lt;/span&gt; test_celsius
./test_celsius   &lt;span class="c"&gt;#68 degrees Fahrenheit is 20 degrees Celsius!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fgtpbpb7z6otajr51lbvq.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.amazonaws.com%2Fuploads%2Farticles%2Fgtpbpb7z6otajr51lbvq.png" alt=" " width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; Debugging!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;br&gt;
Sometimes, I feel like I have no idea where things are heading. Like I have absolutely zero control over the outcome of things I put effort into. But Indian philosophy and even Greek (I’ve been reading Marcus Aurelius’ Meditations lately) seem to emphasise that that’s exactly the point. That we do our duty and just let go of the rest. Slightly harder than I thought. &lt;/p&gt;

&lt;p&gt;In times like these, among the few things that grounds me is looking at the night sky (of all directions). I feel like it's been witness to countless tales like these. In a way, knowing that I’m but only a tiny, tiny, one-millionth of this pale blue dot that is our earth, amidst the vast endlessness that is our universe, helps me feel like the things I think of, may after all, not really be as final as they feel in the moment. And that all will be okay. Those stars remind me of Tennyson’s ‘For men may come and men may go, but I go on forever’ and help me ground myself into, and enjoy the now.&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.amazonaws.com%2Fuploads%2Farticles%2F70528x8qceausfa97pgi.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.amazonaws.com%2Fuploads%2Farticles%2F70528x8qceausfa97pgi.png" alt=" " width="800" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>llvm</category>
      <category>compilers</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
    <item>
      <title>LLVM #5 — Mutable Variables</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Mon, 18 May 2026 09:40:38 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-5-mutable-variables-ppg</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-5-mutable-variables-ppg</guid>
      <description>&lt;p&gt;So far, Kaleidoscope has been a functional language with immutable variables and no reassignment. But to write anything resembling real code (loops that accumulate or programs with state), we need mutation. We add it now.&lt;/p&gt;

&lt;p&gt;We introduce two features: the ability to mutate variables with &lt;code&gt;=&lt;/code&gt;, and the ability to define new local variables with &lt;code&gt;var/in&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.amazonaws.com%2Fuploads%2Farticles%2Fy0dh8dz6w09tuod59bif.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.amazonaws.com%2Fuploads%2Farticles%2Fy0dh8dz6w09tuod59bif.png" alt=" " width="800" height="748"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built:&lt;/strong&gt; &lt;a href="https://github.com/laharitenneti/Kaleidoscope/commit/7911f2b0a21b63c3bd240f376290dc49e017d985" rel="noopener noreferrer"&gt;Commit 7911f2b&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I understood:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;Context&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kaleidoscope, in its functional paradigm, only ever had immutable variables. &lt;/li&gt;
&lt;li&gt;But what if we want to write things like this iterative Fibonacci:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def fibi(x)
  var a = 1, b = 1, c in
  (for i = 3, i &amp;lt; x in
     c = a + b :
     a = b :
     b = c) :
  b;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The issue is that LLVM IR requires SSA form. In SSA, a variable is assigned exactly once. But mutation means assigning the same variable multiple times. &lt;/li&gt;
&lt;li&gt;If we want to maintain SSA while making our language more imperative, there's immediate confusion over where to put the PHI nodes.&lt;/li&gt;
&lt;li&gt;Because unlike &lt;code&gt;if/else&lt;/code&gt; where the merge point is obvious from the AST, here the assignments could be scattered anywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Solution:&lt;/p&gt;

&lt;p&gt;a) &lt;u&gt;The "Easy Version:"&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The trick LLVM recommends is that we write a deliberately simple (and temporarily inefficient) IR, and let LLVM clean it up. Here's how it works:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1) At the start of the function, we c&lt;u&gt;reate a "box" on the stack&lt;/u&gt; for each mutable variable:&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;%x_addr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;alloca&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This gives &lt;code&gt;x&lt;/code&gt; a memory address. The compiler doesn't need to track how many times &lt;code&gt;x&lt;/code&gt; has been changed and just always knows the address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) Every time the user changes the value, we &lt;u&gt;update the box&lt;/u&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;store&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;%x_addr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) Every time we need to use the variable, we &lt;u&gt;peek inside the box&lt;/u&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="nv"&gt;%x_val&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;i32&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;%x_addr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This is easy for the front-end to generate and requires no PHI node reasoning. &lt;/li&gt;
&lt;li&gt;The cost is that we're constantly reading and writing to memory, which is slow. &lt;/li&gt;
&lt;li&gt;But that's where &lt;code&gt;mem2reg&lt;/code&gt; comes in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;b) &lt;u&gt;mem2reg: The lifting pass:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After we generate the "easy version", we run &lt;code&gt;PromotePass()&lt;/code&gt; (which is &lt;code&gt;mem2reg&lt;/code&gt;). It performs a "lifting" operation:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1) It looks at each &lt;code&gt;alloca&lt;/code&gt; and checks if it's only used for simple loads and stores.&lt;/p&gt;

&lt;p&gt;2) If yes, it deletes the &lt;code&gt;alloca&lt;/code&gt;, &lt;code&gt;load&lt;/code&gt;, and &lt;code&gt;store&lt;/code&gt; instructions entirely.&lt;/p&gt;

&lt;p&gt;3) It replaces them with high-speed CPU registers (and for this, it tracks the lifetime of the values like a timeline), assigning a new SSA register every time a new value is written.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When paths merge (like after an &lt;code&gt;if/else&lt;/code&gt;), &lt;code&gt;mem2reg&lt;/code&gt; mathematically figures out where the PHI nodes need to go, using a graph theory concept called &lt;strong&gt;dominance frontier&lt;/strong&gt;, so the logic remains correct but the speed is improved.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The dominance frontier works like this: if we have a block that forks into two paths and merges later (Block A → Block C, Block B → Block C), then C is in A's dominance frontier if A can influence what happens right before C, but A doesn't have "total" control over C (because B is an alternate path that skips A). At every such frontier, &lt;code&gt;mem2reg&lt;/code&gt; knows a PHI node is needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The final result is that we don't have to look at a box in memory anymore and only need pure, high-speed CPU registers. And we never had to figure out PHI placement ourselves.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Rules for &lt;code&gt;mem2reg&lt;/code&gt; to work:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alloca&lt;/code&gt; instructions must be in the entry block of the function (so the memory address is only created once per function call).&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;alloca&lt;/code&gt; variable can only be used for direct load/store operations and can't pass its address into another function.&lt;/li&gt;
&lt;li&gt;Works on single numbers, booleans, and pointers. Won't promote complex data structures like arrays or custom structs (that needs a different pass, &lt;code&gt;sroa&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;&lt;u&gt;Making it happen:&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) &lt;code&gt;NamedValues&lt;/code&gt; changes type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AllocaInst&lt;/span&gt;&lt;span class="o"&gt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NamedValues&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Previously it held &lt;code&gt;Value*&lt;/code&gt;. Now it holds &lt;code&gt;AllocaInst*&lt;/code&gt;. This one line physically transitions the compiler from tracking values to tracking memory locations. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) &lt;u&gt;Adding an &lt;code&gt;alloca&lt;/code&gt;&lt;/u&gt; at the beginning of a block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;AllocaInst&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;CreateEntryBlockAlloca&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TheFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringRef&lt;/span&gt; &lt;span class="n"&gt;VarName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;IRBuilder&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TmpB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TheFunction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;getEntryBlock&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                   &lt;span class="n"&gt;TheFunction&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;getEntryBlock&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;begin&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;TmpB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateAlloca&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;getDoubleTy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TheContext&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VarName&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;ul&gt;
&lt;li&gt;This creates a temporary &lt;code&gt;IRBuilder&lt;/code&gt; pointing at the very first instruction of the entry block, then creates an &lt;code&gt;alloca&lt;/code&gt; there.&lt;/li&gt;
&lt;li&gt;Why the entry block specifically? Because &lt;code&gt;mem2reg&lt;/code&gt; only promotes allocas it can find in the entry block guaranteeing that the box is created exactly once per function call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) &lt;u&gt;Loading/Reading Variables:&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;AllocaInst&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;NamedValues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Name&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;Builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;CreateLoad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;getAllocatedType&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="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c_str&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Every variable reference is now a load from a memory address rather than a direct SSA value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4) &lt;u&gt;Registering variables in &lt;code&gt;NamedValues&lt;/code&gt;:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For each argument, we now make an alloca, store the initial value into it, and register the alloca in &lt;code&gt;NamedValues&lt;/code&gt;. This is what allows function arguments to be mutable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5) &lt;u&gt;Getting rid of the Phi node in the codegen for &lt;code&gt;For&lt;/code&gt;:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The for loop no longer needs a PHI node for its induction variable. &lt;/li&gt;
&lt;li&gt;Instead, we create an alloca for the loop variable in the entry block.&lt;/li&gt;
&lt;li&gt;Store the start value into it.&lt;/li&gt;
&lt;li&gt;At the end of each iteration, load the current value, add the step, and store the result back.&lt;/li&gt;
&lt;li&gt;With this, the PHI node is gone and &lt;code&gt;mem2reg&lt;/code&gt; handles the SSA construction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;6) &lt;u&gt;Adding &lt;code&gt;mem2reg&lt;/code&gt;:&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;TheFPM&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addPass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PromotePass&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This is the &lt;code&gt;mem2reg&lt;/code&gt; pass. It runs first, before &lt;code&gt;InstCombine&lt;/code&gt;, &lt;code&gt;GVN&lt;/code&gt;, etc. and converts all our alloca/load/store patterns back into clean SSA registers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;7) &lt;u&gt;The Assignment Operator:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;=&lt;/code&gt; is parsed as a binary operator with precedence 2 (lower than everything else), but its codegen is a special case as it doesn't follow the normal "emit LHS, emit RHS, do computation" model. Instead:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Op&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&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;VariableExprAST&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;LHSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VariableExprAST&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;LHS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RHS&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;codegen&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;CreateStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Variable&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;Val&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;ul&gt;
&lt;li&gt;It's important to note that the LHS must be a variable and not an expression. &lt;code&gt;(x + 1) = 5&lt;/code&gt; is illegal; only &lt;code&gt;x = 5&lt;/code&gt; is valid. &lt;/li&gt;
&lt;li&gt;And assignment returns the assigned value, which allows chaining like &lt;code&gt;x = (y = z)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;8)&lt;u&gt;&lt;code&gt;var/in&lt;/code&gt; (user-defined local variables):&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;var/in&lt;/code&gt; declares one or more variables, optionally initialises them (defaulting to &lt;code&gt;0.0&lt;/code&gt;), and makes them available for the duration of the body expression. It has the aame procedure as always — lexer, AST, parser, codegen.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;VarExprAST::codegen()&lt;/code&gt; loops over all the declared variables, emits each initialiser before adding the variable to scope (so &lt;code&gt;var a = 1 in var a = a in ...&lt;/code&gt; correctly refers to the outer &lt;code&gt;a&lt;/code&gt;), creates an alloca, stores the initial value, and saves the old binding in &lt;code&gt;OldBindings&lt;/code&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After the body runs, it restores all the old bindings.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fbb3hi56y7atm36xnetup.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.amazonaws.com%2Fuploads%2Farticles%2Fbb3hi56y7atm36xnetup.png" alt=" " width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; Compiling to object code.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's something nice about the &lt;code&gt;alloca&lt;/code&gt; + &lt;code&gt;mem2reg&lt;/code&gt; pattern. We deliberately write something worse (slower, more verbose, and naively use memory where registers would do) and then trust a pass to fix it. The front-end stays simple while the complexity lives in the optimiser, where it's been tested and tuned.&lt;/p&gt;

&lt;p&gt;I suppose not every problem needs to be solved at the level it's encountered. Sometimes the right move is to do the honest and simpler version of something and let a more capable system handle the hard part. The trick is knowing which problems are ours to solve and which ones we can hand off.&lt;/p&gt;

</description>
      <category>llvm</category>
      <category>compilers</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
    <item>
      <title>LLVM #4 — User Defined Operators</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Wed, 13 May 2026 11:56:31 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-4-user-defined-operators-3f58</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-4-user-defined-operators-3f58</guid>
      <description>&lt;p&gt;Kaleidoscope's grammar can now be extended by the user. They can define their own binary and unary operators with custom symbols and precedence, without rewriting the parser or adding new cases to the codegen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built:&lt;/strong&gt; &lt;a href="https://github.com/laharitenneti/Kaleidoscope/commit/89fa3f80c85039674b1e9e73b0c38cf5dc439cb5" rel="noopener noreferrer"&gt;Commit 89fa3f8&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I understood&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The idea is simple. We should allow for users to write something like:&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.amazonaws.com%2Fuploads%2Farticles%2Fxplc91k6k8g5oiqukzth.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.amazonaws.com%2Fuploads%2Farticles%2Fxplc91k6k8g5oiqukzth.png" alt=" " width="786" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and&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.amazonaws.com%2Fuploads%2Farticles%2Fr765qe3v9tg1lamp9c56.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.amazonaws.com%2Fuploads%2Farticles%2Fr765qe3v9tg1lamp9c56.png" alt=" " width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Through which we can do:&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.amazonaws.com%2Fuploads%2Farticles%2F0r4vfcwqd4xy8o13nnzh.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.amazonaws.com%2Fuploads%2Farticles%2F0r4vfcwqd4xy8o13nnzh.png" alt=" " width="800" height="778"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1) &lt;u&gt;Lexer:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We add two new tokens: &lt;code&gt;tok_binary&lt;/code&gt; and &lt;code&gt;tok_unary&lt;/code&gt;, along with their checks in &lt;code&gt;gettok()&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) &lt;u&gt;AST:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We create a &lt;code&gt;UnaryExprAST&lt;/code&gt;, which is pretty similar to &lt;code&gt;BinaryExprAST&lt;/code&gt;, but with one child instead of two. &lt;/li&gt;
&lt;li&gt;We also extend &lt;code&gt;PrototypeAST&lt;/code&gt; to have two new fields: &lt;code&gt;IsOperator&lt;/code&gt; (&lt;code&gt;bool&lt;/code&gt;) and &lt;code&gt;Precedence&lt;/code&gt; (&lt;code&gt;unsigned&lt;/code&gt;). &lt;/li&gt;
&lt;li&gt;A prototype now knows whether it's defining an operator, and if yes, at what precedence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) &lt;u&gt;Parser:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ParsePrototype()&lt;/code&gt; uses a switch-case on &lt;code&gt;CurTok&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If it sees &lt;code&gt;tok_identifier&lt;/code&gt;, it's a regular function, like before.&lt;/li&gt;
&lt;li&gt;But if it sees &lt;code&gt;tok_binary&lt;/code&gt;, it reads the operator character, optionally reads a precedence number (in case of binary), and builds the name &lt;code&gt;"binary" + char&lt;/code&gt; (so &lt;code&gt;binary|&lt;/code&gt;, &lt;code&gt;binary&amp;gt;&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If it sees &lt;code&gt;tok_unary&lt;/code&gt;, it does the same but without precedence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ParseUnary()&lt;/code&gt; is also new. It sits between &lt;code&gt;ParseExpression()&lt;/code&gt; and &lt;code&gt;ParsePrimary()&lt;/code&gt; in the call chain. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the current token looks like a unary operator (an ASCII character that isn't &lt;code&gt;(&lt;/code&gt; or &lt;code&gt;,&lt;/code&gt;), it consumes it and recursively calls &lt;code&gt;ParseUnary()&lt;/code&gt; on the rest. This is for handling chaining (like &lt;code&gt;!!x&lt;/code&gt;). Otherwise, it falls through to &lt;code&gt;ParsePrimary()&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ParseExpression()&lt;/code&gt; and &lt;code&gt;ParseBinOpRHS()&lt;/code&gt; are also updated to call &lt;code&gt;ParseUnary()&lt;/code&gt; instead of &lt;code&gt;ParsePrimary()&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4) &lt;u&gt;Codegen:&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;This is where things change a bit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For binary operators, &lt;code&gt;BinaryExprAST::codegen()&lt;/code&gt; already had a switch-case on &lt;code&gt;Op&lt;/code&gt;. We just add a default case that does a symbol table lookup for &lt;code&gt;"binary" + Op&lt;/code&gt; and emits a call to it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"binary"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Op&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s"&gt;"binary operator not found!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Ops&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&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;Builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;CreateCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ops&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"binop"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;User-defined operators are mostly similar to functions (only with new names). The codegen doesn't bother distinguishing whether it's a function or UDF; It merely finds the function and calls it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Likewise, for unary operators, &lt;code&gt;UnaryExprAST::codegen()&lt;/code&gt; looks up &lt;code&gt;"unary" + Opcode&lt;/code&gt; and calls it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Like I mentioned earlier, before building the function body, if the prototype is a binary operator, we register its precedence in &lt;code&gt;BinopPrecedence&lt;/code&gt;. This change is made in &lt;code&gt;FunctionAST::codegen()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isBinaryOp&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="n"&gt;BinopPrecedence&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getOperatorName&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getBinaryPrecedence&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The grammar is dynamically extensible at JIT runtime: define a new operator and it is immediately available with the right precedence.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What I didn't understand:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;a) How does &lt;u&gt;naming operators&lt;/u&gt; &lt;code&gt;binary|&lt;/code&gt; or &lt;code&gt;unary!&lt;/code&gt; work?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I had this question because we construct a string like &lt;code&gt;binary|&lt;/code&gt; and use it as a function name. &lt;/li&gt;
&lt;li&gt;The thing is, LLVM's symbol table allows names with symbols, so &lt;code&gt;binary|&lt;/code&gt; is a perfectly valid function name in LLVM IR. &lt;/li&gt;
&lt;li&gt;When the user writes &lt;code&gt;x | y&lt;/code&gt;, codegen looks up &lt;code&gt;binary|&lt;/code&gt; in the module, finds the user-defined function, and emits a call. &lt;/li&gt;
&lt;li&gt;It is an ordinary function dispatch dressed up to look like operator syntax.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;b) Why do user-defined operators not need &lt;u&gt;new AST nodes?&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is because the existing &lt;code&gt;BinaryExprAST&lt;/code&gt; and &lt;code&gt;UnaryExprAST&lt;/code&gt; already represent “an operator applied to operands”, and thus don't care whether the operator is built-in or user-defined. &lt;/li&gt;
&lt;li&gt;The only thing that changes is what &lt;code&gt;codegen()&lt;/code&gt; does with an unrecognised &lt;code&gt;Op&lt;/code&gt;. Instead of erroring, it looks the operator up as a function. &lt;/li&gt;
&lt;li&gt;The AST stays blissfully unaware of the distinction.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2F9lsgc4ssvy2ovzgm576o.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.amazonaws.com%2Fuploads%2Farticles%2F9lsgc4ssvy2ovzgm576o.png" alt=" " width="800" height="678"&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fqsk0uvmlvi3mpmr0gf0k.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.amazonaws.com%2Fuploads%2Farticles%2Fqsk0uvmlvi3mpmr0gf0k.png" alt=" " width="800" height="834"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; Mutable variables and SSA construction (the last big piece).&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I have the insanest Tiny Chef obsession. He's the most adorable, sassy, and tiny little bundle of joy I've seen in a long, long time now. He's so refreshingly and unabashedly authentic. Bad singing (but still does it anyway), pop-astrology, yoga, wardrobe dilemmas, and above all — unapologetic optimism. I never imagined I'd find myself rooting for, or seeking life-lessons from a barely legible green little ball of felt. But hey, here we are. When the going gets tough, all we've got to do is put our hand on our heart and say, "You know what? I'm blenough, and it's all going to be blokay." &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.amazonaws.com%2Fuploads%2Farticles%2Fpn4h3tetl9flrlllbuf3.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.amazonaws.com%2Fuploads%2Farticles%2Fpn4h3tetl9flrlllbuf3.png" alt=" " width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>llvm</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
    <item>
      <title>LLVM #3 — Control Flow</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Wed, 06 May 2026 10:16:56 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-3-control-flow-1c42</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-3-control-flow-1c42</guid>
      <description>&lt;p&gt;After all we've done (building a lexer, parser, code-generator, optimiser, and the JIT), we give Kaleidoscope decision-making abilities by adding support for if/then else conditionals and for-loops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built:&lt;/strong&gt; &lt;a href="https://github.com/laharitenneti/Kaleidoscope/commit/5ba58038714185be6bd1471d9719d2539f03fa07" rel="noopener noreferrer"&gt;Commit 5ba5803&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I understood:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;u&gt;If/Then/Else:&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;1) &lt;u&gt;Lexer:&lt;/u&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We add three new tokens, namely &lt;code&gt;tok_if&lt;/code&gt;, &lt;code&gt;tok_then&lt;/code&gt;, and &lt;code&gt;tok_else&lt;/code&gt;, and their corresponding checks in &lt;code&gt;gettok()&lt;/code&gt; (through &lt;code&gt;if (IdentifierStr == ...)&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) &lt;u&gt;AST:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;IfExprAST&lt;/code&gt; holds three child expressions — &lt;code&gt;Cond&lt;/code&gt;, &lt;code&gt;Then&lt;/code&gt;, and &lt;code&gt;Else&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It's worth noting that in Kaleidoscope, everything is an expression. There are no statements. This means that &lt;code&gt;if/then/else&lt;/code&gt; doesn't result in an action, and instead returns a value.&lt;/li&gt;
&lt;li&gt;This is to keep the language consistent, as the codegen never has to resolve whether something is an expression or a statement, as only the former is allowed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) &lt;u&gt;Parser:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;ParseIfExpr()&lt;/code&gt; consumes &lt;code&gt;if&lt;/code&gt;, parses the condition, expects &lt;code&gt;then&lt;/code&gt;, parses the then-expression, expects &lt;code&gt;else&lt;/code&gt;, parses the else-expression, and returns an &lt;code&gt;IfExprAST&lt;/code&gt;. This is simple recursive descent at play.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4) &lt;u&gt;Codegen:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When we generate code for an if-then-else condition, we can't emit instructions linearly anymore. The two branches (then, else) are mutually exclusive, and only one runs. &lt;/li&gt;
&lt;li&gt;This is where we need proper control-flow: a conditional branch, two separate blocks of code, and a merge point.&lt;/li&gt;
&lt;li&gt;That looks like:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="nl"&gt;entry:&lt;/span&gt;
   &lt;span class="nv"&gt;%ifcond&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fcmp&lt;/span&gt; &lt;span class="k"&gt;one&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.0&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;%ifcond&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;%then&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;%else&lt;/span&gt;

&lt;span class="nl"&gt;then:&lt;/span&gt;
   &lt;span class="nv"&gt;%calltmp&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;double&lt;/span&gt; &lt;span class="vg"&gt;@foo&lt;/span&gt;&lt;span class="p"&gt;()&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;%ifcont&lt;/span&gt;

&lt;span class="nl"&gt;else:&lt;/span&gt;
  &lt;span class="nv"&gt;%calltmp1&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;double&lt;/span&gt; &lt;span class="vg"&gt;@bar&lt;/span&gt;&lt;span class="p"&gt;()&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;%ifcont&lt;/span&gt;

&lt;span class="nl"&gt;ifcont:&lt;/span&gt;
  &lt;span class="nv"&gt;%iftmp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;phi&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;%calltmp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%then&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;%calltmp1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%else&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nv"&gt;%iftmp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What I didn't understand (in if/else):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;a) &lt;u&gt;Why basic blocks?&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code is merely a list of instructions. Why not just emit them line-by-line and tell the CPU to 'jump' when it hits an &lt;code&gt;if&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;This is because it would be nightmarish for the optimiser to understand the flow in such a scenario. LLVM hence forces us to use 'basic blocks,' which are chunks of code guaranteed to execute from beginning to end, without any jumping in or out.&lt;/li&gt;
&lt;li&gt;Through blocks, the compiler has a somewhat high-level map (&lt;strong&gt;Ex:&lt;/strong&gt; It knows exactly what happens in a &lt;code&gt;ThenBB&lt;/code&gt;, an &lt;code&gt;ElseBB&lt;/code&gt;, etc.), which matters for optimisation. If the optimiser knows a block runs as a unit, it can reason about the whole block at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;b) &lt;u&gt;Why the Phi node?&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why couldn't I just assign a value to a variable in both blocks, depending on the condition, and then have the &lt;code&gt;ifcont&lt;/code&gt; read the value? For example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="n"&gt;ans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;span class="nf"&gt;if &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="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;ans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;even&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
   &lt;span class="n"&gt;ans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;odd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This is because LLVM uses SSA. The same variable cannot be changed once defined, thereby making the &lt;code&gt;else&lt;/code&gt; branch impossible unless... we use a Phi node.&lt;/li&gt;
&lt;li&gt;This Phi node sits at the junction where both &lt;code&gt;if&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt; branches meet. It does no "calculation" and only looks back at the path the CPU took. At runtime, when the CPU jumps from &lt;code&gt;then&lt;/code&gt; or &lt;code&gt;else&lt;/code&gt; to &lt;code&gt;ifcont&lt;/code&gt;, it already carries information about which block it just came from. &lt;/li&gt;
&lt;li&gt;Phi reads that "came from" information and resolves to the corresponding value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ex:&lt;/strong&gt; my value is &lt;code&gt;calltmp&lt;/code&gt; if we came from &lt;code&gt;then&lt;/code&gt;, or &lt;code&gt;calltmp1&lt;/code&gt; if we came from &lt;code&gt;else&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;c) &lt;u&gt;Why do we insert the Phi node manually&lt;/u&gt; for &lt;code&gt;if/else&lt;/code&gt;, instead of using &lt;code&gt;alloca&lt;/code&gt; + &lt;code&gt;mem2reg&lt;/code&gt; to handle user variables?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is because we know exactly where the merge happens. &lt;/li&gt;
&lt;li&gt;When codegen is processing an &lt;code&gt;IfExprAST&lt;/code&gt;, it knows the shape of the problem before it even starts. There are two branches. They will meet at exactly one point. That meeting point needs exactly one Phi node with exactly two inputs. It's the same every single time, no matter what. So we just write it directly. &lt;/li&gt;
&lt;li&gt;Contrarily, &lt;code&gt;alloca&lt;/code&gt; + &lt;code&gt;mem2reg&lt;/code&gt; is more helpful when code looks like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;x&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="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;x&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Here, &lt;code&gt;x&lt;/code&gt; gets assigned in multiple places. And in a more complex program, those assignments could be scattered across loops, nested ifs, all over the place. The compiler can't just look at the AST node for x and know where to put the Phi. It would have to trace every possible path through the entire program to figure out where values of &lt;code&gt;x&lt;/code&gt; merge. &lt;/li&gt;
&lt;li&gt;So instead of doing that hard work ourselves, we use &lt;code&gt;alloca&lt;/code&gt; (we give &lt;code&gt;x&lt;/code&gt; a slot in memory, let every branch just write to that slot, and then hand it off to &lt;code&gt;mem2reg&lt;/code&gt;). &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mem2reg&lt;/code&gt; is a pass that already knows how to trace control flow, find all the merge points, and insert the right Phi nodes automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;d) &lt;u&gt;Why do we re-fetch the &lt;code&gt;ThenBB&lt;/code&gt; block?&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why is there a need for another &lt;code&gt;ThenBB = Builder -&amp;gt; GetInsertBlock()&lt;/code&gt; if we already had it?&lt;/li&gt;
&lt;li&gt;This is to account for recursion. If the code inside our &lt;code&gt;then&lt;/code&gt; block is a simple &lt;code&gt;x + y&lt;/code&gt;, the pointer is the same. But if it contains another &lt;code&gt;if/else&lt;/code&gt;, the nested &lt;code&gt;if&lt;/code&gt; will create its own blocks and move the builder's insertion point. &lt;/li&gt;
&lt;li&gt;In this case, the builder sits at the end of the nested merge block. Hence, we re-fetch it because the Phi node needs to know the final block that ran, and not necessarily the one we started out with.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2F4lfeb6zezj7jtqv3upjf.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.amazonaws.com%2Fuploads%2Farticles%2F4lfeb6zezj7jtqv3upjf.png" alt=" " width="800" height="875"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;u&gt;The for loop:&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&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="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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&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="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="n"&gt;putchard&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;ul&gt;
&lt;li&gt;There are four parts to this: start value, end condition, step value (defaults to 1.0), and the body. We follow the same 'lexer, parser, AST, codegen' template as &lt;code&gt;if/else&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The IR generated by the codegen looks like:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight llvm"&gt;&lt;code&gt;&lt;span class="nl"&gt;entry:&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;%loop&lt;/span&gt;

&lt;span class="nl"&gt;loop:&lt;/span&gt;
  &lt;span class="nv"&gt;%i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;phi&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%entry&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;%nextvar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%loop&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nv"&gt;%calltmp&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;double&lt;/span&gt; &lt;span class="vg"&gt;@putchard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="m"&gt;42.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;%nextvar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fadd&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nv"&gt;%i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;
  &lt;span class="nv"&gt;%loopcond&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fcmp&lt;/span&gt; &lt;span class="k"&gt;one&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nv"&gt;%booltmp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.0&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;%loopcond&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;%loop&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;%afterloop&lt;/span&gt;

&lt;span class="nl"&gt;afterloop:&lt;/span&gt;
  &lt;span class="k"&gt;ret&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What I didn't understand (in for loops):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;a) &lt;u&gt;Why is there a preheader&lt;/u&gt; in the for-loop, before it even starts?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Again, for the Phi node. It needs to know which block a value came from. When we enter a loop for the first time, we aren't wntering from within the loop itself. As trivial as it sounds, we enter from outside the loop.&lt;/li&gt;
&lt;li&gt;The Preheader thus gives the Phi node a clear starting point for the first iteration, and without which the Phi node wouldn't know what our counter's initial value should be.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;b) &lt;u&gt;Why return a &lt;code&gt;0.0&lt;/code&gt;?&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As of now, our for loops return a value of &lt;code&gt;0.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;This is because we don't have mutable memory yet. The loop variable disappears once the loop ends, causing the body's value to not be accumulated anywhere. &lt;/li&gt;
&lt;li&gt;This is just a temporary placeholder of sorts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;c) &lt;u&gt;What if the variable we use for the loop already exists?&lt;/u&gt; What happens to its value after the loop ends?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is better understood with an example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;
  &lt;span class="nf"&gt;putchard&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The outer &lt;code&gt;i&lt;/code&gt;'s value is 99. And the loop has an &lt;code&gt;i&lt;/code&gt; value of its own. &lt;/li&gt;
&lt;li&gt;The problem is that both live in the same symbol table, &lt;code&gt;NamedValues&lt;/code&gt;. Whenever the loop writes &lt;code&gt;NamedValues["i"] = Variable&lt;/code&gt;, the previous value gets overwritten. Thus, when the loop ends, the pre-loop value is either gone, or replaced by the loop's last value. &lt;/li&gt;
&lt;li&gt;The answer to this is Variable shadowing. Pretty similar to what we did with Lox's environments. We peek at &lt;code&gt;NamedValues&lt;/code&gt; to see that pre-loop value, and save it.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;OldVal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NamedValues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;VarName&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="n"&gt;NamedValues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;VarName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Variable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// loop's i takes over&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we restore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OldVal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;NamedValues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;VarName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OldVal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 99 comes back&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="n"&gt;NamedValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;erase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VarName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// nothing was there before, so clean up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The inner variable doesn't destroy the outer one and only temporarily steps in front of it. Once the loop exits, the outer scope is exactly as it was. Same principle as Lox's chained environments, just done manually here since we're managing the symbol table ourselves.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2F48b7f6mtfe90cesad9le.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.amazonaws.com%2Fuploads%2Farticles%2F48b7f6mtfe90cesad9le.png" alt=" " width="800" height="608"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; Extending Kaleidoscope with user-defined operators. More control.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;br&gt;
Hehe, hello again. Twenty thousand different things came up. I hope to be more consistent with this though. I mean, it is sort of good that I keep coming back. Not even as an obligation because I actually enjoy it very much. But I could do better.&lt;/p&gt;

&lt;p&gt;Anyhoo, I've been walking quite a bit lately (which I absolutely love). It is among the few things I do to relax. I think excellently when I'm walking. Any problem I feel I'm unable to find a solution to seems to solve itself merely 15-20 minutes into a walk. I feel more optimistic and in charge of life. I also learn to, briefly though it may be, set aside all that goes on in my little world, and just feel like I'm part of something bigger. Almost akin to that mix of awe and ease one feels knowing they're in the audience witnessing something grand. Our world too, can be beautiful and inspire hope if we figure out how to look at it. A walk out there helps. &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.amazonaws.com%2Fuploads%2Farticles%2Fj0lqo0wjktc7gsvg3igb.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.amazonaws.com%2Fuploads%2Farticles%2Fj0lqo0wjktc7gsvg3igb.png" alt=" " width="800" height="681"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>llvm</category>
      <category>compilers</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
    <item>
      <title>LLVM #2 — Optimiser Support &amp; JIT Compilation</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Tue, 17 Mar 2026 06:48:33 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-2-optimiser-support-jit-compilation-15hm</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-2-optimiser-support-jit-compilation-15hm</guid>
      <description>&lt;p&gt;Having understood what compiler optimisations are and how they work in theory, we now actually wire them into Kaleidoscope, and then take things one step further by adding a JIT compiler so our REPL can evaluate expressions on the spot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built:&lt;/strong&gt; &lt;a href="https://github.com/laharitenneti/Kaleidoscope/commit/91267d2382ab9ff11d8a907290aca1ae0168b81f" rel="noopener noreferrer"&gt;Commit 91267d2&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I understood:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1) Adding Optimisation Passes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;So far, our codegen was correct but not efficient. The IR we produced was merely a pretty-print of the AST. LLVM provides a &lt;code&gt;FunctionPassManager&lt;/code&gt; to change that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A pass is simply one "go" over the IR that looks for a specific pattern and rewrites it. The &lt;code&gt;FunctionPassManager&lt;/code&gt; contains a sequence of passes and runs them over each function in that order, passing the output of one as input to the next.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The key change is in &lt;code&gt;InitialiseModuleAndManagers()&lt;/code&gt;. After creating the module and the IR builder, we now also have a whole suite of analysis and pass managers:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;TheFPM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_unique&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FunctionPassManager&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;TheLAM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_unique&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LoopAnalysisManager&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;TheFAM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_unique&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FunctionAnalysisManager&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;TheCGAM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_unique&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CGSCCAnalysisManager&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;TheMAM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_unique&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ModuleAnalysisManager&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The four &lt;code&gt;AnalysisManagers&lt;/code&gt; each correspond to a level of LLVM's IR hierarchy — loops, functions, call-graph SCCs, and whole modules.&lt;/li&gt;
&lt;li&gt;They're required so transform passes can look up analysis results when they need them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side note:&lt;/strong&gt; A call graph is one with functions for nodes, with every edge representing a call. So an edge from A to B means A calls B.&lt;/li&gt;
&lt;li&gt;SCCs (Strongly Connected Components) in a call graph represent a group of functions where each function can reach/call the other. (Mutual recursion of sorts)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then we create four transform passes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;TheFPM&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addPass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InstCombinePass&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;    &lt;span class="c1"&gt;// peephole optimisations&lt;/span&gt;
&lt;span class="n"&gt;TheFPM&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addPass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReassociatePass&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;    &lt;span class="c1"&gt;// reorder expressions&lt;/span&gt;
&lt;span class="n"&gt;TheFPM&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addPass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GVNPass&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;            &lt;span class="c1"&gt;// eliminate redundant computations&lt;/span&gt;
&lt;span class="n"&gt;TheFPM&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;addPass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SimplifyCFGPass&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;    &lt;span class="c1"&gt;// clean up unreachable blocks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;A) InstCombinePass:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handles "peephole" optimisations (small/local rewrites).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1+2&lt;/code&gt; becoming &lt;code&gt;3.0&lt;/code&gt; before the program ever runs is constant folding at play, and this pass is what catches it in the generated IR.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;B) ReassociatePass:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reorders expressions to enable more opportunities for other passes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;(x+1)+2&lt;/code&gt; becomes &lt;code&gt;x+3&lt;/code&gt;. Small change, but it means GVN can now recognise that &lt;code&gt;(x+1)+2&lt;/code&gt; and &lt;code&gt;1+(x+2)&lt;/code&gt; are the same thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;C) GVNPass (Global Value Numbering):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assigns a symbolic identity to each new/unique computation and replaces duplicates.&lt;/li&gt;
&lt;li&gt;Ex: if we write &lt;code&gt;(1+2+x)*(x+(1+2))&lt;/code&gt;, both sides of the multiplication are &lt;code&gt;x+3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Without GVN, the IR computes &lt;code&gt;x+3&lt;/code&gt; twice. With it, the result is computed once and reused.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So before GVN:&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;%addtmp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fadd&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="m"&gt;3.000000e+00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;
&lt;span class="nv"&gt;%addtmp1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fadd&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3.000000e+00&lt;/span&gt;
&lt;span class="nv"&gt;%multmp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fmul&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nv"&gt;%addtmp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%addtmp1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After GVN:&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;%addtmp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fadd&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nv"&gt;%x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3.000000e+00&lt;/span&gt;
&lt;span class="nv"&gt;%multmp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fmul&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nv"&gt;%addtmp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%addtmp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;D) SimplifyCFGPass:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cleans up the control flow graph. If a branch can never be entered into, or a block has no predecessors, this pass removes it.&lt;/li&gt;
&lt;li&gt;It's more like keeping the IR tidy after the other passes have finished their work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, we run the pass manager after every function is constructed, just before returning it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;TheFPM&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TheFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TheFAM&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;FunctionPassManager&lt;/code&gt; updates the function in-place. The IR going in is the naive transcription; the IR coming out is the cleaned-up, optimised version.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;2) JIT Compilation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Now that we have nice IR coming out of the optimiser, we want to execute it — not just pretty-print it. That's where the JIT comes in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JIT (Just-In-Time) compilation means converting LLVM IR to native machine code at runtime, in memory, right as the user types. The result is a pointer to executable code we can call directly, as if it were a C function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting it up requires initialising the native target first:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;InitializeNativeTarget&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;InitializeNativeTargetAsmPrinter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;InitializeNativeTargetAsmParser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;TheJIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExitOnErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KaleidoscopeJIT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// Side note: LLVM is cross-platform by design. These initialisation calls are what tell&lt;/span&gt;
&lt;span class="c1"&gt;// the JIT to look at the hardware the user is on and prepare to speak its specific language.&lt;/span&gt;
&lt;span class="c1"&gt;// Ex: Even if we're on Apple Silicon (arm64), LLVM could generate x86 code if we told it to.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And setting the module's data layout to match the JIT's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;TheModule&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;setDataLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TheJIT&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;getDataLayout&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This is important as it ensures that the memory layout of structs, function arguments, and return values matches what the host machine expects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the user types a top-level expression like &lt;code&gt;4+5;&lt;/code&gt;, we wrap it in an anonymous function (&lt;code&gt;__anon_expr&lt;/code&gt;), add the module to the JIT, look up the symbol, and call it:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;ExprSymbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExitOnErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TheJIT&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"__anon_expr"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;FP&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ExprSymbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Evaluated to %f&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FP&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The JIT compiles the LLVM IR to machine code, returns its address, we cast it to a function pointer, and call it like any other native function.&lt;/li&gt;
&lt;li&gt;There's no difference at the hardware level between JIT-compiled code and statically linked machine code (i.e., they live in the same address space).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After calling it, we clean it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;ExitOnErr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RT&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;ResourceTracker&lt;/code&gt; (&lt;code&gt;RT&lt;/code&gt;) is responsible for the JIT'd memory allocated to that anonymous expression. Removing it frees that memory.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;3) The module lifetime problem:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The issue in the REPL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ready&amp;gt; def testfunc(x y) x + y*2;
ready&amp;gt; testfunc(4, 10);
Evaluated to 24.000000

ready&amp;gt; testfunc(5, 10);
LLVM ERROR: Program used external function 'testfunc' which could not be resolved!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;testfunc&lt;/code&gt; was defined in the same module as the anonymous expression for &lt;code&gt;testfunc(4, 10)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When we removed that module from the JIT to free the memory for the anonymous expression, we inadvertently deleted &lt;code&gt;testfunc&lt;/code&gt; along with it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The solution: Every function definition gets its own module. The JIT can resolve calls across module boundaries, so &lt;code&gt;testfunc&lt;/code&gt; lives in its own module indefinitely. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each new anonymous expression gets a fresh module, which we remove after execution. Function definitions stay.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;But this creates a &lt;u&gt;new problem&lt;/u&gt;: when the codegen for a new anonymous expression tries to emit a call to &lt;code&gt;testfunc&lt;/code&gt;, that function doesn't exist in the current module. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The IR emitter needs at least a declaration of &lt;code&gt;testfunc&lt;/code&gt; to generate a valid &lt;code&gt;call&lt;/code&gt; instruction.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;u&gt;solution&lt;/u&gt; is &lt;code&gt;getFunction()&lt;/code&gt;, a helper that first checks the current module for a declaration, and if it doesn't find one, regenerates it from &lt;code&gt;FunctionProtos&lt;/code&gt; (a map of the most recent prototype for every function we've seen):&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;getFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TheModule&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;getFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&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;F&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;FI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FunctionProtos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FI&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;FunctionProtos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&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;FI&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;second&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;codegen&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;nullptr&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;ul&gt;
&lt;li&gt;So the function "body" lives in its own module in the JIT.&lt;/li&gt;
&lt;li&gt;The function "declaration" gets re-emitted into each new module that needs to call it.&lt;/li&gt;
&lt;li&gt;The JIT links them at call time.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fllyp4upwj4mphefnw20r.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.amazonaws.com%2Fuploads%2Farticles%2Fllyp4upwj4mphefnw20r.png" alt=" " width="800" height="1062"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; Control flow (if/then/else and loops).&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;br&gt;
Learning the piano taught me something about passes. When you're learning a new piece, you don't play the whole thing perfectly on the first try. You play it slowly, fix the wrong notes, correct the timing, then fix the phrasing, then the dynamics. Each run is a pass, and each one builds on the last until what comes out sounds nothing like the stumbling first attempt, but means exactly the same thing. The optimiser does the same thing. The IR that enters is technically correct and the IR that exits is still correct; only better.&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.amazonaws.com%2Fuploads%2Farticles%2F8kgwhyquel08cbbecl0l.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.amazonaws.com%2Fuploads%2Farticles%2F8kgwhyquel08cbbecl0l.png" alt=" " width="800" height="1007"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>llvm</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
    <item>
      <title>Compiler Optimisations</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Tue, 24 Feb 2026 11:03:40 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/compiler-optimisations-6al</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/compiler-optimisations-6al</guid>
      <description>&lt;p&gt;Before we perform compiler optimisations, we must know what they are, why they're needed, and how we do them.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;What:&lt;/u&gt;&lt;br&gt;
Compiler Optimisations are systematic transformations that rewrite our source code into faster, smaller machine code without changing its meaning.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Why:&lt;/u&gt;&lt;br&gt;
They're needed because programs are full of inefficiencies—redundant calculations, impossible branches, or operations the CPU could do cheaper. Raw code from the parser is readable but slow, so optimisations squeeze out every drop of performance. &lt;/p&gt;

&lt;p&gt;Importantly, many impactful optimisations aren't fully hardware-agnostic. Universal ones like "this expression is always 0, delete it" work anywhere, but the big wins (like using SIMD registers, scheduling for a chip's pipeline, or picking specific CPU instructions) are often hardware-specific. &lt;/p&gt;

&lt;p&gt;Without a shared layer like LLVM's IR, every backend duplicates this effort. LLVM lets you write optimisations once against the IR, with backends handling hardware translation later.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;How:&lt;/u&gt;&lt;br&gt;
Compilers apply optimisations in structured passes over the code. First local (within basic blocks, like folding constants), then global (across the function, like dead code elimination), often in multiple rounds. Each pass analyses data flow, rewrites IR, and repeats until no more gains are possible.&lt;/p&gt;

&lt;p&gt;As I'd mentioned in the previous post, LLVM uses SSA for this. In action, it's the &lt;code&gt;%addtmp&lt;/code&gt; or &lt;code&gt;%multmp1&lt;/code&gt; variables we keep seeing in Kaleidoscope's output. While it looks redundant (mostly because we're used to seeing the output directly), it's what optimisation ultimately depends on.&lt;/p&gt;

&lt;p&gt;In most programming languages, we can change/reassign a variable's value whenever we want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x = 5
x = x + 2
x = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In SSA form, every variable is assigned exactly once. If we wish to change the value of x, the compiler creates a new version of it. (Remember persistent data structures and the blockchain example from the Lox resolver?) Hence, the code above looks 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;%x1 = 5
%x2 = %x1 + 2
%x3 = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, through dead-code elimination (see below), it would become more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%x3 = 10 #because %x2 isn't used anywhere and %x3 comes immediately after
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is to address the issue of Data Flow Analysis — where did a value come from?&lt;/p&gt;

&lt;p&gt;In non-SSA code, if you see a variable on a certain line, you'll have to look at every line before it to figure out which assignment currently "owns" that variable.&lt;/p&gt;

&lt;p&gt;But in SSA, the name of the variable is its definition. We know exactly where &lt;code&gt;%x2&lt;/code&gt; was born and what value it holds.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How SSA is used in Compiler Optimisations:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) &lt;u&gt;Constant Propagation and Folding:&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;It took me a while to understand that these were two different things. But it's only that they both feed into each other.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Propagation&lt;/strong&gt; is simply fetching values. If I know the value of a constant used in certain expressions, I'll just replace that variable with its value directly.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r = 5
area = 3.14 * r * r
#becomes
area = 3.14 * 5 * 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Folding&lt;/strong&gt; is merely precomputing known values — the actual math is performed at compile-time instead of runtime. It's like saying, "I know the answer to this. Why do I make the CPU calculate it later?"
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;area = 3.14 * 5 * 5
#becomes
area = 78.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) &lt;u&gt;Value Range Propagation:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tracking the possible range of values a variable can hold, so the compiler can make smarter decisions later in the program.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (x &amp;gt; 0 &amp;amp;&amp;amp; x &amp;lt; 10):
    y = x * 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The compiler now knows &lt;code&gt;y&lt;/code&gt; must be in the range (0, 20). If a later condition asks something like &lt;code&gt;if (y &amp;gt; 100)&lt;/code&gt;, the compiler can eliminate that branch entirely because it's impossible.&lt;/li&gt;
&lt;li&gt;SSA makes it easy to track a variable's range through the program since each definition has a single, known origin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) &lt;u&gt;Sparse Conditional Constant Propagation:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Constant propagation + branch awareness; only propagating values along branches that are actually reachable.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x = 5
if (x &amp;gt; 10):
    y = x * 2   #dead; compiler knows x = 5 can never satisfy x &amp;gt; 10
else:
    y = x + 1   #compiler propagates: y = 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;SSA names are unique per definition, so once the compiler knows a branch is dead, every variable inside it is unreachable too. This avoids wastage of effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4) &lt;u&gt;Dead Code Elimination:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removing code that will never execute or whose result is never used.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x = 10
y = x + 5    #y is never used again
return x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x = 10
return x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Every SSA variable has a list of uses. If that list is empty, the variable (and the code that produced it) is eliminated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5) &lt;u&gt;Global Value Numbering:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assigning a symbolic "number" to each unique computation, then replacing duplicates that produce the same result with a single reference.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a = x + y
b = x + y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Both expressions get the same value number. The compiler replaces &lt;code&gt;b&lt;/code&gt; with &lt;code&gt;a&lt;/code&gt;, computing &lt;code&gt;x + y&lt;/code&gt; only once, even if &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are in different parts of the function. Again, SSA at play.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;6) &lt;u&gt;Partial Redundancy Elimination:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removing calculations that are redundant on some paths through the program, by hoisting them to a point where they cover all paths.&lt;/li&gt;
&lt;li&gt;In the following example, in case of &lt;code&gt;heavy_traffic = true&lt;/code&gt;, The CPU calculates &lt;code&gt;distance/speed&lt;/code&gt; inside the &lt;code&gt;if&lt;/code&gt; block, then calculates it again at the end. That’s unnecessary.&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;else&lt;/code&gt;, The CPU skips the first calculation and only does it once at the end.&lt;/li&gt;
&lt;li&gt;Compiler optimisation's goal is to make the work uniform so the final result is always "pre-calculated."
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (heavy_traffic):
    time = distance/speed
else:
    pass

total_time = distance/speed #why calculate the same thing a second time?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler "hoists" the missing calculation into the else block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (heavy_traffic):
    tmp = distance/speed
    time = tmp
else:
    tmp = distance/speed

#Now the final result just uses the 'tmp' already in the register
total_time = tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;It might look like we're adding code, but we’re actually ensuring that no matter which path the CPU takes, it only performs the heavy division exactly once.&lt;/li&gt;
&lt;li&gt;SSA tracks exactly where every value was computed. So the compiler can see at a glance, "this path did the work, that one didn't."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;7) &lt;u&gt;Strength Reduction:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swapping out a costly operation for a cheaper one that produces the same result.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;y = x * 8
# becomes
y = x &amp;lt;&amp;lt; 3 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Multiplying by 8 is multiplying by 2³. In binary, multiplying by 2 is a left shift by one bit, so multiplying by 8 is simply a left shift by three bits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The CPU performs this using a barrel shifter, which shifts bits in a single clock cycle. It’s basically a network of wires and switches that reroutes bits to new positions simultaneously; more like rearranging than computing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;General multiplication is more complex. The hardware must perform multiple additions and shifts internally, requiring more circuitry and cycles.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;8) &lt;u&gt;Register Allocation:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Mapping the potentially unlimited SSA virtual variables down to the finite number of physical CPU registers available at runtime.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ex:&lt;/strong&gt; If the function produces &lt;code&gt;%tmp1&lt;/code&gt; through &lt;code&gt;%tmp20&lt;/code&gt; but the CPU only has 6 registers, the compiler figures out which temporaries work at the same time and assigns them registers accordingly, spilling the rest to the stack. Good allocation means fewer memory round-trips and faster code.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; Having established some context to compiler-optimisations, we can proceed with adding support for them and JIT, for Kaleidoscope.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;br&gt;
Optimisation, at its core, is about simplicity. There's an incredible story from the Mahabharata about this. Dronacharya, the renowned guru, gathered his students—already among the world's finest—to find the greatest archer. The goal was the eye of a wooden bird perched high in a tree. Pointing to it, he asked each student what they saw. They described the tree, the bird’s feathers, and the sky. He dismissed them. When he asked Arjuna, the latter replied, “I only see the eye of the bird.” He shot, and the arrow struck the centre instantly. By treating everything except the target as “noise,” we focus all resources on the singular point of impact. Compiler optimisations do the same, ensuring the CPU never looks at anything but the essential logic. After all, &lt;em&gt;Neti Neti&lt;/em&gt;-ing eventually led those ancient minds to the Ultimate Answer.&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>computerscience</category>
      <category>learning</category>
      <category>llvm</category>
    </item>
    <item>
      <title>LLVM #1 — Lexer, Parser, Codegen</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Fri, 13 Feb 2026 06:14:09 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-1-lexer-parser-codegen-51of</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-1-lexer-parser-codegen-51of</guid>
      <description>&lt;p&gt;The Lexer/Scanner and Parser aren’t very different from what we’ve done earlier. Both are initial/front-end phases of compilation, after which the code is converted into LLVM IR.&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.amazonaws.com%2Fuploads%2Farticles%2Fbuia113ocfszp9jyb2fy.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.amazonaws.com%2Fuploads%2Farticles%2Fbuia113ocfszp9jyb2fy.png" alt=" " width="772" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built:&lt;/strong&gt; &lt;a href="https://github.com/laharitenneti/Kaleidoscope/commits/main/src?since=2025-12-05&amp;amp;until=2026-02-09" rel="noopener noreferrer"&gt;Commits 15710fe, 2562e61&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I understood:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) &lt;u&gt;The Lexer:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads raw text input one character at a time and groups them into meaningful chunks of code called Tokens.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;enum Token&lt;/code&gt; defines the kinds of words the language can process.
&lt;code&gt;gettok()&lt;/code&gt; loops through characters, skips whitespaces, recognises keywords/numbers, and handles comments too (by skipping text after &lt;code&gt;#&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) &lt;u&gt;AST:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once the tokens are ready, we need to understand how they relate to each other. A tree is the best way to do the same.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ExprAST&lt;/code&gt; is the base/parent class for all the nodes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;public: virtual ~ExprAST() = default;&lt;/code&gt;: Here, &lt;code&gt;Expr&lt;/code&gt; objects will later be manipulated through pointers to the base (&lt;code&gt;ExprAST&lt;/code&gt;) class. &lt;/li&gt;
&lt;li&gt;Without &lt;code&gt;virtual&lt;/code&gt;, if we wish to delete a subclass node, only the base class' destructor will be called, not the subclass' destructor. Any data stored in the subclass won't be deleted and can affect other areas through leaks.&lt;/li&gt;
&lt;li&gt;Thus, we use &lt;code&gt;virtual&lt;/code&gt; to delete/release all memory/resources used by the derived (sub)class.&lt;/li&gt;
&lt;li&gt;Similarly, &lt;code&gt;NumberExprAST' represents a number,&lt;/code&gt;VariableExprAST&lt;code&gt;represents a variable,&lt;/code&gt;BinaryExprAST&lt;code&gt;represents an operator with two operands,&lt;/code&gt;CallExprAST&lt;code&gt;represents a function call,&lt;/code&gt;PrototypeAST&lt;code&gt;represents a function signature (not the body), and&lt;/code&gt;FunctionAST` represents a full function (prototype + body)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) &lt;u&gt;The Parser:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses operator-precedence parsing for building AST objects for binary expressions and recursive descent parsing for everything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ex:&lt;/strong&gt; On seeing a number token, it creates a &lt;code&gt;NumberExprAST&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4) &lt;u&gt;Code Generation:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is LLVM specific. We traverse through the AST we've built and ask each node to &lt;code&gt;codegen()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ex:&lt;/strong&gt; In &lt;code&gt;NumberExprAST&lt;/code&gt;, we use &lt;code&gt;ConstantFP::get&lt;/code&gt; to create a floating-point constant in LLVM's internal format.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NamedValues&lt;/code&gt; is a symbol table/dictionary for remembering where (in which specific memory location/register) a variable is stored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Note:&lt;/strong&gt; LLVM uses Static Single Assignment (SSA), meaning its "virtual registers" can only be assigned once. It's why we see names like &lt;code&gt;%multmp&lt;/code&gt; and &lt;code&gt;%multmp1&lt;/code&gt; in our results.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BinaryExprAST::codegen&lt;/code&gt; is for generating code for the left and right sides recursively. &lt;/li&gt;
&lt;li&gt;Then, we use &lt;code&gt;Builder&lt;/code&gt; to create the math instruction (like &lt;code&gt;Builder-&amp;gt;CreateFAdd&lt;/code&gt; for float addition)&lt;/li&gt;
&lt;li&gt;For &lt;code&gt;&amp;lt;&lt;/code&gt;, it converts the result to a float (0.0 or 1.0) as Kaleidoscope only uses doubles.&lt;/li&gt;
&lt;li&gt;Similarly, &lt;code&gt;FunctionAST::codegen&lt;/code&gt; creates a new function in the LLVM &lt;code&gt;Module&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Module&lt;/code&gt; here is like a project folder; a container that holds all our functions together so they can see and call each other.&lt;/li&gt;
&lt;li&gt;It creates a block called &lt;code&gt;entry&lt;/code&gt;. LLVM code lives inside such basic blocks (chunks of instructions)&lt;/li&gt;
&lt;li&gt;It tells the &lt;code&gt;Builder&lt;/code&gt; to start writing instructions into this new block.&lt;/li&gt;
&lt;li&gt;It adds the function arguments to &lt;code&gt;NamedValues&lt;/code&gt; so the body can use them.&lt;/li&gt;
&lt;li&gt;Finally, it creates a &lt;code&gt;ret&lt;/code&gt; instruction to finish the function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5) &lt;u&gt;Driver Code:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;MainLoop&lt;/code&gt; is an infinite loop printing &lt;code&gt;ready&amp;gt;&lt;/code&gt; waiting for the user to type.&lt;/li&gt;
&lt;li&gt;It switches based on what is typed (&lt;code&gt;def&lt;/code&gt;, `extern, or just math) and calls the appropriate handler.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HandleDefinition&lt;/code&gt; parses the code, generates the LLVM IR, and prints it to the screen.&lt;/li&gt;
&lt;li&gt;It also sets up the operator precedence so the math logic works correctly, initialises the module, and starts the loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s understand this through an example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;def foo(x) x + 1&lt;/code&gt; is converted by the Lexer into &lt;code&gt;[tok_def] [tok_identifier “foo”] [ ( ] [tok_identifier “x”] [ ) ] [tok_identifier “x”] [ + ] [tok_number1]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The Parser turns it into a &lt;code&gt;FunctionAST&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;The codegen turns that object into LLVM IR &lt;code&gt;define double @foo(double %x) { … }&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;u&gt;Results:&lt;/u&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.amazonaws.com%2Fuploads%2Farticles%2Fy18g4vc7r3vxx3zwszp0.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.amazonaws.com%2Fuploads%2Farticles%2Fy18g4vc7r3vxx3zwszp0.png" alt=" " width="800" height="132"&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fuhdyb203ljte7xczdr2a.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.amazonaws.com%2Fuploads%2Farticles%2Fuhdyb203ljte7xczdr2a.png" alt=" " width="800" height="137"&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fl2df3yhosv5bh7t37jgv.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.amazonaws.com%2Fuploads%2Farticles%2Fl2df3yhosv5bh7t37jgv.png" alt=" " width="800" height="279"&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Flqmze34cy46qarhtrimb.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.amazonaws.com%2Fuploads%2Farticles%2Flqmze34cy46qarhtrimb.png" alt=" " width="798" height="844"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; Optimising and Just-in-time (JIT) compilation!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;br&gt;
The one thing that calms me the most is playing the piano. You see, mindfulness and being focused is a rather difficult thing, especially in the attention economy. So it feels particularly wonderful and refreshing when an activity demands our time, patience, and full focus, lest we compromise on the quality of its outcome. “Right hand first, left hand next, and then do it together.” I feel like every single inch of my brain is dedicated to getting that one bar right. And nothing feels more rewarding than seeing (or maybe hearing?) the fruits of our labour. Whenever I’ve felt bored, lost, confused, overwhelmed, or even happy—the piano has helped me move on feeling better. My mom wanted us to learn some or the other instrument. I was merely “okay” with the idea of learning the piano, but never could I have fathomed how important it would become to me. Life’s little surprises are often like that, with some of the most beautiful experiences coming to us when we least expect it. So it doesn’t hurt to try and be a little open.&lt;/p&gt;

</description>
      <category>llvm</category>
      <category>compilers</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
    <item>
      <title>LLVM — Introduction and Setup</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Wed, 11 Feb 2026 12:37:50 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-introduction-and-setup-4c3c</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/llvm-introduction-and-setup-4c3c</guid>
      <description>&lt;p&gt;Helloo! Welcome to my next project—The LLVM framework (the Kaleidoscope tutorial, to be precise). I decided to try this out as soon as I finished the jlox Interpreter in Robert Nystrom’s Crafting Interpreters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s LLVM?&lt;/strong&gt;&lt;br&gt;
The ‘Low Level Virtual Machine’ (LLVM) is a full compiler infrastructure that can take various programming languages, generate LLVM Intermediate Representation (IR), optimise it, and finally convert it into hardware-specific machine level code. This low level code is then run on the CPU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why am I doing this?&lt;/strong&gt;&lt;br&gt;
The Abstract Syntax Trees (ASTs) borne out of interpreting any code are specific to its language. When we use LLVM’s IR, which is aware of hardware concepts like registers, memory, arithmetic operations, etc., our language becomes more universal.&lt;/p&gt;

&lt;p&gt;As against assembly language, which is very platform/hardware dependent, LLVM (whose IR almost resembles assembly language) supports various machines like Intel x86, ARM, RISC-V, etc. I find heterogenous hardware very fascinating, especially the prospect of hardware-agnostic compilation. That’s pretty much what drew me LLVM.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;How I set it up:&lt;/strong&gt;&lt;br&gt;
While there are many tutorials to familiarise oneself with the platform, I (surprisingly) found the documentation wonderful. Some of it I skipped, but the set-up and introduction were quite helpful. I still haven’t figured out a lot, so I’m taking things slowly. For anyone wanting to start out with LLVM, I’d recommend these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://llvm.org/docs/GettingStarted.html#getting-the-source-code-and-building-llvm" rel="noopener noreferrer"&gt;Getting the source code and building LLVM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://llvm.org/docs/GettingStarted.html#an-example-using-the-llvm-tool-chain" rel="noopener noreferrer"&gt;An example for using the LLVM tool chain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disclaimer:&lt;/strong&gt; Most of what I did below is adapted from these tutorials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1) &lt;u&gt;Getting LLVM running&lt;/u&gt; on macOS (especially Apple Silicon) is very straightforward with &lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;Homebrew.&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;llvm
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/homebrew/opt/llvm/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CMAKE_PREFIX_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/homebrew/opt/llvm"&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) &lt;u&gt;Managing Dependencies:&lt;/u&gt; I used CMake.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llvm &lt;span class="nt"&gt;-config&lt;/span&gt; —version
&lt;span class="nb"&gt;cd&lt;/span&gt; ~
&lt;span class="nb"&gt;mkdir &lt;/span&gt;toy-compiler
&lt;span class="nb"&gt;cd &lt;/span&gt;toy-compiler
&lt;span class="nb"&gt;mkdir &lt;/span&gt;src build
nano CMakeLists.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Here's the &lt;code&gt;CMakeLists.txt&lt;/code&gt; (configuration) for the toy-compiler/Kaleidoscope project I'll be doing. It serves as a project manager, telling the compiler where the LLVM "brains" are and which specific libraries (like JIT, native codegen) we want to use. &lt;/li&gt;
&lt;li&gt;It doesn't compile the code itself, and instead gathers all the ingredients (libraries, headers, compiler settings) so the build tool (Ninja, in my case) knows what to do.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="nb"&gt;cmake_minimum_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;VERSION 3.13&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;ToyCompiler LANGUAGES C CXX&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;CMAKE_CXX_STANDARD 17&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;CMAKE_CXX_STANDARD_REQUIRED ON&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;LLVM REQUIRED CONFIG&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;STATUS &lt;span class="s2"&gt;"Found LLVM &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LLVM_PACKAGE_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;add_definitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LLVM_DEFINITIONS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;include_directories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LLVM_INCLUDE_DIRS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;link_directories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LLVM_LIBRARY_DIRS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;add_executable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;toy src/main.cpp&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# For JIT and native codegen&lt;/span&gt;
&lt;span class="nf"&gt;llvm_map_components_to_libnames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;llvm_libs core orcjit native&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;target_link_libraries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;toy &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;llvm_libs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) &lt;u&gt;Verifying the build system:&lt;/u&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To ensure the libraries are linking correctly, I wrote a simple sanity check in &lt;code&gt;src/main.cpp&lt;/code&gt; using LLVM's output stream, &lt;code&gt;llvm::outs()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"llvm/Support/raw_ostream.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="kt"&gt;int&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="n"&gt;llvm&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;outs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"LLVM setup works!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&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;ul&gt;
&lt;li&gt;To actually compile the project, I used Ninja. It's a small build system focused on speed.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;ninja
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/toy-compiler/build
cmake &lt;span class="nt"&gt;-G&lt;/span&gt; Ninja ..
ninja
./toy -&amp;gt; LLVM setup works!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4) &lt;u&gt;An example:&lt;/u&gt; To truly understand how compilation happens at lower levels, it helps to follow a program through the entire pipeline, from high level code to bit code, and finally to a native binary. &lt;br&gt;
&lt;code&gt;test1.c&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="kt"&gt;int&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="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"First go at LLVM!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&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;ul&gt;
&lt;li&gt;
&lt;code&gt;clang -O3 -emit-llvm test1.c -c -o test1.bc&lt;/code&gt; (Clang is the C compiler macOS uses as a front-end; &lt;code&gt;emit-llvm&lt;/code&gt; creates the bitcode.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lli test1.bc&lt;/code&gt; (&lt;code&gt;lli&lt;/code&gt; directly executes the bytecode.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llvm-dis &amp;lt; test1.bc | less&lt;/code&gt; (This is for looking at the human-readable LLVM assembly code.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;llc test1.bc -o test1.s&lt;/code&gt; (&lt;code&gt;llc&lt;/code&gt; converts bitcode to native assembly.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gcc test1.s -o test1.native&lt;/code&gt; (This assembles the native file into a program.)&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;./test1.native&lt;/code&gt; → First go at LLVM!&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt;&lt;br&gt;
The lexer and parser for Kaleidoscope!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;br&gt;
Sometimes, I get these sudden, intense urges to just... be somewhere. I often find myself wishing for Doraemon’s "Anywhere Door" so I could instantly step into a completely different world. It’s not that I don’t enjoy where I am, but there’s an incomparable high that comes from being somewhere totally foreign.&lt;/p&gt;

&lt;p&gt;Travel has always been my ultimate meditation. I once &lt;a href="https://www.stevenmtaylor.com/essays/from-the-unreal-to-the-real/#:~:text=The%20same%20thing,place%20to%20them." rel="noopener noreferrer"&gt;read&lt;/a&gt; that our memories of new places are so vivid because we become hyper-sensitive to our surroundings. In our daily lives—the same commute, the same routine—we stop truly "seeing" the world. We stop noticing the colour of the sky, the old man reading the newspaper by the shopfront, or the little puppy prancing around with an aluminium foil. But in a new place, with your senses wide open, you notice everything.&lt;/p&gt;

&lt;p&gt;A few years ago, I visited Sikkim (an absolutely stunning state in our North-East), and I’ve never felt more alive. Even now, I can recall every sound, smell, and sight from that trip with perfect clarity. That level of observation taught me how to be more attentive to the daily moments in my life. Ever since, I've tried to carry that traveler’s spirit with me, finding joy in the small details—in the "normal" and the everyday.&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.amazonaws.com%2Fuploads%2Farticles%2Fjucljmm85ixb9yvhnvcz.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.amazonaws.com%2Fuploads%2Farticles%2Fjucljmm85ixb9yvhnvcz.png" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>llvm</category>
      <category>compilers</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
    <item>
      <title>Extensions — Lists and for-in loops</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Mon, 09 Feb 2026 07:18:03 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/extensions-lists-and-for-in-loops-194f</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/extensions-lists-and-for-in-loops-194f</guid>
      <description>&lt;p&gt;Well, here we are. Here’s my interpreter. I want to get philosophical already, but I’ll save it for the musings section. Anyway, I’ve extended Robert’s jlox interpreter by adding support for lists and for-in loops.&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.amazonaws.com%2Fuploads%2Farticles%2Fldakyzbw3c42rpzr0kzn.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.amazonaws.com%2Fuploads%2Farticles%2Fldakyzbw3c42rpzr0kzn.png" alt=" " width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built:&lt;/strong&gt; &lt;a href="https://github.com/laharitenneti/jlox-interpreter/commits/main/lahari-jlox/src/jloxinterpreter?since=2026-02-05&amp;amp;until=2026-02-06" rel="noopener noreferrer"&gt;Commits 8409dee, 1f4f09d, 380a16d, 72972f6, 64fdc5f&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I understood:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I. Lists&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) &lt;u&gt;The basics:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We start by adding the tokens/keywords in the &lt;code&gt;TokenType.java&lt;/code&gt; file. Because lists use square brackets, we add them (&lt;code&gt;LEFT_BRACKET&lt;/code&gt;, &lt;code&gt;RIGHT_BRACKET&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;We then update the &lt;code&gt;scanToken()&lt;/code&gt; function in the Scanner to handle the newly added tokens.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;case '[': addToken(LEFT_BRACKET); break;
case ']': addToken(RIGHT_BRACKET); break;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;We then create two types of expressions through &lt;code&gt;GenerateAst&lt;/code&gt;, one for creating a list (&lt;code&gt;Array&lt;/code&gt;) and another for accessing or modifying it (&lt;code&gt;Subscript&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) &lt;u&gt;Parsing for lists:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We update the &lt;code&gt;primary()&lt;/code&gt; method to catch a &lt;code&gt;LEFT_BRACKET&lt;/code&gt;, create a new list, and check for comma-separated elements until the &lt;code&gt;RIGHT_BRACKET&lt;/code&gt; is caught.&lt;/li&gt;
&lt;li&gt;We also use chaining to check for sub indices, which if found, are made into a new subscript.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;else if (match(LEFT_BRACKET)) {
    Expr index = expression();
    Token bracket = consume(RIGHT_BRACKET, "Expect ']' after index.");
    expr = new Expr.Subscript(expr, bracket, index);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2F7obkpbooisqnevhvnujr.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.amazonaws.com%2Fuploads%2Farticles%2F7obkpbooisqnevhvnujr.png" alt=" " width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3) &lt;u&gt;Visitor methods in the Resolver:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because we’ve added new AST nodes (Array, Subscript)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4) &lt;u&gt;Actual List Logic:&lt;/u&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use a java list to represent a lox list, with every expression/element inside evaluated and stored in the lox list. The SubscriptExpr’s job is to check whether the variable we’re trying to index is actually a list, if the index is a number and within the list size.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fvnn5lxqcta4n9tq5y0y9.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.amazonaws.com%2Fuploads%2Farticles%2Fvnn5lxqcta4n9tq5y0y9.png" alt=" " width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5) &lt;u&gt;List functions:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finally, we implement global native functions that act as an interface between the user and the Java &lt;code&gt;ArrayList&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;push(list, item)&lt;/code&gt; calls &lt;code&gt;add()&lt;/code&gt; on the java list to append an item to the array.&lt;/li&gt;
&lt;li&gt;pop(list)  finds the size() - 1th element and uses java’s &lt;code&gt;remove()&lt;/code&gt; method to delete it (the last item) from the list and return it.&lt;/li&gt;
&lt;li&gt;Likewise, len(list) uses java’s size() function to fetch the array’s length.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fgyhzwlw5p2jl5hkp2k5a.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.amazonaws.com%2Fuploads%2Farticles%2Fgyhzwlw5p2jl5hkp2k5a.png" alt=" " width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;II. For-in loops&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) &lt;u&gt;The basics:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the &lt;code&gt;IN&lt;/code&gt; token to the enum in &lt;code&gt;TokenType.java&lt;/code&gt; and &lt;code&gt;in&lt;/code&gt; to the keywords map in the Scanner.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;checkNext()&lt;/code&gt; is a helper function we use to look ahead for an &lt;code&gt;in&lt;/code&gt; keyword when we encounter &lt;code&gt;for (var i …&lt;/code&gt;. If found, the Parser will build a list-iterator. If &lt;code&gt;in&lt;/code&gt; is not found, it is considered a normal for-loop.&lt;/li&gt;
&lt;li&gt;Next, update the &lt;code&gt;forStatement()&lt;/code&gt; function in the Parser with a check for &lt;code&gt;in&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) &lt;u&gt;Desugaring:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We then desugar for the parser to break down the complex and user-friendly syntax into simpler pieces the interpreter can handle (primitives). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side note:&lt;/strong&gt; Desugaring is also a life-lesson. Breaking down complex problems into smaller and simpler ideas always improves the possibility of solving them. Richard Feynman all the way!&lt;/li&gt;
&lt;li&gt;It creates two hidden variables - &lt;code&gt;_list&lt;/code&gt; and &lt;code&gt;_i&lt;/code&gt; to store the collection and counter (starting at 0.0) respectively.&lt;/li&gt;
&lt;li&gt;The Parser then creates an AST that resembles a &lt;code&gt;while&lt;/code&gt; loop. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ex:&lt;/strong&gt; &lt;code&gt;for (var item in myList) { print item; }&lt;/code&gt; effectively becomes:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    var _list = myList; 
    var _i = 0;
    while (_i &amp;lt; len(_list)) {
        {
            var item = _list[_i]; // The "item" variable is created here
            print item;           // The original body
        }
    _i = _i + 1;            // The increment
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;listVar&lt;/code&gt; and &lt;code&gt;itemVar&lt;/code&gt; in the &lt;code&gt;forStatement()&lt;/code&gt; are initialisers,  which occur once before the loop begins.&lt;/li&gt;
&lt;li&gt;The forStatement() creates a variable meant for the user (like &lt;code&gt;item&lt;/code&gt;) and uses &lt;code&gt;Expr.Subscript&lt;/code&gt; to fetch its value from &lt;code&gt;_list&lt;/code&gt; at index &lt;code&gt;_i&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The next block increments by adding 1 to &lt;code&gt;_i&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The condition block calls the native &lt;code&gt;len()&lt;/code&gt; function to check if the index &lt;code&gt;_i&lt;/code&gt; is still less than the list size.&lt;/li&gt;
&lt;li&gt;We use curly braces for scope, to ensure that the hidden variables &lt;code&gt;_list&lt;/code&gt; and &lt;code&gt;_i&lt;/code&gt; are destroyed as soon as the loop ends. Else, trying to use &lt;code&gt;_list&lt;/code&gt; later in the program might confuse the interpreter as it was never previously defined.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fskjqayof0zsbuz1cg2f3.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.amazonaws.com%2Fuploads%2Farticles%2Fskjqayof0zsbuz1cg2f3.png" alt=" " width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;br&gt;
That brings the interpreter to an end. Not “The End,” (because I’m going to keep extending it) but you could say all that I’d intended to do for this project has been accomplished. I took on something that puzzled me, sat and reasoned with it, learnt from it, and finally overcame my hesitation towards it. I now want to get into the “proper” backend of compilation.&lt;/p&gt;

&lt;p&gt;The first step is always the hardest one. But we must take it anyway. Mistakes will happen and failing is certain. But rarely will we regret it, if we learn how to learn from our experiences. I’ve taken risks that didn’t yield what I hoped for. It was a little disappointing, but they’ve 100% made me a better person—well informed and sensitive to issues that matter. &lt;br&gt;
This project coincided with such a phase in life. Amidst confusion and uncertainty, it gave me something I could keep coming back to consistently; something I could see tangible progress in; something that gave me the satisfaction of “creating.” We see programming languages all the time, but know so little of all the thought and effort going into them. Every design choice is intended to make programming easier and more intuitive; more accessible too! If I had to sum up in a sentence what I learnt from this—&lt;em&gt;“God is in the details.”&lt;/em&gt; I will always be grateful for having done this. &lt;/p&gt;

&lt;p&gt;Dear reader, if you’ve followed this project thus far, thank you for your time! Until next time, for the next adventure! &lt;/p&gt;

</description>
      <category>compilers</category>
      <category>interpreter</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
    <item>
      <title>Classes and Inheritance</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Wed, 04 Feb 2026 11:02:31 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/classes-and-inheritance-495j</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/classes-and-inheritance-495j</guid>
      <description>&lt;p&gt;We now move on to complete the interpreter by adding support for classes. They will also be able to inherit from other classes (i.e., reuse their properties and methods) while also customising them through modifications and additions.&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.amazonaws.com%2Fuploads%2Farticles%2Fts00ib2tnqm34hx7h6kw.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.amazonaws.com%2Fuploads%2Farticles%2Fts00ib2tnqm34hx7h6kw.png" alt=" " width="800" height="1234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built:&lt;/strong&gt; &lt;a href="https://github.com/laharitenneti/jlox-interpreter/commits/main/?since=2025-10-21&amp;amp;until=2025-10-31" rel="noopener noreferrer"&gt;Commits bac3f3c, bfa82b9, 03b3af3, 6c70087&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I understood:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;1) &lt;u&gt;Class Declarations:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The parser creates a &lt;code&gt;Stmt.Class&lt;/code&gt; node for each class declaration, containing the name and list of methods.&lt;/li&gt;
&lt;li&gt;The resolver declares the class name in the current scope to allow the class to refer to itself (inside its own methods), for recursion.&lt;/li&gt;
&lt;li&gt;When the interpreter visits &lt;code&gt;Stmt.Class&lt;/code&gt;, it converts the AST node into a &lt;code&gt;LoxClass&lt;/code&gt; object, which stores a map of methods (which are in turn converted into &lt;code&gt;LoxFunction&lt;/code&gt; objects). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) &lt;u&gt;Class Instances/Objects:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lox doesn’t use any &lt;code&gt;new&lt;/code&gt; keyword.&lt;/li&gt;
&lt;li&gt;Instead, &lt;code&gt;LoxClass&lt;/code&gt; implements the &lt;code&gt;LoxCallable&lt;/code&gt; interface such that a class is instantiated whenever called.&lt;/li&gt;
&lt;li&gt;When the class is “called,” a new &lt;code&gt;LoxInstance&lt;/code&gt; object is instantiated and returned.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;This new object stores a reference to its class (&lt;code&gt;LoxClass&lt;/code&gt;) so it can access methods later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) &lt;u&gt;Properties:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fields aren’t declared in the class, but are created on assignment through instances.&lt;/li&gt;
&lt;li&gt;When the interpreter evaluates that an object is a &lt;code&gt;LoxInstance&lt;/code&gt;, it first calls &lt;code&gt;get()&lt;/code&gt;, which checks the instance’s field map.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the field exists, the value is returned. Else, it looks for a method. If neither are found, it throws a runtime error.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Also, the parser can’t easily distinguish between a set and get expression until it encounters a &lt;code&gt;=&lt;/code&gt; sign. Thus, it parses the left-hand side as a regular expression (a &lt;code&gt;get&lt;/code&gt;), and on encountering the &lt;code&gt;=&lt;/code&gt; it converts that node into a &lt;code&gt;set&lt;/code&gt; node.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If &lt;code&gt;set()&lt;/code&gt; is invoked, the &lt;code&gt;fields&lt;/code&gt; map is updated to overwrite any existing key.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4) &lt;u&gt;Methods:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lox methods don’t have the &lt;code&gt;fun&lt;/code&gt; keyword preceding them and are accessed through instances.&lt;/li&gt;
&lt;li&gt;If we extract a method (assign it to a variable) and call it later, &lt;code&gt;this&lt;/code&gt; must still refer to the instance the method was accessed/extracted from, and not where it was called.&lt;/li&gt;
&lt;li&gt;To achieve this, when a method is accessed, &lt;code&gt;LoxClass&lt;/code&gt; instead of returning the raw &lt;code&gt;LoxFunction&lt;/code&gt;, calls &lt;code&gt;bind(instance)&lt;/code&gt; on it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bind()&lt;/code&gt; creates a new environment nested inside the method’s original closure, defines &lt;code&gt;this&lt;/code&gt; in that new environment, and uses it to return a new &lt;code&gt;LoxFunction&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;this&lt;/code&gt; is used outside a class, the resolver (which tracks if it’s currently inside a class) reports an error. Else, it resolves &lt;code&gt;this&lt;/code&gt; as a local variable.&lt;/li&gt;
&lt;li&gt;The interpreter looks up &lt;code&gt;this&lt;/code&gt; in the environment like any other variable. Because of the binding step, the correct instance is found.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2F1nwn4scb53kuewihcgut.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.amazonaws.com%2Fuploads%2Farticles%2F1nwn4scb53kuewihcgut.png" alt=" " width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5) &lt;u&gt;Constructors:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We implement support for user-defined constructors called &lt;code&gt;init&lt;/code&gt;, which if found (by &lt;code&gt;LoxClass.call&lt;/code&gt;), is called immediately to set up the new instance. Also, arity is checked here to ensure the class’ argument count matches the initialiser’s.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;init&lt;/code&gt; always returns &lt;code&gt;this&lt;/code&gt; (even if the user tries to return any value from inside the function). The resolver ensures this by tracking through an &lt;code&gt;isInitialiser&lt;/code&gt; flag in &lt;code&gt;LoxFunction&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;6) &lt;u&gt;Inheritance:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows a subclass to inherit methods from a superclass through a reference to the latter (stored by the subclass’ &lt;code&gt;LoxClass&lt;/code&gt;) and a method lookup walking up the chain.&lt;/li&gt;
&lt;li&gt;The parser creates a &lt;code&gt;Stmt.Class&lt;/code&gt; node that now includes a &lt;code&gt;superclass&lt;/code&gt; variable (of type &lt;code&gt;Expr.Variable&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The resolver checks if a class tries to inherit from itself and reports an error if yes.&lt;/li&gt;
&lt;li&gt;If a superclass exists, the resolver creates a new scope and defines &lt;code&gt;super&lt;/code&gt; in it, ensuring &lt;code&gt;super&lt;/code&gt; is available to all methods in the subclass.&lt;/li&gt;
&lt;li&gt;While creating a subclass, the interpreter creates a new environment, defines &lt;code&gt;super&lt;/code&gt; to point to the superclass, and then creates the methods.&lt;/li&gt;
&lt;li&gt;The parser recursively looks up a method by first checking the instance’s fields, then the class’ methods, and then the superclass, which if null, causes the lookup to fail.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fucafxqk8gaix3iou4h7h.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.amazonaws.com%2Fuploads%2Farticles%2Fucafxqk8gaix3iou4h7h.png" alt=" " width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;7) &lt;u&gt;&lt;code&gt;Super&lt;/code&gt;:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This keyword allows subclasses to override (their own version of) a method and access the original implementation in a parent class.&lt;/li&gt;
&lt;li&gt;It ensures that even if a chain of subclasses overrides a method, &lt;code&gt;super&lt;/code&gt; will always point to the correct parent class definition.&lt;/li&gt;
&lt;li&gt;It is followed by a dot and an identifier (&lt;code&gt;super.method&lt;/code&gt;) and is parsed as an &lt;code&gt;Expr.Super&lt;/code&gt; node.&lt;/li&gt;
&lt;li&gt;It looks up a method on the superclass but binds it to the current instance (&lt;code&gt;this&lt;/code&gt;), with both belonging to different environments.&lt;/li&gt;
&lt;li&gt;The resolver treats &lt;code&gt;super&lt;/code&gt; as a local variable and calculates its distance (number of hops) from the environment in which &lt;code&gt;super&lt;/code&gt; is defined.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fta2fd8ho8sa1zlbs3vj2.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.amazonaws.com%2Fuploads%2Farticles%2Fta2fd8ho8sa1zlbs3vj2.png" alt=" " width="800" height="826"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; Some extensions to Lox. Support for lists and for-in loops.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;br&gt;
Blogging this project has been really good for me. It probably is because despite doing other projects at the moment, trying to explain it to myself by writing encourages me to remember what I did (months ago!) and why. In a way, the insights I gained earlier continue to influence how I approach my current work. I absolutely love documenting things I do, even if it's for the most mundane of activities. In Indian philosophy, work is among the highest acts of devotion. Whatever we do, if we do it with intent, it leads us to competence and mastery. Because it's the process of learning and doing something that helps us become our best versions. Tagore put it well — "Where tireless striving stretches its arms towards perfection..."&lt;br&gt;
In pursuit of that perfection, I remind myself of all that I've learnt.&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>interpreter</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
    <item>
      <title>Functions</title>
      <dc:creator>Lahari Tenneti</dc:creator>
      <pubDate>Mon, 05 Jan 2026 07:04:47 +0000</pubDate>
      <link>https://dev.to/lahari_tenneti_4a8a082e9c/functions-2pmi</link>
      <guid>https://dev.to/lahari_tenneti_4a8a082e9c/functions-2pmi</guid>
      <description>&lt;p&gt;Well, It's been a minute. I'd finished the interpreter a long time ago, but had put off blogging it because a thousand different things came up these past few months. Anyhow, it now takes on the ability to handle functions - user defined, native, and nested - and even deal with shadowing, wherein a variable in a local scope "eclipses" the same (existing) variable in an outer scope. We'll also enter proper compiler territory by creating a "resolver" phase.&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.amazonaws.com%2Fuploads%2Farticles%2Fricyxk0j91x74ci4o0li.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.amazonaws.com%2Fuploads%2Farticles%2Fricyxk0j91x74ci4o0li.png" alt=" " width="783" height="1024"&gt;&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.amazonaws.com%2Fuploads%2Farticles%2Fv2i9zx9hwqvvjw2oosh4.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.amazonaws.com%2Fuploads%2Farticles%2Fv2i9zx9hwqvvjw2oosh4.png" alt=" " width="800" height="749"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I built:&lt;/strong&gt; &lt;a href="https://github.com/laharitenneti/jlox-interpreter/commits/main/lahari-jlox/src/jloxinterpreter?since=2025-10-07&amp;amp;until=2025-10-15" rel="noopener noreferrer"&gt;Commits 3da669d, 930014e, 5adb148, and 9d534f3&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I understood:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) &lt;u&gt;Updating the Grammar:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The function call takes the highest precedence here, above the unary rule.&lt;/li&gt;
&lt;li&gt;We also create an object (class) called &lt;code&gt;Callable&lt;/code&gt; that helps resolve whether or not something can be used as a function.&lt;/li&gt;
&lt;li&gt;Ex: A string isn't callable, while classes and functions (user-defined and native) are. Python does something really cool by allowing all its datatypes to be callable, because they're really objects that're being called.&lt;/li&gt;
&lt;li&gt;If the "callee" (entity calling the function) doesn't belong to &lt;code&gt;LoxCallable&lt;/code&gt;, Lox raises an error.&lt;/li&gt;
&lt;li&gt;The parser also allows for currying or chained function calls through &lt;code&gt;(( "(" arguments? ")" )*&lt;/code&gt;, allowing for &lt;code&gt;function(arg)(arg)(arg)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2F9gyailgxdcxmkkz9m1jm.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.amazonaws.com%2Fuploads%2Farticles%2F9gyailgxdcxmkkz9m1jm.png" alt=" " width="800" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2) &lt;u&gt;Arguments:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We also check for arity, wherein the number of parameters in the function definition must equal the number of arguments passed in the call.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fqgjai2caydn65d48jfiz.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.amazonaws.com%2Fuploads%2Farticles%2Fqgjai2caydn65d48jfiz.png" alt=" " width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3) &lt;u&gt;Native Functions:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We create &lt;code&gt;clock()&lt;/code&gt; in Java, which Lox can access.&lt;/li&gt;
&lt;li&gt;This is an anonymous class implementing &lt;code&gt;LoxCallable&lt;/code&gt;, instantiated and bound to a variable "clock" in the interpreter's globals environment.&lt;/li&gt;
&lt;li&gt;It returns the system time in seconds, for us to perform benchmarks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4) &lt;u&gt;Function Declaration:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every time the interpreter encounters &lt;code&gt;fun&lt;/code&gt;, it creates a callable object out of it (through &lt;code&gt;LoxCallable&lt;/code&gt;) and binds it to the function's name in a new environment created.&lt;/li&gt;
&lt;li&gt;In this local environment, the function's arguments are bound to the parameters mentioned in the declaration.&lt;/li&gt;
&lt;li&gt;Every time the interpreter encounters a function call, a new environment is created. This is to allow for recursion, which wouldn't be possible if an environment existed only for declarations.&lt;/li&gt;
&lt;li&gt;Once the function's body is completed, this local environment is destroyed and the execution environment returns to the state before the call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5) &lt;u&gt;Return:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The interpreter looks for a return statement followed by an optional expression.&lt;/li&gt;
&lt;li&gt;If no return is mentioned, &lt;code&gt;nil&lt;/code&gt; is implicitly returned.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;return&lt;/code&gt; is encountered, the interpreter must immediately jump out of all its (current) environments and revert to the original function call site.&lt;/li&gt;
&lt;li&gt;Instead of a complex stack to do so, we use a simple exception class called &lt;code&gt;Return&lt;/code&gt;, which evaluates the return value (if given) and wraps it, finally returning it as the function's result.&lt;/li&gt;
&lt;li&gt;This is kept inside a try-catch block, that looks for the return "exception", which if not found, causes the full function to be parsed and &lt;code&gt;nil&lt;/code&gt; returned implicitly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;6) &lt;u&gt;Scoping:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;So far, Lox was able to support dynamic scoping wherein a called function's scope was always set to the global environment, and not the environment it was defined in. &lt;/li&gt;
&lt;li&gt;This hindered the called function's access to variables in the environment it was defined in, once the environment ended (when the function finished executing).&lt;/li&gt;
&lt;li&gt;Basically, a live reference to a mutable scope was being captured, when it should have captured the lexical environment it was defined in, instead of deferring name resolution to the call site.&lt;/li&gt;
&lt;li&gt;To explain this with an example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun getName(name) {
     var person = name;
     fun greet() {
          print “Hello, “ + person + “!”;
     }
     return greet;
}
var momo = getName("Momo”);
var chutney = getName("Chutney”);

momo(); //Should give “Hello, Momo!” ; Instead, it gives an error: “Undefined variable!”
chutney(); //Should give “Hello, Chutney!”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;This is because once &lt;code&gt;getName("Momo") finishes, the variable&lt;/code&gt;person` is destroyed, as the function's scope ended.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When &lt;code&gt;momo()&lt;/code&gt; is called, it tries to find &lt;code&gt;person&lt;/code&gt; in its parent scope, which has now become the global scope (which doesn't contain the variable &lt;code&gt;person&lt;/code&gt;) → So error!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We deal with this issue of dynamic scoping, by using lexical scoping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Here, when a function is first declared, we close over and capture the environment surrounding the function (inner function holds a live reference to the outer one, which the garbage collector can't destroy despite the completion of the function's execution).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the function is called, this captured environment becomes the call's parent instead of &lt;code&gt;globals&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;7) &lt;u&gt;Resolver:&lt;/u&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementing closures through lexical scoping gave rise to another issue.&lt;/li&gt;
&lt;li&gt;Because a closure holds a live reference to a "mutable" map representing the current scope, its captured view of the current scope can change based on code that runs later in the same block.&lt;/li&gt;
&lt;li&gt;This shouldn't be the case. Because Lox has lexical/static scope, a variable's usage must always "resolve" to the same declaration throughout the program's execution.&lt;/li&gt;
&lt;li&gt;Even a redeclaration/redefinition of the variable later in the code shouldn't influence behaviour written with the original/declared value in mind.&lt;/li&gt;
&lt;li&gt;Let's understand this through an example:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
var a = "one";&lt;br&gt;
{&lt;br&gt;
    fun showA() {&lt;br&gt;
        print a;&lt;br&gt;
    }&lt;br&gt;
    showA();&lt;br&gt;
    var a = "two";&lt;br&gt;
    showA();&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In this code, the variable &lt;code&gt;a&lt;/code&gt; belongs to the global scope, with its value being "one."&lt;/li&gt;
&lt;li&gt;The function &lt;code&gt;showA&lt;/code&gt; creates a closure that captures the block/environment it is declared in.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;showA()&lt;/code&gt; is called, &lt;code&gt;a&lt;/code&gt; is first searched for in the block, and when not found, is searched for in the environment above (i.e., global). Hence, the output for &lt;code&gt;print a&lt;/code&gt; is "one."&lt;/li&gt;
&lt;li&gt;But, &lt;code&gt;a&lt;/code&gt; is redeclared to have the value "two" in the same block (i.e., in the same environment captured by &lt;code&gt;showA()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;p&gt;So, when &lt;code&gt;showA()&lt;/code&gt; is called after the redeclaration, because the scope was mutable, the output is "two" when it should've been "one" again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The resolver, added after the parser and before the interpreter, thus uses a concept called "Persistent Data Structures" wherein the original data structure can't be tampered with directly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Any change to an existing structure creates a newer structure that contains the original information along with the modification.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To give a parallel, this is how blockchain works. If you're familiar with it, you must know that every time a block is modified in a ledger, a new block is created referencing the older one's hash, while also containing the change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the resolver enters a block and sees a variable being "used" (after its declaration), it searches upward from the function's scope.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
Scope 0 (the function's scope):&lt;/code&gt;a&lt;code&gt;is not found&lt;br&gt;
Scope 1 (block scope):&lt;/code&gt;a&lt;code&gt;is not found (as&lt;/code&gt;var a = "two"&lt;code&gt;hasn't been defined yet in the resolver's static pass)&lt;br&gt;
Scope 2 (global scope):&lt;/code&gt;a&lt;code&gt;is found&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because the variable is found 2 hops away from the innermost scope (of its use), the resolver annotates that "use" or node with the number 2. &lt;/li&gt;
&lt;li&gt;When the interpreter encounters &lt;code&gt;print a&lt;/code&gt;, instead of searching for the variable, it just uses the given number of hops to jump to the declaration.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What's next:&lt;/strong&gt; We're going to keep it Classy! 💅&lt;br&gt;
(Hehe, classes and inheritance, in case you didn't get it)&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Musings:&lt;/strong&gt;&lt;br&gt;
I’ve had the opportunity to witness some of the most wonderful and bewildering of things these past few months. Feels like I saw the full spectrum of life, much like Shakespeare’s seven ages of man. In a way, it really humbled me. For the longest time, I was afraid of letting go… of my beliefs, interests, and even some dreams, because I thought I’d lose my very identity. Now, I’ve learnt that much like functions, it’s okay to have a template of non-negotiables, upon which we can let life keep adding her own “implementations.” (I absolutely HAD to use that analogy, pleeease excuse me). Change and unpredictability don’t seem so daunting anymore, and maybe (like 0.05%) are good too. I suppose c’est la vie.&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>interpreter</category>
      <category>computerscience</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
