The Bet on a General-Purpose VM — Why Parrot Ended Up Nobody's
Parrot aimed to be the shared VM for dynamic languages: a substrate that Perl 6, Python, Ruby and Tcl would all run on. The premise did not hold. With Perl 6 as the only serious user, the cost paid for generality could never be recovered. In 2013 Rakudo moved to MoarVM — and only by giving generality up and committing to one language did it finally finish.
Last time I wrote about Pugs proving the value of having something that runs. This time, the other side of that — the one bet Perl 6 lost on its implementation substrate.
The Name Comes From an April Fools’ Joke
On 1 April 2001, a joke article appeared:
Larry Wall and Guido van Rossum are merging Perl and Python into a new language called Parrot.
A reference to the Monty Python parrot sketch.
Later, when a VM for Perl 6 was actually built, the name was adopted. The origin captures the goal well: one VM running many dynamic languages.
The Design Bet
Parrot was not a VM for Perl 6. It aimed to be a VM for dynamic languages in general — Perl 6, Perl 5, Python, Ruby, Tcl, PHP, Scheme.
The main design decisions:
| Decision | Reasoning |
|---|---|
| Register machine (not stack) | Read as better for optimising dynamic languages. JVM and CPython are stack machines |
| PMCs (Polymorphic Containers) | Hold differing per-language value semantics in a common container |
| First-class continuations | Unify coroutines, exceptions, and dynamic scope |
| A rich set of built-in types | So implementers of each language could reuse VM-side parts |
Dan Sugalski led it as chief architect. Allison Randal and others took over later, and the Parrot Foundation was established in 2008. Parrot 1.0 shipped in March 2009.
This was a serious and intellectually attractive design. It does not deserve to be laughed at in hindsight. There genuinely is shared work across dynamic-language implementations, and consolidating it is the right shape of idea.
What Went Wrong
1. The Only Serious User Was Perl 6
The premise — that multiple languages would run on it — did not hold.
Python and Ruby already had their own implementations and were improving them independently. There was no motivation to move. They had C extensions, performance characteristics, communities.
So the situation became: the cost paid for generality, with no return on generality.
Abstractions that would have been unnecessary in a Perl 6-specific VM had to be carried forever. The PMC indirection, the language-neutral calling convention — they existed for when the other languages arrive, and the other languages did not arrive.
Here is this series’ sixth pattern.
One user cannot reveal a fake generalisation.
Until a second user turns up, whether an abstraction is genuinely general goes unverified. Parrot got heavier and heavier with a generality that was never tested, because the second user never came.
The nasty part is that this state does not look like failure. Parrot worked. Rakudo ran on Parrot. “The generality is not being recovered” is not the kind of problem that turns a test red.
2. The Performance Did Not Materialise
The register-machine bet did not produce the margin hoped for. GC, calling conventions, PMC dispatch — overheads at each layer accumulated, and Rakudo on Parrot was widely judged too slow for real use.
This is largely the period in which “Perl 6 is slow” became the received wisdom. And the reputation outlived the substrate by years.
3. Building Against a Moving Specification
Because the VM was being built while Perl 6’s specification was still unsettled, every change in the requirements above meant rework below.
The spec/implementation back-and-forth I described last time (the thing Pugs started) is a healthy mechanism — but when that oscillation reaches the VM layer, the cost changes by an order of magnitude.
Settling Up — MoarVM, 2013
In 2013, Rakudo moved its primary backend to MoarVM.
MoarVM (Metamodel On A Runtime) is a VM written in C, led principally by Jonathan Worthington.
Parrot’s lesson is written directly into its design principles.
| Parrot | MoarVM |
|---|---|
| A general VM for many dynamic languages | Committed to NQP / Rakudo only |
| Language-neutral abstractions | The VM knows Raku’s object model (6model) directly |
| Always paying the generality cost | Optimises only what Raku needs |
From “build a general VM and put Perl 6 on it” to “build a VM for Perl 6.”
This is the technical turning point at which Perl 6 headed toward completion. 6.c shipping two and a half years later, in December 2015, follows from this decision.
What Committing Made Possible
Look at MoarVM’s features and you see things that are only possible because it is specialised.
NFG strings — MoarVM holds strings at the grapheme level.
my $s = "a\c[COMBINING ACUTE ACCENT]"; # a + combining accent
say $s.chars; # 1
What looks like one character counts as one in .chars.
Set next to other languages, Raku’s position is clear.
| Language | String unit |
|---|---|
| C | Bytes |
| Java / JavaScript / C# | UTF-16 code units |
| Python 3 / Go (rune) | Code points |
| Ruby | Code points (with encoding) |
| Raku | Graphemes |
A language-neutral VM cannot make this choice, because it would mean taking a specific position on what a string is. A dedicated VM can.
The same goes for handling 6model natively, spesh (specialisation on runtime type information), the JIT, precise GC, and async I/O via libuv — all of them aimed squarely at what Raku needs.
How to Place Parrot
Calling it a failure in one word is not accurate. Things did survive.
- It produced the first path on which Perl 6 actually ran — Rakudo was born on Parrot
- Experience designing an intermediate representation fed into NQP’s design
- The idea of having multiple backends survived as NQP’s backend-neutral design
The accurate summary is:
The premise of the bet (that many languages would arrive) failed, and the cost being paid for it could no longer be recovered.
How Many Backends Can You Actually Maintain?
And the same problem casts a shadow over NQP, which supposedly inherited Parrot’s lesson.
NQP is a subset of Raku, designed to have multiple backends. To support a new VM, port NQP and Rakudo comes along on top. Beautiful in principle.
Here is the reality as of September 2026.
| Backend | Status |
|---|---|
| MoarVM | Effectively the only usable backend. The default |
| JVM | Works, but lags in both features and performance |
| JavaScript | Experimental. Not at production level |
The reason is not technical but staffing. Maintaining one backend is expensive; maintaining several at parity requires a continuing set of hands for each. At Raku’s scale that did not hold.
Which gives the seventh pattern.
Having N backends and having N verified backends are different things.
Unless the second one has a serious user, the layer you believed you wrote neutrally has quietly conformed to the first one’s convenience.
This One Is Not Somebody Else’s Problem
I build a language myself, and it has five backends (an interpreter, C, LLVM IR, Wasm, and RISC-V machine code).
Researching Parrot and NQP gave me a name for something I was already doing. Having five is not the same as having five verified.
In my case, what holds it together is that I keep the interpreter as an oracle and diff the other three backends’ output byte for byte (the fifth, RISC-V, is checked by booting it under QEMU). “I believed I wrote it neutrally” gets actually run and compared.
But that mechanism has a hole too. If the oracle itself is wrong, all five are wrong together. It is the same kind of problem as Parrot being unable to detect, through tests, that its generality was never being recovered.
Which makes the next instalment’s subject urgent for me as well. What is a specification? On what basis do we say something is correct?
Next (part 7): Defining a specification as a test suite. Perl 6 abandoned prose and moved to the position that “the specification is: it passes roast.” What that made possible, and what it gave up.