Skip to main content

Compiler

33 posts

· 8 min read compiler stdlib

Closing the Gaps: std Imports Compile Native, HashMap Indexing, and Idiomatic Examples

A run of fixes that make the v0.5 surface something a real program can lean on — std.fs and std.encoding.json reaching native codegen, m[k] indexing on HashMap, by-value generic methods, and a language guide so the examples actually model idiomatic Hew.

· 11 min read compiler design

Two ways to be generic: monomorphisation, and the day abstract types lowered without one

How Hew lowers generics, told as the evolution it actually was — from stamping out one mangled LLVM function per instantiation, through unifying dispatch under a single call authority, to representing an abstract type parameter directly in MIR so a generic body can lower once even when its type is never made concrete.

· 7 min read compiler design

Abstract type parameters with value witnesses, and a receive ABI that can borrow

Generics that lower without a concrete type yet, an actor receive path that takes a non-owning borrow of the message, and the smaller control-flow pieces — const globals, bare loop, break/continue — I'd been putting off.

· 11 min read compiler design

No GC, no double-free: ownership, copy-on-write strings, and fail-loud traps

How Hew's memory model actually works underneath — single ownership with compiler-inserted drops, strings as copy-on-write refcounted values over a header-aware allocator, and arithmetic that traps with distinct exit codes instead of wrapping silently.

· 8 min read compiler design

String Becomes Copy-on-Write and the Last Legacy Dispatch Path Is Retired

Two days on ownership — retiring the old HashMap/HashSet dual-dispatch, turning `string` into a copy-on-write refcounted value, and adding `&T` borrow markers that fail closed when you try to clone the wrong way.

· 9 min read compiler design

Collections get a real ABI, closures get capture inference, and dispatch goes through one authority

Two days on the substrate under generic data structures — what a layout descriptor actually carries, why the closure checker now infers move vs borrow, and the moment I noticed three different pieces of the compiler each had their own opinion about how a method call lowers.

· 8 min read compiler design

Hardening the Seams Instead of Adding Surface

Two days of making the compiler refuse to lie — fail-closed gates that reject unsupported shapes at compile time, a memory-safety sweep through the runtime, and a `hew check` that finally runs the real pipeline.

· 11 min read compiler design

Talking between nodes: the CBOR wire envelope and a SWIM cluster that gossips

The distributed substrate for v0.5 — replacing a hand-rolled binary wire format with a CBOR envelope described by a CDDL schema, deleting 2,498 lines of wire.rs, and giving the cluster a phi-accrual failure detector that adapts instead of a fixed timeout. Plus cross-node sends that stay type-checked.

· 8 min read compiler runtime

Generators get a state machine, the cluster learns to gossip, and the wire format flips to CBOR

Two dense days grouped together — generator lowering finally has a state record and cross-yield liveness, the SWIM detector goes phi-accrual, and I delete the 2498-line hand-built wire format in favour of a CBOR envelope with a CDDL schema.

· 11 min read compiler design

State as a type: how machine declarations become tagged-union dispatch

The machine construct in detail — what the front end checks before a single instruction is emitted, how a state declaration becomes a tagged-union LLVM layout, and why I'd rather reject an illegal transition at compile time than catch it at runtime. Includes the parts that don't fully work yet.

· 7 min read compiler

Generic Enums End-to-End and the Day for and while Finally Lower

Generic enum values now make it all the way to LLVM, so I get to delete the not-yet-supported guard that's been standing in for them. Match over payload-bearing variants works, and for and while loops lower to real control flow for the first time.

· 7 min read compiler design

State Machines Reach Codegen and Closures Become First-Class Values

Machine declarations used to type-check and then stop — no runtime behaviour. Now they lower all the way through to native code. Plus pipe-form closures, a canonical pointer spelling, and a TCP-as-Stream bridge.

· 7 min read compiler design

Supervisors That Wire Themselves and Crash Hooks With Real Trap Codes

A supervisor declaration now lowers to a spawn-and-wire bootstrap that returns a typed pid, and a crash hook can finally see the trap code that killed a child. The bottom half is real; the top half is where I am today. Underneath, int and uint are gone and module-qualified calls like math.sqrt resolve.

· 11 min read compiler design

Sends are method calls, crashes carry codes: the actor and supervision model

A walk down the actor stack in v0.5. Why I deleted the `<-` send operator, how a method call lowers to spawn/send/ask through an execution context, and how supervision fires `#[on(crash)]` with the real trap code.

· 10 min read compiler design

WASM parity for free, almost: four targets from one MIR and the seams that weren't free

Native and wasm32 share the same MIR and the same textual LLVM IR — they only diverge at the TargetMachine. That makes most of WASM support genuinely free. This is about the parts that weren't — traps, blocking recv, timers, and a use-after-free that only exists on WASM.

· 8 min read compiler

From Parse Tree to Running Actor

Actors stopped being syntax today. I wired actor method calls down to spawn/send/ask, made init and the on(start)/on(stop) hooks fire at the right moments, and threaded an execution context through the whole thing so the reply channel has somewhere to live.

· 8 min read compiler design

Retiring the C++ Backend and Teaching the Compiler to Monomorphise

The day the pipeline became Rust top to bottom — over fifteen hundred C++/MLIR files deleted, and generics finally grew a spine through the IR ladder.

· 9 min read compiler design

Decide in MIR, carry in nothing: why we deleted the MLIR backend before shipping a binary

The backend cutover for v0.5 — the borrowed-String double-free that took six revisions to kill, why each revision grew the C++ walker registry, and how I convinced myself it was safe to delete a 1,568-file MLIR subtree and lower straight from MIR to LLVM IR.

· 8 min read compiler design

Making integers honest and making failure loud: widths, records, and trap-on-overflow

Today the value layer stops being vague. I gave integers real widths and platform-sized isize/usize, wired records all the way through with auto-derived markers, and made arithmetic that overflows trap instead of silently lying.

· 7 min read design compiler

Deleting the Send Operator to Make the Actor Model Honest

First day of the v0.5 rewrite. I tore out the `<-` send operator and the spawn-lambda form, and spent the rest of the day deciding what the actor and machine surface should look like before a single line of it lowers to a binary.

· 6 min read compiler design

The Double-Free and the Sized Integers

Two breaking changes in v0.4.0 that were really just cleaning up things I should have gotten right earlier — explicit teardown for resource types, and int uniformity in the stdlib.

· 5 min read tooling compiler

The JIT REPL Can Survive a Crash Now

The in-process JIT path was fast but had no floor — any bug in generated code killed the session. Here's what we did about it.

· 6 min read tooling compiler

Comments Are Not Optional

The formatter was silently dropping inline comments — not crashing, not warning, just eating them. Seven variants of the same missing flush call, and how wiring LSP formatting shook them all out.

· 7 min read compiler stdlib

Hardening the Standard Library's Error Paths

After making the compiler fail-closed, the same scrutiny hit the stdlib — thirty commits fixing sentinel values that lied, parsers with no depth limits, and a mutex that silently swallowed messages.

· 6 min read compiler

Making the Compiler Fail Closed

The v0.3.0 codegen hardening in detail — what the AST fallback was hiding, how it hid it, and why removing it uncovered a range loop signedness bug I had no idea existed.

· 6 min read case-study compiler

What MicroGPT Found — 13 Bugs and 340 Lines of Workarounds

The point of porting a real program isn't to have a GPT. It's to find out where your language breaks. MicroGPT found 13 compiler issues and revealed that 31% of the code was working around language gaps.

· 5 min read compiler

Type System Improvements After the Initial Release

The type checker kept rejecting programs that worked fine at runtime. Here's what it took to fix that — literal coercion, casts, bytes, indirect enums, and array-to-Vec.

· 6 min read compiler

Fixing Generics and Adding Verification

Fixing generic monomorphization collisions, wiring codegen to the type checker, adding MLIR verification, and building IR visualization tools.

· 5 min read compiler runtime

Closing the Gaps

Walking through the Hew pipeline to fix broken tail calls, unregistered defer, SQL injection in the stdlib, duration literals stuck at the lexer, and 120 new tests.

· 6 min read compiler tooling

Replacing FlatBuffers with MessagePack

Ripping out FlatBuffers, splitting the standard library namespace, adding a package manager, and building an export macro system.

· 5 min read language-design compiler

Testing the Language on Myself

Writing fifteen programs from scratch to find UX problems, then fixing error messages, adding else-if chains, and killing false positives.

· 6 min read compiler

Throwing Away the First Compiler

Why Hew ended up with two compilers — the Rust/Inkwell attempt, the pivot to C++/MLIR, and rebuilding the pipeline in 12 hours.

· 6 min read compiler

From cargo init to a Running Compiler

Building Hew's lexer, parser, type checker, IR, and C codegen in under two hours. Fibonacci printed 55.