Skip to content

ponyc

Pony’s Errors Stop Unwinding the Stack

Here’s how it is: for a few years, off and on, I went looking for a bug.

My aarch64 testing machine, a sturdy little Raspberry Pi, was the site of so very many segfaults. The same tests would fail run after run, and I could boil the crash down to a handful of lines of try and error. It came out of the machinery Pony uses to unwind the stack when error fires. So I’d pull that code up and compare it to the spec. It looked right. I’d run out of leads and put it down. Months later I’d pick it up and start over.

I never found it.

I kept coming back to it. I’d talk it through with Sylvan, and we’d end up in the same place. I’d talk it through with Joe, and we’d end up there too. The bug probably isn’t ours. It’s probably down in the guts of LLVM, somewhere we don’t own. Probably.

So I stopped chasing the bug and looked at where it lived: the stack unwinding. Pony doesn’t have to unwind the stack to raise an error. Take the unwinding away, and the bug has nowhere left to be. Soon, that’s how it’ll work — there’s a pull request open against the compiler that takes stack unwinding out of Pony’s errors, and that bug is part of why I wrote it. But only a part.

Round and Round

What comes around goes around. I’ll tell you why, why, why, why.

That is, near enough, how the Pony compiler’s subtype check works on a type that refers to itself. It follows the type down into its own structure, and for an ordinary self-referential type it soon comes back around to a pair it is already checking further up. That return is the exit. What comes around closes, and the check finishes.

This is a story about two types where the check went down and never came back around. One of them sent the compiler into a loop with no end. The other ran it off the end of its stack and knocked it over. One was reported in 2016, the other in 2021, and both closed in a single pull request.

Making Finite Recursive Type Aliases Compilation Fast

This is the third post in a short series on finite recursive type aliases in Pony. The first post told the story of the eleven years it took to allow them. The second post laid out the algorithm that decides which recursive aliases are legal.

That algorithm is correct. Correct isn’t enough. Past a certain size, a tangle of type aliases that all refer to each other sent the compiler’s type checker into exponential work. Slow at first. Then, on a bigger tangle, eleven minutes of churning with no end in sight. The compiler wasn’t rejecting these programs — the algorithm accepts them. It just couldn’t finish checking them in any reasonable time.

Inside Pony’s Finite Recursive Type Aliases

This is the second post in a short series on finite recursive type aliases in Pony. The first post told the story of why this took eleven years. As I write this, the pull request that adds the feature is open and in review on ponyc. It hasn’t merged yet. Details may shift before it does, but everything in this post is foundational. It should all hold.

So how does the compiler tell a finite recursive type alias from an infinite one?

The algorithm is Tarjan’s strongly connected components. I’ll walk through it. Then I’ll show you the two checks I built on top of it.

Eleven years to a finite recursive type alias

There’s a pull request open against the Pony compiler. It’s in review right now. When it lands, and it will land soon, you’ll be able to write this:

use "collections"

type JsonValue is
  ( String
  | F64
  | Bool
  | None
  | Array[JsonValue]
  | Map[String, JsonValue])

Today the compiler rejects it. JsonValue mentions Array[JsonValue], which mentions JsonValue, and ponyc throws up its hands: type aliases can't be recursive. That has been true for the entire history of the language. It’s about to stop being true, and the pull request that changes it closes out the oldest open issue in the ponyc repository.

pony-doc: From the back pew to the front pew

ponyc --docs is how you used to generate Pony API documentation. For more than a decade, you’d run that command, point it at a package, and the compiler would write you documentation in MkDocs-compatible format. It was quiet. It was reliable. It was boring.

Last month I deleted the documentation pass from the compiler. pony-doc, a separate Pony program, generates Pony documentation now. It creates the same output as the old documentation pass did. We switched all our sites over from one to the other and no one noticed.

So why move documentation generation out of the compiler? Why do work that has no discernible change for the user? Why now, after a decade of ponyc --docs working just fine?

pony-lint: Codifying the Style Guide

A couple months ago I wrote about teaching Claude to write Pony. The shorthand version: treat the LLM like a junior developer with no memory, build up a CLAUDE.md, refine it as you find where Claude falls short. The CLAUDE.md got Claude most of the way there.

The piece it never quite covered was the style guide. There’s one for the projects under the ponylang GitHub org. It has rules for indentation, line length, naming, where blank lines go. Claude would write code that compiled and worked but didn’t follow the rules. Indentation that should have been two spaces would land on three. A type name would pick up an underscore. A function that belonged on three lines would end up jammed onto one. Stray trailing whitespace. Each one I’d correct. And correct it again. And again.

pony-lsp: An Actor and a Callback

Embed You a ponyc for Great Good introduced libponyc-standalone, a static compiler library you can link your tools against, and a Pony wrapper called pony-ast that exposes the compiler as a callable function. This post is about the Pony language server we built on top of it.

A quick disclaimer before I get going. Almost none of pony-lsp is my work. Matthias Wahl built it from scratch. He wrote the actor architecture, the message dispatch, and the original feature set. He also wrote pony-ast. Orien Madgwick has been pushing it forward; most of the new features over the past several months are his. My contribution is mostly fixing things that broke when I imported the project into ponyc, plus a small feature here and there. The clever stuff is theirs.

Embed You a ponyc for Great Good

The ponyc command you run every day is a main() function with a terminal-width detector glued to it. The actual compiler is a library called libponyc. ponyc is a wrapper around that library, and the wrapper is 149 lines of C.

That’s the setup for this post. The Pony compiler is a library and you can link against it. And because you can link against it, you can build your own tools. And if you want your tool to be one binary instead of a ball of loose dependencies, you want libponyc-standalone.

Pony Gets an Embedded Linker

As of ponylang/ponyc 0.61.1, the compiler carries its own linker. When you compile a Pony program on Linux, macOS, or Windows, ponyc no longer shells out to an external tool to produce your binary. It calls LLD directly, in-process, using the same LLVM infrastructure it already uses for code generation. Cross-compilation to Linux targets works the same way. The compiler is more self-contained than it’s ever been, and cross-compilation just got a lot simpler.