Skip to content

Building a Minimalistic Virtual Machine

February 24, 2023

Just over a year ago, I wrote a blog post about a topic that’s very important to me, which is the problem of code rot, of software constantly breaking because of shifting foundations, and the toll it takes on programmers, and on society at large. We’re no doubt collectively wasting billions of dollars and millions of human hours every year because of broken software that should never have been broken in the first place My own belief is that stability is undervalued. In order to build robust, reliable software, it’s important to be able to build such software on stable foundations.

One of the potential solutions I outlined last year is that if we could build a simple Virtual Machine (VM) with no dynamic linking and a small set of minimalistic APIs that remain stable over time, it would make it a lot easier to build software without worrying about becoming randomly broken by changing APIs or dependencies. Such a VM wouldn’t necessarily meet everyone’s needs for every possible use case, but it could help a lot of us build software for the long term.

This is something I’ve been thinking about for at least two years, but in all honesty, I didn’t really dare even get started on this project, because I felt scared of the amount of work it represented. I was also scared of potentially getting a fairly negative reception with lots of criticism and cynicism. Last December though, I decided that, well, fuck it, I’m really interested in working on this project, I keep thinking about it, so I’m doing it. I’m well aware that the concept of a VM is not a new idea, and I’m sure that some people are going to tell me that I’m basically reinventing WASM, or that I should base my system on an existing processor architecture and work on something like Justine Tunney’s blink instead. I’ll elaborate a bit on why I’m taking a different approach.

WASM is trying to be a universal binary format and satisfy many different use cases in very different areas. Because it has so many stakeholders, it evolves very slowly. For instance, we were promised that WASM would have support for JIT compilers and include a garbage collectors 5 years ago, but this support still isn’t here today. At the same time, even though WASM is evolving relatively slowly, there are a ton of new features in the works, and it will surely become a very complex system. With so many stakeholders, the risk of massive feature creep is real.

In my opinion, the focus on minimalism is crucial for guaranteeing the longevity of both the VM itself and the software running on it. Exposing large, complex APIs to software running on the VM can become a liability. One of the biggest issue with modern web browsers, in my opinion, is that they’ve become so complex, it’s basically impossible to guarantee that your web app will behave the same on different browsers.

Working on my own web-based music app, I discovered that there are major differences in the way the pointer events API behaves on Chrome, Firefox and Safari. That’s kind of insane to even think about, because handling mouse clicks is something basic and fundamental that so many web apps rely on, and this API has been around at least as far back as 2016. Why can’t it behave the same in every browser? In my opinion, it’s in part because browsers are such hugely complex beasts that are growing in complexity so fast, that even tech giants can’t manage to have enough developers to implement all the APIs properly. If you don’t believe me, just take a look at this audio API bug that I ran into two years ago, that was reported 4 years ago, and still isn’t fixed at the time of this writing.

UVM has a fairly strict focus on minimalism, which will help keep the VM portable and maintainable. It’s also a lot smaller than other systems. It’s a simple untyped stack-machine. I made that choice because I want the VM to be both intuitive and easy to target. It’s not based on emulating an existing Instruction Set Architecture (ISA), because in my opinion, existing ISAs have a lot of quirks and a broad surface area (the ISA itself is a large, complex API). There’s also a phenomenon where if I said, for example, that UVM emulates an x86 CPU and Linux system calls, then people would expect UVM to support more and more x86 instructions, as well as more and more system calls. In order for the VM to maintain its strict focus on minimalism and simplicity, it has to be its own thing.

At this stage, UVM is just an interpreter with a tiny set of APIs. It’s still very immature and should be considered hobbyist-grade at best. That being said, some of its strengths is that it’s a small system that’s designed to be easy to understand. It’s important to me that software be approachable. It can run programs written in its own assembler syntax, but unlike other systems, the assembler doesn’t use some weird esoteric syntax, it uses a simple syntax based on NASM/YASM. If you’ve programmed in assembler before, and you understand how a stack machine works, then the syntax of UVM’s assembler should seem fairly intuitive to you.

I’m also in the process of building a toy C compiler that targets UVM. It’s currently lacking several features, but it already supports macros and enough C to be able to write something like a simple paint program, a snake game, and a few other simple graphics programs. UVM provides a frame buffer API that’s really easy to use. Just two function calls and you can plot pixels into a window, which makes the system fun to develop for as you can write a simple 2D game in something like 200 lines of C code without any boilerplate.

So here it is, UVM is currently in very early stages, and I don’t expect everyone to understand the purpose of this project, but I would really like to connect with people who share the vision, and find potential collaborators. If you’d like to know more, I recorded a short video demo and wrote a decent amount of documentation on GitHub, including some notes to explain the various technical decisions made in the design of UVM. There’s also automatically-generated documentation for the system calls that UVM exposes to software running on the VM. I’m also happy to answer any questions and to accept pull requests to improve the documentation.

In terms of what’s coming next, I want to improve the C compiler and I’d like to add an audio API. I could use some input on how to best design a simple file IO and networking API. Longer-term, I would also like to design a simple and safe parallelism model for UVM. Probably something based on actors, with or without the possibility of sharing memory, but parallel computation is not my area of expertise. I could honestly use some input. If UVM is something that’s interesting to you, feel free to reach out via GitHub issues and discussions or via twitter.

4 Comments
  1. Chris permalink

    This is a really interesting and fun project. It reminds me a little of uxn (https://100r.co/site/uxn.html) which has similar aims. I’ve seen people call this “permacomputing” which I love. Good luck with the project and thank you for sharing!

Trackbacks & Pingbacks

  1. Memory, Pages, mmap, and Linear Address Spaces | Pointers Gone Wild
  2. Preparing UVM for 3D Graphics | Pointers Gone Wild

Leave a comment