# Malbolge Unshackled * **Author**: Ørjan Johansen, 2007, as a variant of Ben Olmstead's Malbolge (1998). * **Year**: 2007. * **Canonical sources**: there is no separate specification document. - Johansen's Haskell interpreter, http://oerjan.nvg.org/esoteric/Unshackled.hs (header: "By Ørjan Johansen (Feb 2007-). This program is in the public domain."), defines the language; this implementation follows it step for step; - the prose description of the deviations from Malbolge on the community page https://esolangs.org/wiki/Malbolge_Unshackled (CC0), which links that interpreter as the reference implementation; and - Wikipedia has no article of its own for the variant; it is covered in a section of the Malbolge article, https://en.wikipedia.org/wiki/Malbolge#Variants. * **In LangLib**: - [`Langlib/Languages/MalbolgeUnshackled/`](../../Langlib/Languages/MalbolgeUnshackled/), - runner `lake exe malbolge-unshackled`, - [examples](../../Langlib/Examples/MalbolgeUnshackled/), - tests in [`Langlib/Tests/MalbolgeUnshackled.lean`](../../Langlib/Tests/MalbolgeUnshackled.lean), - the ground floor of a completeness proof, not yet a witness, in [`Langlib/Computability/MalbolgeUnshackled/Main.lean`](../../Langlib/Computability/MalbolgeUnshackled/Main.lean) and [docs/malbolge-unshackled/computability.md](computability.md), with running notes in [completeness-progress.md](completeness-progress.md), and - a Turpentine backend over the input-free fragment in [`Langlib/Languages/Turpentine/Compile/MalbolgeUnshackled.lean`](../../Langlib/Languages/Turpentine/Compile/MalbolgeUnshackled.lean) ([docs/malbolge-unshackled/compiler.md](compiler.md)) * **See also**: [Malbolge](../malbolge/spec.md), the bounded original, and [its computability page](../malbolge/computability.md), which proves that the bound makes Malbolge's halting problem decidable. ## Why the variant exists Malbolge was designed to be impossible to program, and it very nearly was: the first program in it was found by a beam search, not written. But it has 59049 words of 59049 values, and that is a finite state space, so Malbolge is not Turing complete — a fact this library proves rather than asserts. Johansen's fix is a single change with large consequences: take the bound out. Every register and every memory cell holds an unbounded value, memory is infinite, and the language becomes Turing complete while remaining, in every other respect, Malbolge. The cruelty is preserved exactly; only the ceiling is gone. ## What a value is This is the design decision the whole language turns on, and it is not "arbitrary-precision integer". Padding a value with zeros to the left is not available, because the crazy operation has `crz 0 0 = 1`: a zero-padded value would behave differently from the value it pads. Johansen's answer is that **the leading trit repeats forever to the left**. A value is a 3-adic integer whose trit sequence is eventually constant, so `...01` and `...001` are the same thing, and `crz ...01 ...01 = ...110`. The values whose repeating trit is `0` are exactly the naturals, and those are the ones instruction decoding and I/O use. The others are perfectly good values that no instruction can print. The *width* of a value is the number of trits below the repeating prefix, so `...0` has width 0 and Malbolge's 59048 has width 10. Width is what the rotation instruction works in, and it is why Unshackled needs a register Malbolge does not have. ## The machine Three registers, as in Malbolge: the accumulator `a`, the code pointer `c` and the data pointer `d`, all starting at zero. One loop iteration: 1. Let `w = mem[c]`. If `w` is not a natural in 33..126, hang. 2. Dispatch on `(w + m) mod 94`, where `m` is the residue of the *address* `c` (see decision 3): `4` jump `c := mem[d]`, `5` output, `23` input, `39` rotate `mem[d]` right, `40` load `d := mem[d]`, `62` the crazy operation `a := mem[d] := crz a mem[d]`, `68` no-op, `81` halt, everything else no-op. 3. Encrypt the word now at `c` through Malbolge's `xlat2` permutation. 4. Add one to both `c` and `d`. There is no modulus: `...222 + 1 = ...000`. The two extra registers are the **rotation width**, which the rotate instruction works in, and the widest address `d` has been sent to, which is what can make the rotation width grow. ## Semantic decisions in LangLib 1. **Values are normalised.** A value is a repeating lead trit plus the finite list of trits below it, with the invariant that the list does not end in another copy of the lead. Its length is then literally Johansen's *width*. 2. **Pointer arithmetic has no modulus.** Both pointers advance by 3-adic successor, so from `...222` they wrap to `...000` — which is a consequence of the representation, not a bound. 3. **Addresses that are not naturals still decode.** The opcode is `(w + m) mod 94` where `m` comes from `Value.modClass`, which extends "remainder" to every value by fixing the contribution of the repeating trit. This is Johansen's rule, and it is what lets code live at addresses no natural number names. 4. **The memory fill is Malbolge's, extended.** After the source, the rest of memory holds the crazy-operation iteration of the last two words. The iteration is 6-periodic, so an untouched cell's contents depend only on its address's residue mod 6, and the loader computes that six-element table once. Unlike Malbolge's loader, this one accepts source characters above code point 255: values are unbounded and the I/O is Unicode, so a code point is a perfectly good cell value. 5. **A non-printable word loads unchecked and hangs when executed.** Johansen's loader stores it (his `-n` flag, our `--strict`, rejects it instead), and his interpreter's `hang` loops forever when one is executed, exactly as Malbolge's does. We model the hang as a fuel-consuming spin, so it shows up as running out of fuel rather than as an error. 6. **Encryption can crash.** After an instruction runs, the word at `c` is replaced through `xlat2`, which is a table indexed by 33..126. In Malbolge every word is in range, so the question never arises; in Unshackled a rotated or crazy-operated word need not be, and Johansen's interpreter calls `crash` rather than leaving it alone. We report that as a runtime error naming the word. This is the most common way for a naive Unshackled program to die. 7. **I/O is Unicode.** Input reads one character; a newline arrives as `...21` and end of input as `...22`. Output writes the character its code point names, turns `...21` back into a newline, and treats `...22` as *closing the output stream* — so a program that outputs after end of input prints nothing rather than a byte, which is where Unshackled and Malbolge visibly part company. Outputting any other non-natural value is reserved, and we report it as a runtime error. 8. **The starting rotation width is a knob, not a decision.** The language promises only "at least 10 trits". Johansen's interpreter randomises it on every run, precisely so that a program which depends on it fails sometimes. A reference semantics has to be deterministic, so ours is a parameter: `--rot-width N`, default 10, values below 10 raised to 10. A program is correct only if it works at every setting, and the test suite runs `hello.mu` at two. 9. **The growth policy is the least the language allows.** When a `j` instruction sends `d` to an address wider than any seen before, the rotation width becomes twice that width. Johansen's interpreter adds random slack here too; ours is his policy with the slack set to zero. 10. **The rotation width never shrinks**, and nothing but `j` changes it. 11. **Whitespace is the six ASCII space characters** that C's `isspace` and Haskell's `Data.Char.isSpace` agree on. Haskell's is Unicode-aware and C's is locale-dependent; stopping at ASCII is the only choice that is both, and it keeps the loader's behaviour independent of the reader's locale. 12. **Fuel is loop iterations**, including no-ops and each turn of the out-of-bounds spin. ## Computational class **Turing complete, supported by an external construction; not yet proved in Lean.** Matthias Lutter's [2016 Brainfuck interpreter](https://lutter.cc/unshackled/brainfuck.html) implements a dialect with unbounded cells and tape, and publishes its assembly source for inspection. The later [MalbolgeLisp](https://github.com/iczelia/malbolge-lisp) is further evidence. Removing Malbolge's finite bound alone would not establish universality. The [proof audit](proof-audit.md) identifies an impossible invariant in the previous Lean approach and gives the revised fixed-counter construction. LangLib has no machine-checked proof of this yet; the status matrix in [docs/README.md](../README.md) tracks it, and [compiler.md](compiler.md) explains why it would be one of the harder ones here: the simulation has to survive both the self-encrypting code and the free choice of rotation width. ## Trying it The classic first program, and a reminder that nobody writes these by hand. ``` lake exe malbolge-unshackled Langlib/Examples/MalbolgeUnshackled/hello.mu ``` Output: ``` Hello, world! ``` The same program at a rotation width the default run never uses. A correct Unshackled program works at every legal width, and this is how to check one. ``` lake exe malbolge-unshackled --rot-width 37 Langlib/Examples/MalbolgeUnshackled/hello.mu ``` Output: ``` Hello, world! ``` The truth machine: print `0` and halt, or print `1` forever. On `0` it halts. ``` echo -n 0 | lake exe malbolge-unshackled Langlib/Examples/MalbolgeUnshackled/truth.mu ``` Output: ``` 0 ``` On `1` it does not, so give it a fuel bound and expect to hit it. ``` echo -n 1 | lake exe malbolge-unshackled --fuel 200000 Langlib/Examples/MalbolgeUnshackled/truth.mu ``` Output, on stderr after the ones it printed: ``` malbolge-unshackled: out of fuel after 200000 steps (raise with --fuel) ``` A word that has been rotated is no longer a printable natural, so the encryption step after it has nothing to look up and the run dies. The three-character `rotcrash.mu` does exactly that, and it is the failure mode to expect from anything written by hand. ``` lake exe malbolge-unshackled Langlib/Examples/MalbolgeUnshackled/rotcrash.mu ``` Output: ``` malbolge-unshackled: runtime error: the word 13 at c has no encryption; Johansen's interpreter crashes here (Malbolge would leave it unchanged) ``` The shortest program that does anything at all is two characters and halts at once. It prints nothing, so there is no output block below; `--verbose` is the way to see that it really did stop rather than hang. ``` lake exe malbolge-unshackled --verbose Langlib/Examples/MalbolgeUnshackled/halt.mu ``` Output, on stderr: ``` malbolge-unshackled: halted normally; read 0 input byte(s), wrote 0 output byte(s) ``` `echo.mu` is `cat.mu` with a bound: it reads one character, prints it, and halts, which makes it the smallest program here that does I/O and still finishes. ``` echo -n Z | lake exe malbolge-unshackled Langlib/Examples/MalbolgeUnshackled/echo.mu ``` Output: ``` Z ``` `star.mu` prints a character without reading one, in eleven characters, and it is the cheapest way to see a value being *made*. See the "Example programs" section for how. ``` lake exe malbolge-unshackled Langlib/Examples/MalbolgeUnshackled/star.mu ``` Output: ``` * ``` Building a whole string takes rather more. `answer.mu` prints `42` in 134 characters and `banner.mu` prints `MALBOLGE` in 160. ``` lake exe malbolge-unshackled Langlib/Examples/MalbolgeUnshackled/banner.mu ``` Output: ``` MALBOLGE ``` `hello-small.mu` prints the same greeting as `hello.mu` in 172 characters rather than 24365, by putting words the loader does not check into its data cells. The "Example programs" section explains what that buys and what it costs. ``` lake exe malbolge-unshackled Langlib/Examples/MalbolgeUnshackled/hello-small.mu ``` Output: ``` Hello, world! ``` `99bottles.mu` is the whole song, and the largest example here: 78802 characters, printing 11459. It needs about 40000 steps and takes the better part of a minute, so give it fuel and be patient. ``` lake exe malbolge-unshackled --fuel 200000 Langlib/Examples/MalbolgeUnshackled/99bottles.mu ``` Output, of which these are the first four lines and the last: ``` 99 bottles of beer on the wall, 99 bottles of beer, Take one down, pass it around, 98 bottles of beer on the wall. ... No more bottles of beer on the wall. ``` Every one of those works at any legal rotation width, which is the property that matters and the one a hand-written program usually fails. Check it the same way `hello.mu` was checked: ``` lake exe malbolge-unshackled --rot-width 37 Langlib/Examples/MalbolgeUnshackled/banner.mu ``` Output: ``` MALBOLGE ``` ### Programs that read input Three of the examples read: `echo.mu` takes one character, `cat.mu` copies until it is stopped, and `truth.mu` branches on what it is given. All of them read **stdin**, so a pipe or a redirect is what feeds them, and the `-n` matters — without it `echo` adds a newline the program will also read. ``` echo -n Z | lake exe malbolge-unshackled Langlib/Examples/MalbolgeUnshackled/echo.mu ``` Output: ``` Z ``` A redirect does as well, and is the way to feed a program more than a line: ``` lake exe malbolge-unshackled --fuel 200000 Langlib/Examples/MalbolgeUnshackled/cat.mu < README.md ``` There is no output block for that one because `cat.mu` never halts: it copies its input and then spins, so it prints the file and then reports running out of fuel on stderr. That is the program, not a fault. Three things about input here are Unshackled's own rather than the shell's. **Input is Unicode, not bytes.** The input instruction reads one *character* and puts its code point in `a`, decoding UTF-8 on the way in; the output instruction encodes it again. So a character outside ASCII makes one round trip, not two: ``` printf '\xc3\xa9' | lake exe malbolge-unshackled Langlib/Examples/MalbolgeUnshackled/echo.mu ``` Output: ``` é ``` **End of input is a value, not an error.** Reading past the end yields `...22`, and `...22` is exactly the word whose output *closes the stream*. So `echo.mu` with nothing to read prints nothing at all and halts cleanly, rather than failing — the character it read was the end of the input, and printing that ends the output: ``` lake exe malbolge-unshackled --verbose Langlib/Examples/MalbolgeUnshackled/echo.mu < /dev/null ``` Output, on stderr: ``` malbolge-unshackled: halted normally; read 0 input byte(s), wrote 0 output byte(s) ``` **A newline is `...21`, and survives the trip.** It is a value of its own rather than the code point 10, which is why the table in "What a value is" gives the two reserved prefixes meanings at all; `echo.mu` fed a bare newline prints a bare newline. If stdin is an interactive terminal rather than a pipe or a file, the program sees **empty input** — it does not wait for you to type. That is the shared runner's behaviour across the whole library, and it is why every command above pipes or redirects something, even `/dev/null`. ## Compilation from Turpentine The reason this variant is implemented at all: unbounded memory means a total compiler can exist, where for Malbolge it provably cannot. A backend now exists, over the programs that do not read input — `turpentine compile --to malbolge-unshackled`. That is not a restriction on the source syntax (loops, arrays and arithmetic all compile) but on where the control flow is decided: the backend settles it before the target runs, and emits a straight-line image whose cells each execute once, so self-encryption never bites. Three compiled programs are checked in under `Langlib/Examples/MalbolgeUnshackled/compiled/` and appear among the examples below — one of them the whole beer song, which the backend emits in fewer cells than the hand-written port of it in this same directory. Reading input is the half that is missing, and it is the Turing-completeness work rather than more code generation: a chain of crazy operations against compiled-in constants cannot produce a flag that depends on the accumulator (`no_accumulator_flag`), so branching on an unknown value needs the register encoding, which needs rotation. See [compiler.md](compiler.md) for the backend and [completeness-progress.md](completeness-progress.md) for the rest. ## Example programs Unshackled inherits Malbolge's syntax exactly — no comments, every byte loaded into memory, a character's meaning depending on the address it lands at — so these texts are as literal as texts get. What is new is that a program must work at *every* rotation width, which is why the interesting examples are so much larger than their Malbolge counterparts. **Halt** (`halt.mu`, two characters) — the same minimal program as in Malbolge, and the only one here anyone would call portable. ``` QC ``` `Q` is code 81 and lands at address 0, so the dispatch is `(81 + 0) mod 94 = 81`, which is halt. It runs and stops in one step, at any rotation width. The second character is there because the loader wants two seeds for the memory fill and refuses a one-character program. **Echo** (`echo.mu`, three characters) — read one character, print it, stop. ``` ubO ``` The whole of it is the address arithmetic: an instruction at address `i` is `(mem[i] + i) mod 94`, and the printable range 33..126 is exactly 94 wide, so for every address there is exactly one character meaning a given instruction there. `u` is 117 and `(117 + 0) mod 94 = 23`, input; `b` is 98 and `(98 + 1) mod 94 = 5`, output; `O` is 79 and `(79 + 2) mod 94 = 81`, halt. That is the entire program, and it is `cat.mu` with a bound. **A character out of nothing** (`star.mu`, eleven characters) — prints `*` without reading anything. ``` DCBA@?>~[H ``` Seven no-ops, then rotate, output, halt. The rotate is the interesting one. At address 7 the character that means rotate is `~`, code 126, and a rotation moves the lowest trit to the top of the window: `126 = 11200₃`, so the result is `126 / 3 = 42` with a zero carried to the top. Two things follow. The result is 42, which is `*`. And **the width does not appear in the answer**, because the trit that would have been placed at the far end of the window is zero — so this prints `*` at width 10, at width 11, and at width 37 alike. Change the low trit and both properties go: that is `rotcrash.mu`, whose rotation lands on 13, which is not printable, and the encryption step then has nothing to look up. **Whole strings** (`answer.mu`, `banner.mu`) — 134 and 160 characters, printing `42` and `MALBOLGE`. `answer.mu` is the direct counterpart of Malbolge's `answer.mal`, which prints the same two characters in 28 instructions and does not survive the move here: ``` DCBA@?>=~5432V0/S@210/.-,+*)('&%$#"!~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZY XWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#@ca}v_ ``` And `banner.mu`: ``` DCBA@?>=~543210T.-,+O)('&J$#G!~}|Bzy?wv=<;:9876543210/.-,+*)('&%$#"!>=O{)(r&v$#m2qSBn- ,NNiu'frqc"! ``` Neither uses a rotation at all, so neither can depend on the width. They are built the way every Malbolge generator builds things, adapted to Unshackled's infinite words. `a` starts at `...0`. One crazy operation against a printable — so `...0` — cell gives a word whose prefix is `...1`, which the output instruction refuses; a second brings the prefix back to `...0` and it can be printed. So characters are built by an **even** number of crazy operations, and the operand of each is a memory cell the program chose. Getting clean cells to operate on is the other half. `d` follows `c` one for one, so `mem[d]` is the instruction being executed and a crazy operation would overwrite it and then crash the encryption step. A load-`d` first walks `d` away — its own character decides where it lands, which is `(7 - k) mod 94 + 33` for a load-`d` at address `k` — and after that the crazy operations eat a run of cells nowhere near the code. Searching those chains breadth-first, the values this construction reaches are exactly **0 through 80** — as characters, space through `P`, 49 of the 95 printable ones. Letting the chains run longer does not extend it by one value, and neither does dropping the rule that a data cell must be a legal instruction: with all 94 printable words available at every step the closure is still 0..80, and length two already reaches all of it. So `MALBOLGE` is constructible and `Hello, world!` is not. **Why 81.** Look at the crazy table's first two rows: `crz 0 0 = crz 0 1` and `crz 1 0 = crz 1 1`. Both send the two columns to the same trit. A printable word is at most `126 = 11200₃`, so its trit 4 is 0 or 1 and never 2 — and a printable word's repeating trit is 0. So at every step the accumulator's trit 4 is computed from the same row and an indistinguishable column as its repeating trit, and the two evolve identically. (The rows do part company at `crz 2 0 = 0` against `crz 2 1 = 2`, but the accumulator's repeating trit starts at 0 and thereafter alternates 0, 1, 0, 1 against `...0` operands, so row 2 is never the one in play.) Output demands a repeating trit of 0, since that is what makes a value a natural at all — so trit 4 is 0 too, and the value is below `3^4 = 81`. **The way out is one data cell.** What the construction lacks is an operand whose trit 4 is 2, and no printable character has one: the smallest word that does is 162. It does not have to be printable, because it is never executed — the loader checks a character against the instruction table only when its code is in 33..126 and stores anything else unchecked (decision 5 above). Add a single such word to the operands and every byte below 128 becomes reachable. `hello-small.mu` is what that buys: the same greeting as `hello.mu` in **172 characters instead of 24365**, printed at every rotation width, with no rotation in it anywhere. The price is exactly one thing, and it is the reason `hello.mu` is still the flagship example — `--strict`, Johansen's `-n`, refuses to load it: ``` lake exe malbolge-unshackled --strict Langlib/Examples/MalbolgeUnshackled/hello-small.mu ``` Output, in which the character between the quotes is U+0099 itself and so shows as nothing at all; it is written `` here because a page cannot render it: ``` malbolge-unshackled: character '' (code 153) at 1:129 is not a printable instruction, and --strict rejects those ``` **99 bottles** (`99bottles.mu`, 78802 characters) — the song, and the one program here that is a *port* rather than an original. Malbolge's `99bottles.mal` does not run under Unshackled; this prints its output byte for byte, at every rotation width, and the two were compared with `cmp` rather than by eye. It is too long to quote, and it is built the same way `hello-small.mu` is — an even number of crazy operations per character, against data cells the loader does not check — with one addition that only a long program needs. A single load-`d` puts `d` a fixed 118 ahead of `c`, which is fine for a hundred instructions and hopeless for thirty-five thousand: the cells `d` is eating are the instructions `c` is about to reach. So the prologue does it twice. The first load-`d` lands on 126, the second reads address 127 — a word the loader never checks, and so as large as we like — and a jump steps over that cell, because a jump to `T` encrypts `mem[T]` and resumes at `T+1`. After that the data region sits above the last instruction and the two never meet. The size is the interesting number. 78802 characters to print 11459, against `99bottles.mal`'s 22807 — so about three and a half times the Malbolge original, which for a language with no bounded words and a straight-line program with no loop in it is a better ratio than `hello.mu`'s 24365 characters for fourteen. It is also slow: about 40000 steps, but the better part of a minute, because the interpreter's cost grows with the size of the program and this one is a hundred kilobytes. That is why it is an example and not a test. **cat** and the **truth-machine** (`cat.mu`, `truth.mu`) — byte-for-byte the same files as `cat.mal` and `truth.mal` in the Malbolge examples. ``` (=BA#9"=<;:3y7x54-21q/p-,+*)"!h%B0/. ~P< <:(8& 66#"!~}|{zyxwvu gJ% ``` They happen to survive the move: nothing they do depends on the width being exactly ten. That is the exception rather than the rule, and it is worth being exact about how rare it is. Of the eight programs in `Langlib/Examples/Malbolge/`, **three** run unchanged here — `cat.mal`, `truth.mal`, and `nop.mal`, which is `halt.mu` under another name. The other five do not, and they all fail the same way: ``` lake exe malbolge-unshackled Langlib/Examples/Malbolge/hello.mal ``` Output — one character, and then the run dies: ``` Hmalbolge-unshackled: runtime error: cannot output ...10221: values starting with trit 1 or 2 are reserved, and only ...22 and ...21 have meanings so far ``` That is the difference between the two languages in one line. Malbolge's words are ten trits and everything above them is thrown away, so a program may leave rubbish in the high trits and print the low ones regardless. Unshackled's words have no top, the rubbish stays, and the output instruction will not print a word whose infinite prefix is not `...0`. The greeting gets as far as `H` and then meets a word beginning `...1`. `rotcrash.mu` above is the same disagreement from the other side: a rotation whose result Malbolge would quietly leave alone is a word Unshackled cannot encrypt. **Hello, world** (`hello.mu`) — 24365 characters, of which the first two lines' worth are ``` bCBA@?>=<;:9876543210/.-,I*)(E~%$#"RQ}|{zyxwvutsrD0|nQl,+*)(f%dF"a3_^]\[ZYX WVUTSRQJmNMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#dc~}|_^yrwZutsrqpinPlO ``` Compare that with Cooke's 120-character Malbolge hello world. The two hundredfold difference is the price of width-independence: nothing in the program may assume ten trits, so its constants have to be built to fit whatever width it finds itself running at. Checking that it really is width-independent is what `--rot-width 37` is for — the greeting comes out the same. **Compiled, not written** (`compiled/primes.mu`, `compiled/sort.mu`, `compiled/99bottles.mu`) — 348, 268 and 64886 characters, printing the primes up to 30, six numbers in order, and the whole beer song. Every other program on this page was written or searched for by a person. These three are output: `turpentine compile --to malbolge-unshackled` applied to `primes-mu.turp` and `sort-mu.turp`, the input-free twins of two of the front end's examples, and to `99bottles.turp`, which needs no twin because it reads nothing as written. The song is the one place on this page where the compiler and a person produced the same program, and the compiler's is the smaller: 64886 cells against 78790 for the hand-built `99bottles.mu` above — its 78802 characters less the whitespace the loader skips — for the same 11459 bytes. That is 5.66 cells per printed byte against 6.87. Both are straight-line images that print one byte at a time, so the gap is in what one printed byte costs, not in anything structural, and neither figure is tuned: the compiler emits the same shape here as it does for a twelve-byte sort. All three need something the language did not intend to offer. Malbolge's loader checks a character in 33..126 against its address and refuses it if the result is not one of the eight instructions, but stores anything *outside* that range unchecked — decision 5 above, an accident of Malbolge's that Unshackled inherits. The compiler uses those cells as data, which is what lets it hold a jump target or an arbitrary constant in one cell. Two consequences: `--strict` refuses to load any of them, by design; and none is confined to the alphabet a printable-only program is confined to, which `hello-small.mu` and the 81 ceiling above describe. Here is `sort.mu` in full, in a transliteration this page states rather than assumes: a cell in 33..126 prints as itself, and a cell outside that range — a data cell — as its decimal code point in angle brackets. ```text ('`A@?>=<;:9876543210/.-,+*)('&%$#"!~}|{z<128>xwvutsrqponmlkjihgfedcba`_^] \[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"<230>>P|<<2187> y<2247><2267>v<2247><2187>s<2241><2267>p<2244><2187>m<2241><2267>j<2244><2 187>g<20><2241>d<2234><20>a<26><2247>^<2240><20>[ZYXWVUTSRQPONMLKJIHGFEDCB A@?>=<;:9876543210/.-,+*)('&%$#"!~}|{zyxqp6nm3kj0hg-ed*ba'_^$\[!YX|VUySRvP Os` ``` The shape is the compiler's, and it is the same in every program it emits. `('` and a backtick are a three-cell prologue that separates `d` from `c`; the long descending ramps are padding, because a no-op at address `a` is the word `(68 - a) mod 94` and consecutive addresses therefore take consecutive characters; `<128>` at address 41 and `<230>` at address 129 are the two pointer cells; from address 130 comes a **data row** of crazy-operation constants, and after a 64-cell gap a **code row** of 37 cells that `c` walks while `d` walks the data row beneath it. The backtick at address 267 is the halt: `(96 + 267) mod 94 = 81`. Run it: ``` lake exe malbolge-unshackled --fuel 100000 Langlib/Examples/MalbolgeUnshackled/compiled/sort.mu ``` Output: ``` 1 2 5 5 6 9 ``` And run the song, which needs more fuel and some fifteen seconds, one step per cell: ``` lake exe malbolge-unshackled --fuel 200000 Langlib/Examples/MalbolgeUnshackled/compiled/99bottles.mu ``` Output, of which these are the first four lines and the last: ``` 99 bottles of beer on the wall, 99 bottles of beer, Take one down, pass it around, 98 bottles of beer on the wall. ... No more bottles of beer on the wall. ``` Byte for byte, that is what `99bottles.mu` prints, and what Malbolge's `99bottles.mal` prints: three programs in two languages, two of them written and one compiled, compared with `cmp` rather than by eye. All three are derived files, regenerated by `scripts/gen-mu-examples.sh` and checked for staleness by its `--check`. The layout, the cost model and the fragment are in [compiler.md](compiler.md). ### Runtime construction examples **Rotation loop and one growth step** (`rotation-loop.mu`, `grow-once.mu`) are original LangLib programs accompanying the [reworked completeness proof](runtime-proof.md). Each has 3004 cells, consumes no input, and prints nothing. Both need the permissive loader. The first repeatedly rotates cell 3000; the second rotates one, grows the rotation width, returns from distant memory, and halts. Neither implements counter arithmetic yet. Here are both programs in full as a sparse transliteration. A pair `a:n` means decimal Unicode code point `n` at cell address `a`. Every unlisted address from 0 through 3003 contains the unique printable no-op word: compute `r = (68-a) mod 94` in `0..93`, and use `r` if `r >= 33`, otherwise `r+94`. Apply the following overrides; there are no other cells before the loader's generated fill. Encode the resulting code points as UTF-8 and append one newline. This specifies even the control character at 3000 in the growth program, which would be invisible in a literal listing. `rotation-loop.mu`: ```text 0:40 1:39 2:96 41:2998 153:74 154:38 248:74 249:37 2998:248 2999:152 3000:243 3001:153 3002:247 3003:2997 ``` `grow-once.mu`: ```text 0:40 1:39 2:96 41:2996 153:74 154:38 248:74 249:37 436:74 440:70 441:110 2997:150 2998:248 2999:435 3000:1 3001:153 3002:247 3003:2997 ``` At addresses 153 and 248, word 74 means rotate and move-data respectively. Their following jumps restore those words before returning. The first program's entry takes three instructions and reaches `c=153, d=3000`; its main loop takes six instructions per rotation. The second enters via two additional no-ops, returns from the reset block to 436, then executes `movd; nop; nop; nop; movd`. The first move grows the width. The last reads an untouched fill cell and returns the data pointer to 2998; code reaches the halt at 441. The default setup makes the working width 16, so the growth example finishes at width 32. With starting width 37 it finishes at 74. The tests inspect those internal states rather than inferring growth from an empty output. Run two complete rotation cycles at the default setting. The program prints nothing and exits with status 2 when its 195 instructions run out: ```sh lake exe malbolge-unshackled --fuel 195 Langlib/Examples/MalbolgeUnshackled/rotation-loop.mu ``` Run the growth demonstration through its halt. It exits successfully without output after 17 instructions: ```sh lake exe malbolge-unshackled --fuel 17 Langlib/Examples/MalbolgeUnshackled/grow-once.mu ``` The same source also grows and returns at an odd starting width: ```sh lake exe malbolge-unshackled --rot-width 37 --fuel 17 Langlib/Examples/MalbolgeUnshackled/grow-once.mu ``` The checked-in programs are derived files. Verify that they match the original generator, with no output on success: ```sh python3 scripts/gen-mu-runtime.py --check ``` **Calling the same growth code twice** (`grow-twice.mu`) starts with a finite initializer, then grows the working width twice using the same code and return records. It has 7002 source cells, reads no input and prints nothing. Its two separate one-markers avoid assuming that the unfinished arithmetic runtime can already rebuild a marker after growth. Here is the complete program in the same sparse transliteration: addresses 0 through 7001 contain the unique printable no-op word specified above, except for these decimal code-point overrides. Encode as UTF-8 and append one newline. ```text 0:40 1:39 2:96 41:4199 71:2996 153:74 154:38 181:3103 248:74 249:37 341:74 342:38 436:74 437:2267 438:180 439:6567 440:70 441:33 1000:74 1001:95 1002:72 1003:93 1004:70 1005:69 1006:90 1007:67 1008:88 1009:65 1010:64 1011:85 1012:62 1013:83 1014:60 1015:59 1016:116 1200:62 1201:119 1300:97 2997:150 2998:248 2999:435 3000:1 3001:153 3002:247 3003:2997 3100:2265 3101:436 3104:217 3105:437 3108:6561 3109:438 3197:338 3198:248 3199:435 3200:1 3201:341 3202:247 3203:3197 4200:999 4201:3099 5002:436 5007:1199 5008:3196 5009:1299 6568:3107 7000:5001 7001:5001 ``` The entry jumps to 1000, where three pairs of crazy operations turn the non-printable source words at 437–439 into 41, 102 and 96. These belong to a five-word encryption orbit whose every phase is a no-op at those three addresses. The runtime accepts these no-op phases; the loader would reject them if they appeared directly in the source. Initialization supplies the missing bridge. The first marker is at 3000, rotated by code at 153; the second is at 3200, rotated by code at 341. Both return through the pointer reset at 248 and the growth block at 436. The jump at 441 restores the first move, then a four-no-op sweep restores the second. After each eleven-step call, code resumes at 1200 with the data pointer at 5008. The move at 1200 becomes a no-op after its first execution, so the second return selects the halt route instead of starting another call. The final two source words, at 7000 and 7001, make the sampled distant fill entry 5001. Run both calls and the halt. The default setup establishes width 18, then grows to 36 and 72. The program exits successfully after 63 instructions with no output: ```sh lake exe malbolge-unshackled --fuel 63 Langlib/Examples/MalbolgeUnshackled/grow-twice.mu ``` At starting width 37, the same source grows to 74 and 148 and halts after the same number of instructions, again without output: ```sh lake exe malbolge-unshackled --rot-width 37 --fuel 63 Langlib/Examples/MalbolgeUnshackled/grow-twice.mu ``` The shared generator and its `--check` cover this program too. Its checked runtime contracts and remaining initialization and overflow-retry obligations are in [runtime-proof.md](runtime-proof.md#reusable-growth-and-initialization). **Regenerating the same marker** (`marker-reset.mu`) constructs resident constants, rotates its marker at address 3200, resets that same cell to one, and halts. It has 4202 source cells, consumes no input and prints nothing. Here is its complete sparse transliteration: addresses 0 through 4201 contain the unique printable no-op word specified above, except for these decimal code-point overrides. Encode as UTF-8 and append one newline. ```text 0:40 1:39 2:96 39:3099 41:4199 75:2998 110:3599 153:74 154:38 248:74 249:37 270:74 271:109 530:74 531:37 1000:74 1001:95 1002:72 1003:71 1004:70 1005:91 1006:68 1007:89 1008:66 1009:65 1010:64 1011:85 1013:61 1014:60 1015:59 1016:116 1300:56 1301:54 1303:53 1304:52 1305:51 1306:108 1400:91 2999:152 3000:243 3001:153 3002:247 3003:3197 3100:243 3101:3399 3198:248 3199:269 3200:243 3201:270 3202:529 3203:3398 3204:1299 3205:3199 3211:1399 3399:269 3400:243 3401:270 3402:247 3403:3497 3498:248 3499:269 3500:2 3501:270 3502:247 3503:3597 3598:248 3599:269 3600:243 3601:270 3602:247 3603:3197 4200:999 4201:2999 ``` The initializer synthesizes two uniform all-ones constants at 3000 and 3400, and clears the future mask at 3600. A first pass through the reset code constructs that mask (zero in the low trit, ones above it) and the marker one. At instruction 54 the resident constants are ready. This bootstrap is checked by execution; the reset theorem assumes the completed resident constants on entry. The caller at 1300 then rotates cell 3200 and re-enters the reset at 153. The reset loads all-ones by rotating the constant at 3000, clears the marker with a crazy operation, and follows the constant path all-ones → two → mask before writing one back to the marker. Every constant keeps its value. A router at 530 changes from move to no-op and back, directing the two marker writes through the same return record to different continuations. The proved reset takes 34 instructions, from instruction 61 to 95 of this source execution. Its contract accepts a marker at any ternary position, without a bound imposed by the current rotation width. Run the bootstrap, rotation, reset, and halt. The setup reaches width 16; the marker temporarily becomes `3^15 = 14348907` before returning to one. The program exits successfully after 103 instructions with no output: ```sh lake exe malbolge-unshackled --fuel 103 Langlib/Examples/MalbolgeUnshackled/marker-reset.mu ``` At width 37 the same cell temporarily holds `3^36 = 150094635296999121`. The program again resets it to one and halts after 103 instructions without output: ```sh lake exe malbolge-unshackled --rot-width 37 --fuel 103 Langlib/Examples/MalbolgeUnshackled/marker-reset.mu ``` The rotation wrapper and final halt selection are single-use. The program demonstrates the resident reset on a rotated marker, not a complete repeating overflow loop. The following example supplies a compatible shared-record convention for repeating rotation and reset. The [reset contract](runtime-proof.md#reusable-marker-reset) states the exact preservation and initialization obligations. The common runtime generator and its `--check` also cover this example; strict loading rejects its data. **Repeating rotation and reset** (`marker-cycle.mu`) removes the single-use rotation wrapper. One physical marker, one adjacent return record, and one finite set of constants support a 50-step cycle indefinitely. This program has 4202 source cells and consumes no input. It has no halt or exit branch on the initialized cycle. This is the complete source in sparse transliteration: addresses 0 through 4201 hold the unique printable no-op word defined above, except for the following decimal code-point overrides. Encode as UTF-8 and append a newline. ```text 0:40 1:39 2:96 39:3099 41:4199 75:2998 83:3599 110:82 153:74 154:38 248:74 249:37 270:74 271:109 272:247 273:2995 526:127 527:2224 528:2467 529:74 530:74 531:37 1000:74 1001:95 1002:72 1003:93 1004:70 1005:91 1006:68 1007:89 1008:66 1009:87 1010:64 1011:85 1012:62 1013:61 1015:81 1016:58 1017:57 1018:56 1019:77 1020:54 1021:75 1022:52 1023:51 1024:50 1025:49 1026:70 1028:46 1029:45 1030:44 1031:101 1300:114 2225:6598 2226:526 2468:6598 2469:527 2996:248 2997:529 2999:152 3000:317 3001:153 3002:247 3003:3197 3100:243 3101:3399 3195:248 3196:525 3198:248 3199:269 3200:243 3201:270 3202:529 3203:3398 3204:1299 3205:247 3206:3194 3399:269 3400:243 3401:270 3402:247 3403:3497 3498:248 3499:269 3500:2 3501:270 3502:247 3503:3597 3598:248 3599:269 3600:243 3601:270 3602:247 3603:3197 3800:6617 3801:525 4200:999 4201:3799 ``` The first initializer writes 74 into 526, 527 and 528. At these addresses both 74 and its encryption 70 are runtime no-ops, but neither is a legal source instruction; the initial words 127, 2224 and 2467 are therefore converted by pairs of crazy operations. The same startup then constructs the reset's constants. It reaches the reset at instruction 35, finishes bootstrapping at 69, and first reaches the rotation entry at 76. Address 529 is both a rotation instruction and a reset landing. Rotating cell 3200 changes word 74 at 529 to 70; the nine-step route jumps back to 529 to restore it before entering reset at 153. The reset itself lands on 529 twice, preserving its entry word. A seven-step return through the three no-ops closes the cycle. Each pass flips those no-op phases; the next pass uses them just as successfully. The marker's adjacent words, `3201:270` and `3202:529`, stay unchanged throughout. Run initialization and nine full cycles. The marker repeatedly becomes `3^15 = 14348907` and returns to one at working width 16. The program emits no bytes; the runner exits with status 2 and reports the fuel limit on stderr: ```sh lake exe malbolge-unshackled --fuel 526 Langlib/Examples/MalbolgeUnshackled/marker-cycle.mu ``` Output: ```text malbolge-unshackled: out of fuel after 526 steps (raise with --fuel) ``` At starting width 37, the marker instead becomes `3^36 = 150094635296999121`. The same nine cycles finish at the same fuel boundary, again with no program output and the runner's diagnostic on stderr: ```sh lake exe malbolge-unshackled --rot-width 37 --fuel 526 Langlib/Examples/MalbolgeUnshackled/marker-cycle.mu ``` Output: ```text malbolge-unshackled: out of fuel after 526 steps (raise with --fuel) ``` The [cycle proof](runtime-proof.md#a-closed-rotationreset-cycle) proves arbitrary repetition from the resident invariant, including all intermediate fuel prefixes. Tests establish this concrete source's initialization at both widths; a general loader-reachability theorem remains open. The cycle does not yet call the width-growth service or implement a terminating arithmetic scan. `scripts/gen-mu-runtime.py --check` covers this source too. **Growing forever with one marker** (`grow-loop.mu`) connects the rotation, growth and reset services into an 87-instruction cycle. Each pass doubles the rotation width, regenerates one at address 3200 and restores the resident services for another call. It consumes no input and produces no output. Growth is unconditional; this is not yet a counter increment or a Turing-completeness witness. The program has 12006 source cells. This is its complete sparse transliteration: addresses 0 through 12005 contain the unique printable no-op word defined above, except for these decimal code-point overrides. Encode as UTF-8 and append one newline. ```text 0:40 1:39 2:96 39:5999 41:7799 42:5999 52:5999 61:5999 71:5999 75:5999 83:5999 97:5999 103:5999 105:5999 110:82 146:6635 147:6635 148:6635 149:6635 150:6635 151:6635 152:6635 153:74 154:38 248:74 249:37 270:74 271:109 272:247 273:2995 429:6635 430:6635 431:6635 432:6635 433:6635 434:6635 435:6563 436:74 437:6617 438:6579 439:6729 440:70 441:33 526:6635 527:6635 528:6635 529:74 530:74 531:37 1200:120 1300:114 1400:6635 1401:6635 1402:6569 1403:6635 1404:104 2225:6598 2226:526 2468:6598 2469:527 2991:248 2992:145 2996:248 2997:529 2999:1399 3000:317 3001:153 3002:247 3003:3197 3004:247 3005:3190 3100:243 3101:3399 3191:248 3192:428 3195:248 3196:525 3198:248 3199:269 3200:1 3201:270 3202:529 3203:3398 3204:1299 3205:247 3206:3194 3399:269 3400:243 3401:270 3402:247 3403:3497 3498:248 3499:269 3500:2 3501:270 3502:247 3503:3597 3598:248 3599:269 3600:243 3601:270 3602:247 3603:3197 3800:6617 3801:525 4200:999 4201:3799 5002:436 5007:1199 5008:247 5009:2990 6000:6561 6001:1402 6003:6567 6004:1401 6006:6567 6007:1400 6009:6561 6010:1399 6012:6561 6013:527 6015:6561 6016:526 6018:6561 6019:525 6021:6751 6022:438 6024:6561 6025:437 6027:6777 6028:436 6030:6561 6031:434 6033:6567 6034:433 6036:6561 6037:432 6039:6561 6040:431 6042:6561 6043:430 6045:6561 6046:429 6048:6561 6049:428 6051:6561 6052:151 6054:6561 6055:150 6057:6561 6058:149 6060:6561 6061:148 6063:6561 6064:147 6066:6561 6067:146 6069:6561 6070:145 6072:2999 6075:243 6076:3399 6078:245 6079:3599 6081:5006 7800:7999 7801:5999 8000:124 8001:51 8002:122 8003:49 8004:120 8005:119 8009:43 8010:114 8011:41 8012:112 8013:111 8020:126 8021:103 8022:124 8023:101 8024:100 8034:112 8035:89 8036:110 8037:87 8038:86 8051:95 8052:72 8053:93 8054:70 8055:69 8071:75 8072:52 8073:73 8074:50 8075:49 8094:52 8095:123 8096:50 8097:121 8098:120 8120:120 8121:97 8122:118 8123:95 8124:94 8149:91 8150:68 8151:89 8152:66 8153:65 8181:59 8182:36 8183:57 8184:34 8185:33 8216:118 8217:95 8218:116 8219:93 8220:92 8254:80 8255:57 8256:78 8257:55 8258:54 8295:39 8296:110 8297:37 8298:108 8299:107 8339:89 8340:66 8341:87 8342:64 8343:63 8386:42 8387:113 8388:40 8389:111 8390:110 8436:86 8437:63 8438:84 8439:61 8440:60 8489:33 8490:104 8491:125 8492:102 8493:101 8545:71 8546:48 8547:69 8548:46 8549:45 8604:106 8605:83 8606:104 8607:81 8608:80 8666:44 8667:115 8668:42 8669:113 8670:112 8731:73 8732:50 8733:71 8734:48 8735:47 8799:99 8800:76 8801:97 8802:74 8803:73 8870:122 8871:99 8872:120 8873:97 8874:96 8944:48 8945:119 8946:46 8947:117 8948:116 9021:43 9022:64 9023:41 9024:40 9025:39 9101:79 9102:56 9103:77 9104:54 9105:53 9106:52 9107:51 9186:88 9187:65 9188:86 9189:63 9190:62 9191:61 9192:60 9274:72 9275:35 12004:5001 12005:5001 ``` The initializer at 8000 synthesizes 24 runtime no-ops, using pairs of crazy operations with natural operands stored at 6000 and above. These no-op phases cannot be loaded directly as source instructions. The initializer then constructs the reset constants and enters the shared return route. It reaches the rotation entry at instruction 1331, with marker one and all resident code ready. The final two source cells seed the periodic fill: the first distant return read, even at the proof's minimum width ten, lies beyond this longer source prefix. A cycle rotates the marker, restores the rotor, and follows a 15-step bridge to the growth service. After eleven steps of growth, an eleven-step bridge enters the 34-step reset. Seven more steps return to the rotation entry. The marker's adjacent records, `3201:270` and `3202:529`, remain unchanged. The [growth-cycle proof](runtime-proof.md#a-closed-growth-cycle-on-one-marker) proves arbitrary repetition from the resident invariant and widths beyond any fixed bound. Full source initialization is checked by execution at the two widths below; its symbolic proof remains open. Run initialization and three full growth cycles. The default setup reaches width 18, then the cycles reach 36, 72 and 144. The runner exits with status 2 and reports the fuel limit on stderr; the program itself emits no bytes: ```sh lake exe malbolge-unshackled --fuel 1592 Langlib/Examples/MalbolgeUnshackled/grow-loop.mu ``` Output: ```text malbolge-unshackled: out of fuel after 1592 steps (raise with --fuel) ``` Starting at width 37, the same cycles reach 74, 148 and 296. The fuel boundary and diagnostic are unchanged: ```sh lake exe malbolge-unshackled --rot-width 37 --fuel 1592 Langlib/Examples/MalbolgeUnshackled/grow-loop.mu ``` Output: ```text malbolge-unshackled: out of fuel after 1592 steps (raise with --fuel) ``` `python3 scripts/gen-mu-runtime.py --check` checks this source together with the preceding five runtime examples. Strict loading rejects their data cells. **A branch that keeps its code** (`bit-branch.mu`) visits the same low-memory conditional branch with flags `0, 1, 1, 0`, exercising each outcome in both phases of its no-op. It halts after 21 instructions without reading input or printing anything. The flags are prepared data, and its callers form a finite chain; this example does not compute a marker test. This is the complete source in sparse transliteration. Addresses 0 through 10203 hold the unique printable no-op word defined above, except for these decimal code-point overrides. Encode as UTF-8 and append one newline. ```text 0:98 1:6635 2:96 97:1999 99:35 100:56 101:33 102:54 103:125 104:124 105:87 700:39 800:86 801:49 900:80 901:43 1000:74 1001:37 2000:0 2001:699 2002:799 2003:9999 6636:6561 6637:0 10000:1 10001:899 10002:10099 10100:1 10101:999 10102:10199 10200:0 10201:699 10202:699 ``` The jump at address 0 skips the initially nonprintable word at 1. Startup at 99 follows that word as a pointer, performs a crazy/move/crazy synthesis that writes 74 into address 1, and reaches the first branch after seven instructions. The bit zero at 2000 jumps to landing 0, so execution performs the no-op at 1 and then the jump at 2. That extra no-op advances the data pointer to the second continuation word, 799; execution resumes at 800. The next caller loads the address of a prepared one. This time the jump lands on 1 and execution resumes directly at the jump at 2, selecting the first continuation word. Two further calls try one and zero again. Address 1 alternates `74 → 70 → 74 → 70 → 74`: each call encrypts it once, whether as a landing or as an executed no-op. Address 2 holds a stable jump throughout. All four flags and all continuation words keep their values. The [branch theorem](runtime-proof.md#low-trit-extraction-and-conditional-dispatch) proves both paths, their memory frames and the preserved code invariant. Separate nine-step routines extract a marker's low bit and restore scratch. A fourteen-step variant reserves compatible branch continuation slots. Connecting its result pointer to dispatch and restoring the caller remain open, as does a terminating marker scan. This example's complete loader/startup reachability is checked by execution. Run the initializer, all four branches and the final halt. The default startup reaches width 18; the program exits successfully with no output: ```sh lake exe malbolge-unshackled --fuel 21 Langlib/Examples/MalbolgeUnshackled/bit-branch.mu ``` Starting at width 37, the same source follows the same branches and halts at the same instruction boundary, again with no output: ```sh lake exe malbolge-unshackled --rot-width 37 --fuel 21 Langlib/Examples/MalbolgeUnshackled/bit-branch.mu ``` The runtime generator's `--check` includes this seventh example. Strict loading rejects the natural data used by its initializer.