compilers & resumania

I've gotten so bad about writing these this year. I wanted to talk about two things I'm quite excited about. First up is a huge compiler project I've been doing for the past month to take Gleam and produce wasm.

Gleam Compiler

WebAssembly (Wasm) is a binary instruction format designed to run in browsers and standalone runtimes. It is stack-based, strongly typed, and sandboxed. The same .wasm binary can run in Firefox, Chrome, Node.js, Wasmtime, or any other compliant runtime, a level of portability that native machine code does not offer. But like machine code, Wasm has value types, functions, linear memory, tables, imports, and exports. Generating valid, efficient Wasm from a high-level language requires the same structured compiler work as generating machine code.

Regulus (codename Reggie), an experimental compiler that takes Gleam source code and produces WebAssembly, written in Rust like the Gleam compiler.

Gleam is a functional language with static types, immutable values, and a friendly syntax. It has pattern matching, custom types, generics, and a module system that normally compiles to Erlang bytecode or JavaScript. As a learning exercise, I opted to create a separate project to compile Gleam to WebAssembly instead.

Reggie produces .wasm from Gleam source through seven steps:

Produces an in-memory tree-sitter concrete syntax tree

Builds a compiler-owned AST

resolves module

type-checks modules

Builds a core Intermediate Representation

Generates WAT (WebAssembly Text format)

Produces a .wasm binary

I want to talk about the most challenging and interesting parts of the whole project.

Name Resolution

In this portion of the pipeline, the resolver walks the AST (step 2) and turns textual name references into known declarations.

It handles lexical scope, imports, module-qualified names, constructors, field names, and visibility rules. The output is a ResolvedModule which is the AST annotated with symbol table data.

Lowering

After the type checker walks the resolved AST and checks that values are used with compatible types, we "lower" (translate) to typed source syntax into a smaller, source-independent representation (core IR). Source-level constructs become explicit runtime operations. For example:

let bindings become local variable allocations and writes

case expressions become branch nodes with explicit pattern tests

let assert becomes a branch with an explicit failure path

function parameters become numbered locals

This IR knows nothing about Gleam syntax. It knows about locals, calls, blocks, branches, and managed-value operations.

Gleam example with use:

pub type User { User(name: String, age: Int) }

fn with_user(user: User, callback: fn(User) -> Int) -> Int {
callback(user)
}

pub fn use_updated_age(age: Int) -> Int {
let user = User(name: "Ada", age: age)
use current <- with_user(user)
let older = User(..current, age: age + 2)
case older {
User(name: _, age: value) -> value
}
}

Emission of WAT

The backend translates core IR into WebAssembly Text format (WAT). It maps scalar Gleam types to Wasm value types:

| Gleam type | Wasm ABI |
| -------------------------- | ------------- |
| `Int` | `i64` |
| `Float` | `f64` |
| `Bool` | `i32` |
| `Nil` | no result |
| strings, lists, records, … | `i32` pointer |

Managed values like strings, lists, tuples, records, custom types, closures live in WebAssembly linear memory. The backend emits a runtime prelude for modules that use managed values: memory, a bump-allocation heap pointer, and helper functions for allocation, string handling, list operations, equality, and panic.

The above gleam example's wat output (truncated):

Long-Term Goals

The long-term goal here is for a complete Gleam-to-WebAssembly compiler that can compile real Gleam applications like including web backends with Wisp and browser UIs with Lustre to WebAssembly that runs in browsers and cloud runtimes like Cloudflare Workers.

Resume

As I continue my job hunt, I've been updating my "brand" and made a resume site on Astro using the JSON Resume schema. The setup generates three outputs from one data file: a web page, a PDF, and an Open Graph image. It's not a super interesting or complex project but I'm quite happy with how it's turned out. To build it, I adapted Colin Hemphill's nextjs-resume to Astro. He had already solved the layout problems. I used his setup as inspiration and swapped out the framework underneath, while adding a few flourishes to make the project my own.

You can view the finished product here:

Résumé | Owais Jamil

The resume lives in a flat data.json file that follows the JSON Resume spec, translated to an Astro content collection.

The PDF generation happens at build time through a static path. It uses @react-pdf/renderer to convert React components into a PDF document. Here's what the PDF ends up looking like:

The OG image also uses React but via using @takumi-rs/image-response. It pulls my name and title from the JSON to create a social media preview card that I think looks pretty cool.