1. X
  2. Andrew Healey
Log inSign up
Andrew Healey
878 posts
Image
user avatar
Andrew Healey
@healeycodes
software engineer @vercel β€’ I write about performance, compilers, puzzles, &more on my website β€’ @recursecenter alum
πŸ‡¬πŸ‡§
healeycodes.com
Joined January 2019
913
Following
1,362
Followers
RepliesRepliesMediaMedia
  • user avatar
    Andrew Healey
    @healeycodes
    Aug 2
    I added πšπšŽπšπšŽπš› to the typescript compiler but along the way, I realised that what I want is different syntax for πšžπšœπš’πš—πš I just want to be able to write: πšžπšœπš’πš—πš πšŠπš πšŠπš’πš πšœπšŽπš–πšŠ.πšŠπšŒπššπšžπš’πš›πšŽ() but you can't ↧ healeycodes.com/adding-defer-t…
    Screenshot of blog post:

So the goal is to be able to write TypeScript code like this:

async function readFile(path: string) {
  await sema.acquire();
  defer sema.release(); // New!

  // ... use resource
}
The TypeScript Compiler
The TypeScript compiler (tsc) is mostly a static analysis engine. Its complexity lies in type-checking a fundamentally dynamic language, and supporting extremely incremental compilation to meet latency expectations in an IDE.

Lucky for us, we don't need to worry too much about types or other analysis in order to add our defer statement. tsc already has the machinery for "recognize syntax X, replace it with equivalent syntax Y."
  • user avatar
    Andrew Healey
    @healeycodes
    Jun 27
    two sum in lisp is actually really cute
    (defun two-sum (xs target)
  (let ((seen (make-hash-table)))
    (loop for x in xs
          for i from 0
          for need = (- target x)
          do (multiple-value-bind (j ok)
                 (gethash need seen)
               (when ok
                 (return (list j i))))
             (setf (gethash x seen) i))))
  • user avatar
    Andrew Healey
    @healeycodes
    Jun 25
    new post! I wrote a tiny compiler that lowers simple kernel loops into explicit data-parallel code (lanes! masks!) ofc I made up a language to make the input/output code easier to read
    Screenshot of post:

A Tiny Compiler for Data-Parallel Kernels
Jun 2026

A lot of fast code starts as a boring loop.

Modern hardware can perform the same operation on multiple values at once (e.g. SIMD and SIMT), and sometimes we write code directly for those execution models but other times, a compiler starts with regular-looking code and rewrites it so multiple loop iterations can run together. I built a tiny compiler (~180LOC of Python) to understand what that transformation looks like.

My compiler lowers kernels (rewrites them into a simpler, more explicit form where data parallelism is visible). The input is a small hand-written AST, and the output is a lowered IR that I print as Python-like code. Rather than going all the way from source code to instructions, think of this compiler as an intermediate step in a larger compiler.

Let's take a look at an example. Scaling audio is easy to parallelize, but it is still common to write non-explicitly parallel code like this:
  • user avatar
    Andrew Healey
    @healeycodes
    Mar 26
    quickjs is GOATed delightful API surface I was able to hack together a tiny runtime and event loop v quickly with it
    Screenshot of:

Building a Runtime with QuickJS
Mar 2026

A JavaScript engine (e.g. V8, JavaScriptCore) executes JavaScript code. It doesn't know about things like files, HTTP requests, or timers.

On the other hand, a JavaScript runtime (e.g. Node.js, Bun) is a more complete environment where JavaScript runs. It contains a JavaScript engine, extra APIs, an event loop and task queues, and platform-specific features.

That's what I'm hacking on today: a tiny runtime with console.log, process.uptime(), setTimeout and clearTimeout, fs.readFileSync and fs.readFile, as well as an event loop and worker pool for file I/O. Built on top of QuickJS.

Here's an example program it can run:

const startedAt = process.uptime();
  • user avatar
    Andrew Healey
    @healeycodes
    Mar 17
    I built a tiny shell with C so I could learn more about syscalls and pipes great fun I found C to be quite suited for the task (fork, execvp) ... until I had to manage some string lifetime stuff πŸ₯²
    Screenshot of:

To recap a bit, I'll step through what happens when ls $HOME | grep foo is entered.

It's tokenized into ["ls", "$HOME", "|", "grep", "foo"].

Then expanded into ["ls", "/Users/andrew", "|", "grep", "foo"].

The flat token list is separated into structured pipeline commands:

["ls", "/Users/andrew"]
["grep", "foo"]
The shell creates a pipe to connect the output of ls to the input of grep.

The child commands start via fork, and execvp swaps them into the target programs.

By the wonderful design of Unix: if grep reads faster than ls writes, it blocks waiting for more data; if ls writes faster than grep reads, the pipe buffer fills and ls temporarily blocks. This synchronization happens automatically through the pipe, while the shell simply waits for both child processes to finish.

The output of grep isn't connected to a pipe as it's the last command, and any results are displayed to the user.

Log in or sign up for X

See what’s happening and join the conversation

Continue with phone
or
Log in with username or email
TermsΒ·PrivacyΒ·CookiesΒ·AccessibilityΒ·Ads InfoΒ·Β© 2026 X Corp.
Advertisement
Advertisement