Visualizing programs with side-effects in a postfix shell with a live-updating text-mode environment. Built all the way up from machine code without any dependencies (except an x86 processor and Linux kernel).


Project page: github.com/akkartik/mu

More context: mastodon.social/@akkartik/1048

*Editing functions in the Mu shell*


Long delay since my last video. Printing floating-point numbers is *really* hard. I'm still half-assing it.

As a follow-up to merveilles.town/@akkartik/1053, I'm tightening focus to two threads:

a) These Mu shell experiments, and
b) An extremely skeletal OS to drop the Linux kernel dependency.

Deprioritized for now:
a) Other processors: RISCV, ARM, RPi, etc.
b) Graphics, mouse, etc. Device priorities for the OS are disk then ethernet.

*Towards running Mu without Linux*

All Mu really needs so far is to print to screen and read from the keyboard. Here's a 2-minute video about achieving that:


It seems such a small thing. But I needed lots of help, as you can see from the additions to my credits: github.com/akkartik/mu/compare

Merry Christmas to all! What a beautiful world.

Project page: github.com/akkartik/mu

More context: mastodon.social/@akkartik/1048

*Switching gears to pure graphics*

Until now Mu has followed classic Unix: stdin, stdout, pure text mode.

But giving up an OS kernel requires controlling the screen myself. Which requires various complicated probing for hardware. Then programs handling various screen sizes.

Easier: just always assume some common graphics mode. Say 1024x768 with 256 colors.

Interestingly, the default palette has far fewer than 256 colors. (Pic: 1024 cols each contain color `col%256`.)


*2020: Flood-filling the Mu computer*

A year ago I had a prototype of a C-level programming language mapping 1:1 to Assembly that I _thought_ could be type-safe.

Since then, I:

* wrote an academic paper on it
* made it type-safe
* began a high-level language atop it
* got into video, with 15 2-minute screencasts
* and ran programs written in it on bare metal, without an OS, like, 5 years before I expected to.

❀️ to everyone who inspired, taught, debated, encouraged.


*Rendering text atop baremetal*

Mu can now render text atop baremetal x86.

Try clicking around from akkartik.github.io/mu/html/bar

The boot-up machine code reads a few sectors from disk, configures a keyboard handler, and loads a bitmap font (2KB for ASCII, with the option for more).

I use GNU Unifont. I believe that means Mu is now GPL v2. So stated. IANAL and I try not to think about software IP. But a font? Copyright seems reasonable there.

Next up: a text editor!


*A more international interface for rendering text*

New 2-minute video: archive.org/details/akkartik-2

You get just one fixed screen resolution: 1024x768, 256 colors. Widely available on modern machines, no drivers needed.

You get just one fixed-width bitmap font. No bold/italics, no anti-aliasing.

BUT it won't make assumptions about English and left-to-right order. I eventually want anybody to be able to customize it to their language.

Main project page: github.com/akkartik/mu

I've been trying to visualize the default 256-color palette I get on baremetal.


To my eyes it looks like I can/should just live in the first 128 colors.

I built a game of "snakes", but it came out more like an etch-a-sketch πŸ˜„


Play it on any non-windows:

git clone github.com/akkartik/mu
cd mu
./translate_mu_baremetal_emulated baremetal/ex7.mu
qemu-system-i386 disk.img

h/j/k/l to draw

It took a while, but I've finally ported a pre-existing Mu program to baremetal: an RPN calculator.

This was _hard_, purely because of cursor management. I have a greater appreciation for everything that display hardware and terminal emulators provide for text mode. Mu so far puts the onus on the programmer.


In the end it's interesting to visualize the changes I had to make:

vim -d apps/rpn.mu baremetal/rpn.mu

They're entirely in `main`; the rest is unchanged.

I've been reimplementing my from-scratch live-updating postfix shell to _really_ from scratch (no more OS kernel), while at the same time rewriting the prototype with lots of tests and actually giving the language some sort of rigorous basis. No demos yet, but in the meantime here's Conway's Game of Life running on baremetal Mu.


Sources: akkartik.github.io/mu/html/bar

Main project page: github.com/akkartik/mu

The Mu shell is now off Linux


Starting point for the sources: akkartik.github.io/mu/html/bar

The architecture is now much cleaner. Functions contain lines, lines contain words, words contain gap buffers. Rendering a thing renders its constituent things. Render takes a top-left coordinate and returns a bottom-right coordinate. Each thing knows which constituent thing has its cursor, shows its cursor when rendering, redirects incoming keystrokes to it.


An experimental way to do control flow in the postfix Mu shell

The screenshot below shows an idea I've been playing with.

The conventional way Forth does control flow is a little confusing with words like `if` and `then` showing up at the end.

Factor uses quotations to put code blocks on the stack. But then you see a potentially complex chunk of code executing "all at once".

Main project page: github.com/akkartik/mu


Drilling into computations on the Mu shell

This was one of the more difficult things I've built, and yet all I've gotten working so far is some rudimentary tokenization. The reason is one little feature.. well, take a look for yourself.

archive.org/details/akkartik-2 (video; 2 mins)

As always, built all the way up from machine code, and designed primarily to be easy to build, easy to run, comprehensible to others. Also this time with lots of tests


Β· Β· Web Β· 1 Β· 1 Β· 1

Today was documentation day

Primitives available in the Mu computer when running without an OS: github.com/akkartik/mu/blob/ma

Primitives available when running on Linux: github.com/akkartik/mu/blob/ma

For starters I focused just on making things more discoverable. These files are optimized for opening in your text editor, jumping to definitions to see type signatures, etc. See github.com/akkartik/mu/blob/ma for a ctags configuration for Mu and SubX programs.

Main project page: github.com/akkartik/mu

Mu can now read from an ATA (IDE) disk drive on Qemu.


It wouldn't have been possible without the lovely folks over on . And the inspiration of ColorForth (merveilles.town/@akkartik/1059), though I still don't understand how that driver works.

The Mu computer now has drivers for disk and mouse.

Still extremely klunky. IDE disk drives only, and the mouse driver uses polling because configuring IRQ 12 is still beyond me.

Example programs (as usual memory safe and translating 1:1 to x86 machine code)

Disk: akkartik.github.io/mu/html/ex9

Mouse: akkartik.github.io/mu/html/ex1

Here's video of the mouse example. There's no pointer so you have to imagine me moving the mouse around.

Like I said. Klunky.

Main project page: github.com/akkartik/mu

What should the signature of a program look like?

Typed languages have a fixed signature for function `main`. A list of strings, a window context, or an IO monad.

Here's the signature on the Mu computer:

fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk)

A rudimentary, hokey capability system. No mouse yet. 'screen' is only used for text; pixel graphics currently go around it. 'data-disk' can't access code, and will eventually include finer-grained restrictions.

Mu's HLL is now Turing-complete, I think.

Things to notice:
* Wordstar-style menu at the bottom.
* List of available primitive functions in bottom left.
* List of globals on the left side that updates as I add definitions.
* Matching parens highlighted as I type.
* Drilling down into the trace to understand how the program was evaluated.


Main project page: github.com/akkartik/mu

Writing code _within_ the Mu computer

I tried and failed to implement Bresenham's line algo. Horizontal and vertical lines work.

No macros => lots of lambdas.

The computer keeps crashing because I type too fast (still can't brain interrupt handlers). It frequently saves to disk, but as an s-expression. Reboots lose indentation.

Ok, edit on host -> create disk image.

I have lots of little buffers. When they overflow the computer crashes. Without a call stack.

Thanks @tekknolagi for pairing!

The bug wasn't in Mu or in Bresenham. I just typoed when I edited the disk image πŸ˜‚

Still lots of catastrophic bugs that require editing the disk image. The disk is clobbered on reboot because I added support for reading multiple sectors from disk but still only write one sector.

Prototyping on the Mu computer

A 3-minute video showing what it's currently like to prototype programs on the Mu computer. There are lots of limitations. It's slow, and it can only handle short runs.

While these limitations will be relaxed over time, the goal is partly to nudge people to throw the prototype away once they know what they want, and rewrite it one level down. Therefore: encourage people to write lots of tests.


Main project page: github.com/akkartik/mu

I suck at colors. Anybody wanna help me paint this bikeshed for the mind?

Constraint: I only have 256 colors.


The Mu computer now has Lisp macros

Here's the Bresenham algorithm for drawing circles using a few macros. My current style is to keep line width to 41 characters and lay out two columns of functions.

* No nested backquotes yet.
* I can't draw circles too far down the screen due to a strange error.
* In general, error messages have been a mess ever since I stopped relying on Linux. A heavy exercise in humility.

Main project page: github.com/akkartik/mu

Playing with the kids on a Saturday morning

(Much faster on Linux, thanks to -enable-kvm.)

git clone github.com/akkartik/mu

cd mu

dd if=/dev/zero of=data.img count=20160

dd of=data.img conv=notrunc < shell/data.limg

./translate shell/*.mu # gen code.img

qemu-system-i386 -enable-kvm -m 2G -hda code.img -hdb data.img

The Mu computer now dumps a snapshot of the call stack when it fails catastrophically

This was not fun. And the debug information in the second half of the code disk is now larger than the code itself.


On the other hand, I hope debugging will now be more fun!

@neauoire πŸ˜‚

I've been thinking of it as mulisp. Or maybe even just Lambda as the layer above Mu?

@akkartik I've been thinking about something, did you watch the talk s-ol shared about smalltalk running on top of lisp, from yesterday?

It has gotten me thinking about implementing a Uxn assembler in Uxn itself, so I could write/assemble uxn programs from within the system.

Do you have something like this in Mu? Is lisp going to be that intermediary language for you? Any suggestions/lessons on this topic you could share?

@neauoire Uxn assembler in Uxn sounds eminently doable! Now I'm interested.

A full Smalltalk/Lisp like in that talk is a _lot_ of work. The final complexity discourages looking under the hood. Which rubs me the wrong way. Maybe you too.

The very notion of 'language' tries to pretend there's nothing underneath, which might be counter-productive.

So I'm still feeling my way with Mulisp. Maybe it's going to stay a toy, a gateway drug for kids to get into the level below.

@akkartik fair enough, that's a good answer. I'm looking forward to see how it evolves then :) Good luck with mulisp ✊

@neauoire I got super interested in building languages and parsers in grad school. The idea of building one language in another (or itself) is like catnip for us programmers. Like with religion or the Singularity, we all have an escapist urge to work at higher levels. But higher level languages don't actually seem to reduce mess. The mess isn't out there, it follows us. HLLs just increase CO2 emissions.

HLLs are good for prototyping. Once that's done, shrink dependencies. Like you already do.

@akkartik yeah I agree, I always thought, well, why don't people make assembly languages that are fun enough to use that there's no need to build on top of it. Maybe there are problems there I haven't encountered yet.

Lisp as the first language above bytecode sounds fun enough that no other language needs to be build on top.

@neauoire Seems worth trying, anyway πŸ™‚ I had some ideas for removing parens and adding infix from a previous Lisp project: akkartik.name/post/wart. I'm planning to pull them in here for like a quick calculator mode.

I honestly don't think there's much for you to learn from "experienced" programmers. Trust your instincts ✊ The rest of us don't know what we're talking about.

Rereading your original question, did you want to build Uxn on mulisp or Lisp? That'd also be a neat prototype.

@akkartik No no, my original question was.. I was wondering if you had considered implement mu in mu itself, I'm curious to see if you're planning on writing mu from within mu itself, when you started working on a lisp a few days back it got me wondering what you needed metaprogramming for. But you answered that well saying it was just a fun direction to take this to.

@neauoire For now the only thing I'm planning for it is exploring the trace UI and how useful it seems. No metaprogramming for now. I'm not even sure of macros at the moment.

@akkartik @neauoire This looks a bit similar to i-expressions, sweet expressions or wisp; Were you aware of them or did you come up with your whitespace mechanism independently?

@clacke I hung out on the readable mailing list, so yes to sweet-exprs and wisp. I'm not sure about i-expressions. But yeah, I found all those to be a little too radical: news.ycombinator.com/item?id=8. Here's the original impetus for my infix: arclanguage.org/item?id=16699. Compare Wart: arclanguage.org/item?id=16776

The whitespace-sensitive infix sprung forth from my own forehead, but I've since learned that it was also in a language called merd (arclanguage.org/item?id=16724)


@neauoire @akkartik do you mean "assembly" languages for actual hardware? or bytecode? the answer for the former seems obvious to me: since an assembler is meant to reflect accurately and completely on the hardware capabilities, the creation of the "fun" is in the hardware, and perhaps in the choices in representing the hardware words/macros- And the way these become not fun is through backwards compatibility, and "natural" growth trying to satisfy shifting performance requirements...

@neauoire @akkartik I used to have a lot of fun programming z80 assembler, essentially an ancestor of x86. But modern x86 processors have a lot of stuff in them that can only be effectively utilised by compilers- hand rolling is just not an option, and those choices were all done for performance.

@neauoire @akkartik performance and-- bakcwards compatiblity with weird memory models.

@zens @akkartik I meant assembly for hardware, I meant it to say that fun should be built in the hardware itself I guess, from the very foundation.

@neauoire @akkartik Sure, though,, you do probably have to sacrifice *something* if that's the thing you want to optimise for.

Worth noting, is many ARM processors provide a region in their ISA kinda analogous to the "private use" area of unicode, where you can define your own opcodes, and, in a way, create your own, or really, customise, the arm assembler.

the Xerox Alto had a similar system, which was a lot of how they built up smalltalk: by iteratively defining custom opcodes they needed

@neauoire @akkartik I mean, you're still defining the OPCODES in terms of some base opcodes. aka "microcode", but that's how most of x86's ISA is defined anyhow.. If all the bytecodes had their own silicon you'd probably need a wafer the size of a flat screen television.

@neauoire @akkartik In xerox and arm cases they're like.. kinda like interrupt handlers. Really kinda just… fancy jp instructions that need fewer bytes. So it might be a little disappointing, if you want to be fully custom- but this is basically how those "java" processors work, they're just ARM processors with some custom opcodes that emulate java vm bytecode.

@neauoire @akkartik (need to work harder to get rid of "basically" and "simply" from my vocabulary)

@neauoire @akkartik The other day I watched a video of the ARM system architect, explaining the rationale for designing the ARM RISC processor. There isn't a lot of philosophical or practical difference between microcode and just plain instructions on a restricted set- so if you make the ISA *just* the microcode, and move "custom instructions" to the compiler, you can make a faster more efficient CPU.

In theory. In practice, code size does make a difference to performance on slow memory.

@neauoire @akkartik but, it reminded me, in this now overly long daydream of a reply thread… Novel CPU architectures could be a fun thing to design. Move thinking beyond the single spool of tape model to all the different kinds of things a programmable computer *could* be. fun puzzle.

@zens @akkartik yeah that's what I meant in the first post :) I hope I can inspire a few people to design some unique and interesting VMs, maybe some of those will end up in FPGAs

@neauoire @akkartik assembler language: musical chords-
bytecode generated from an FFT and executed in realtime.

@neauoire @akkartik how do you storeyour program? just record the song.
how do you execute it faster? fast forward.

Show newer
@zens @neauoire @akkartik I find the VLIW processors like Itanium and Transmeta interesting, as they remove even out-of-order execution from the CPU and put it in the compiler.

Must be terrible and/or a fantastic challenge for a person choosing to write artisanal machine code. =)

@clacke That brings back memories of grad school. At least as of 10 years ago, moving instruction-level-parallelism discovery into the compiler was a losing strategy.

@neauoire @zens

@akkartik @neauoire @zens Lo and behold, Transmeta and "Itanic" are dead. =)

But they're interesting! Failures may be more interesting that successes. The success of x86_64 certainly is one of the most boring yet significant events in the last 30 years of computing.

it's all fun and games until your anonymous functions become a couple lines long ;)

although at least with lisp its trivial to print only e.g. one form deep to keep it short

@s_ol The hope is that anything with this visualization will be easier than without it. It admits skipping past the long forms to drill down to the critical places, which could be short.

Though pretty printing is also an option. It's fairly well-understood for Lisp (though not by me πŸ™‚)

@vertigo A lot of it makes sense, and the comments are super useful. One question I had was about the definitions of more foundational words used in the initial fragment: 1f3 a! swap

Based on colorforth.github.io/forth.htm, it looks like the stack is in eax and *esi, while the A register is in edx. a! stores 0x1f3 in edx. swap now seems to expand to these instructions:

mov EDX, EAX;
mov EAX, [ESI];
mov [ESI], EDX

This is a swap, but it also seems to clobber edx?

@akkartik @vertigo that does destroy the value in EDX.

In the original colorForth, EDX is used as a scratch register as well as the A register. You need to be careful when using it. (I know that some later variants from others have changed the implementation of swap to avoid this.)

@akkartik you want help picking colours from that given palette? or you want to pick which 256 colours should make it up?

@zens Good question! I'd like to stick with this palette for now please. I'm trying to configure as little on the hardware as possible, to minimize how much I have to debug on real hardware down the road.

@akkartik Solarized color scheme is a subset of the xterm 256 color pallette. I'm a big solarized dark fan (except maybe the green)

@alcinnz @zachdecook Thank you for suggesting Solarized! I just pushed an approximation of it: github.com/akkartik/mu/commit/

Unfortunately the default 256-color palette BIOS provides isn't the same as xterm's. Perhaps I should investigate switching it. The default isn't great: merveilles.town/@akkartik/1055

In the meantime this is definitely an improvement.

There might be a better color combination of backgrounds on the left. I plan for functions to order independently like in Tiddlywiki.

@alcinnz @zachdecook I initially wanted to write a Mu function that would return the closest available color given an RGB triple, after computing HSL distances. For now I made a small change to akkartik.github.io/mu/html/vga to edit the left column of squares in a browser's inspector to compare.

I found myself zooming way in to be able to tell subtle differences. So this is an ugly hack. The Mu function would be handy.

Sign in to participate in the conversation

Revel in the marvels of the universe. We are a collective of forward-thinking individuals who strive to better ourselves and our surroundings through constant creation. We express ourselves through music, art, games, and writing. We also put great value in play. A warm welcome to any like-minded people who feel these ideals resonate with them.