<?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: Natan Guzinski</title>
    <description>The latest articles on DEV Community by Natan Guzinski (@natan_guzinski_120c49e589).</description>
    <link>https://dev.to/natan_guzinski_120c49e589</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%2F4006811%2F16b076b2-a174-47af-9a35-1395ce2eaf4f.png</url>
      <title>DEV Community: Natan Guzinski</title>
      <link>https://dev.to/natan_guzinski_120c49e589</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/natan_guzinski_120c49e589"/>
    <language>en</language>
    <item>
      <title>I Built a Statically Typed Bytecode VM Language in C — Here's What I Learned</title>
      <dc:creator>Natan Guzinski</dc:creator>
      <pubDate>Sun, 28 Jun 2026 17:40:16 +0000</pubDate>
      <link>https://dev.to/natan_guzinski_120c49e589/i-built-a-statically-typed-bytecode-vm-language-in-c-heres-what-i-learned-1e7e</link>
      <guid>https://dev.to/natan_guzinski_120c49e589/i-built-a-statically-typed-bytecode-vm-language-in-c-heres-what-i-learned-1e7e</guid>
      <description>&lt;p&gt;A couple months ago I finished reading &lt;em&gt;Crafting Interpreters&lt;/em&gt; and got the itch to go further. Instead of just following along with clox, I decided to design my own language from scratch. The result is &lt;strong&gt;Oli-Nat&lt;/strong&gt; — a statically typed, bytecode-compiled language with its own GC, type checker, class system, and standard library, all written in C.&lt;/p&gt;

&lt;p&gt;This post isn't a tutorial. It's about three specific design problems I ran into and how I solved them, because I think they're interesting and I haven't seen them written up clearly anywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pipeline
&lt;/h2&gt;

&lt;p&gt;Before diving in, here's the full pipeline so the rest makes sense:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source → scanner → Pratt parser → AST → type checker → two-pass bytecode compiler → stack-based VM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick taste of what the language looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"hero"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="nf"&gt;takeDamage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;health&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;getHealth&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;health&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;takeDamage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getHealth&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// 70&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables and functions are declared with &lt;code&gt;make&lt;/code&gt;. Stdlib is imported with &lt;code&gt;#pullf&lt;/code&gt;. Nothing groundbreaking syntactically, but the internals were a different story.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 1: The Circular Dependency Between the Hashmap and Object System
&lt;/h2&gt;

&lt;p&gt;My object system lives in &lt;code&gt;object.h&lt;/code&gt;. My hashmap lives in &lt;code&gt;hashmap.h&lt;/code&gt;. The problem: &lt;code&gt;ObjClass&lt;/code&gt; needs a &lt;code&gt;Hashmap&lt;/code&gt; to store its methods, and &lt;code&gt;Hashmap&lt;/code&gt; needs &lt;code&gt;ObjString*&lt;/code&gt; as a key type. Classic circular dependency.&lt;/p&gt;

&lt;p&gt;The naive solution — just include one from the other — doesn't work because you end up with a cycle the preprocessor can't resolve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My solution: a types-only header.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I created &lt;code&gt;hashmap_types.h&lt;/code&gt; that contains just the struct definitions for &lt;code&gt;Bucket&lt;/code&gt; and &lt;code&gt;Hashmap&lt;/code&gt;, with a forward declaration of &lt;code&gt;ObjString&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="c1"&gt;// hashmap_types.h&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ObjString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// forward declaration only&lt;/span&gt;

&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ObjString&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;buckets&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;count&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;capacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;Hashmap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;object.h&lt;/code&gt; includes &lt;code&gt;hashmap_types.h&lt;/code&gt; — gets the &lt;code&gt;Hashmap&lt;/code&gt; struct without pulling in hashmap implementation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hashmap.h&lt;/code&gt; includes both &lt;code&gt;hashmap_types.h&lt;/code&gt; and &lt;code&gt;object.h&lt;/code&gt; — gets everything it needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No cycles. The key insight is separating &lt;em&gt;struct layout&lt;/em&gt; from &lt;em&gt;function declarations&lt;/em&gt;. You only need the layout to embed a &lt;code&gt;Hashmap&lt;/code&gt; inside &lt;code&gt;ObjClass&lt;/code&gt;; you only need the functions when you're actually calling them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 2: Two-Pass Compilation for Forward References
&lt;/h2&gt;

&lt;p&gt;In most simple interpreters, you can't call a function before you declare it. I wanted Oli-Nat to support forward references — call a function defined later in the file, or have two classes reference each other — without requiring explicit forward declarations from the user.&lt;/p&gt;

&lt;p&gt;The solution is a two-pass compiler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First pass&lt;/strong&gt; (&lt;code&gt;declareFunction&lt;/code&gt;): scan the entire source for function, class, field, and method declarations. Don't parse bodies. Just populate the type checker's symbol table with signatures:&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="c1"&gt;// First pass sees this:&lt;/span&gt;
&lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;add&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;a&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;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// And records: "add" → returns int, takes (int, int)&lt;/span&gt;
&lt;span class="c1"&gt;// Body is skipped entirely&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Second pass&lt;/strong&gt;: full parse, type check, and bytecode emission. By this point the type checker already knows about every function and class in the file, so forward calls type-check correctly and the compiler can emit the right call instructions.&lt;/p&gt;

&lt;p&gt;The tricky part is class declarations. The first pass needs to record not just the class name but all its field types and method signatures, so that the type checker can validate &lt;code&gt;instance.field&lt;/code&gt; and &lt;code&gt;instance.method(...)&lt;/code&gt; expressions in the second pass. That means the first pass has to parse class bodies partially — just enough to extract the metadata, without emitting any bytecode.&lt;/p&gt;

&lt;p&gt;The payoff is that the user never thinks about declaration order. Functions and classes can be used anywhere in the file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 3: GC Safepointing Hazards
&lt;/h2&gt;

&lt;p&gt;This one bit me multiple times and is easy to miss if you're not thinking about it.&lt;/p&gt;

&lt;p&gt;The GC runs during allocation. If you allocate something and it triggers a collection, any object you were holding in a local C variable but hadn't yet anchored to the VM's roots (stack, globals, etc.) will be swept as garbage — even though you're actively using it.&lt;/p&gt;

&lt;p&gt;The classic bad pattern:&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="n"&gt;ObjString&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copyString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// allocation #1&lt;/span&gt;
&lt;span class="n"&gt;Value&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;makeInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;someClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// allocation #2 — might trigger GC&lt;/span&gt;
                                               &lt;span class="c1"&gt;// key is now potentially freed!&lt;/span&gt;
&lt;span class="n"&gt;mapSet&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;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is to push temporaries onto the VM's value stack before any allocation that might trigger GC, then pop after:&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="n"&gt;ObjString&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copyString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OBJECT_VAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;    &lt;span class="c1"&gt;// anchor it as a GC root&lt;/span&gt;

&lt;span class="n"&gt;Value&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;makeInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;someClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// GC safe now — key is on the stack&lt;/span&gt;

&lt;span class="n"&gt;mapSet&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;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&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;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                       &lt;span class="c1"&gt;// done with it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mental model I use now: &lt;em&gt;any call that allocates is a potential safepoint&lt;/em&gt;. Before crossing a safepoint, anchor everything you care about.&lt;/p&gt;

&lt;p&gt;This shows up in subtle places — string interning during compilation, building the methods hashmap while the class object is being constructed, initializing field default values. Every time I added a new feature that involved multiple allocations in sequence, I had to audit the ordering.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The language is working and I'm happy with where it is. The next big things on the roadmap are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Constructors with parameters&lt;/strong&gt; — right now &lt;code&gt;Player()&lt;/code&gt; creates an instance with default field values; I want &lt;code&gt;Player("Alice", 50)&lt;/code&gt; to work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inheritance&lt;/strong&gt; — &lt;code&gt;ObjClass&lt;/code&gt; already has a &lt;code&gt;superclass&lt;/code&gt; pointer reserved for this&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 2D game library&lt;/strong&gt; — the whole reason I wanted a class system in the first place&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full source is on GitHub: &lt;a href="https://github.com/NateTheGrappler/OliNat-Programming-Language" rel="noopener noreferrer"&gt;https://github.com/NateTheGrappler/OliNat-Programming-Language&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about any of the implementation details in the comments.&lt;/p&gt;

</description>
      <category>c</category>
      <category>computerscience</category>
      <category>programming</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
