Behind Microvium

Behind Microvium

Updated: June 2022

My latest project is Microvium, a tiny JavaScript engine for microcontrollers, designed to bring small pockets of scripting capability to an otherwise-C firmware (see also microvium.com and on GitHub). What makes Microvium unique is its tiny size and its use of snapshotting as a deployment mechanism.

The name “Microvium” is a portmanteau of “micro” (for microcontroller) and “VM” (for virtual machine).

I started the project in February 2020 and it reached its first usable release in June 2020. Development has continued since then to refine it and add more features.

Microvium’s “Big Idea”

Microvium is more than just another embeddable interpreter. It introduces a novel paradigm that I haven’t seen used this way in any other comparable engine.

The “big idea” with Microvium is that you don’t deploy the source code or a compiled form of the source code, you deploy a snapshot of the running Microvium virtual machine. At build time, the engine runs all of the root level module code (transitively including root-level code from imports) and then the resulting state of the virtual machine is saved to a snapshot file that you “restore” on the target device.

This seemingly-small principle has big consequences in many different ways:

1. No Configuration Files

It makes Microvium much easier to use because there are no configuration files to orchestrate compilation (e.g. project files, make files, manifest files, etc.). See Snapshotting vs Bundling for more detail.

2. Build-time Execution

With Microvium, all your root-level module code runs at build time with first-class access to build-time capabilities, enabling a whole world of possibilities:

  • Reading and executing other code modules (importing other JS files), as mentioned above.
  • Importing configuration data from the file system or database.
  • Pre-calculating expensive data such as lookup tables
  • Executing much of your initialization code at build time so that startup is instant at runtime.
  • Producing supplementary output files (as simple as using fs.writeFile). In particular, this includes the ability to automatically code-generate C code to deploy alongside the VM image, such as glue code for your custom host API.

And all of this functionality can be bundled into third-party libraries rather than being written by the user by hand or baked into the engine.

3. Accurate Global Optimization

The snapshotting paradigm allows Microvium to do whole-program optimization in a way that’s not easy in general with bundlers and AOT JavaScript compilers, because it has an accurate object graph of the full program (bundlers, by contrast, must use some guesswork to determine what modules are included and can never have perfect knowledge, as discussed in Snapshotting vs Bundling). This gives a certain amount of tree-shaking (see MDN) for free since Microvium can just do a garbage collection cycle before the snapshot is captured, to clean up any unreachable code or objects in the object graph (e.g. all the startup/initialization code is generally not used at runtime and can be garbage collected).

Beyond this, there is an experimental and closed-source project called Microvium Boost that performs whole-program static analysis on the snapshot itself and is able to accurately determine what things like:

  • What variables, properties, and parameters are used or not used in the app?
  • What state in the snapshot can be kept in ROM vs what needs to be stored in RAM?

It does this by a completely novel technique where it maintains a symbolic representation of the virtual machine and runs the runtime code symbolically. See Microvium Boost – It’s Like Magic.

For advanced readers: the runtime machine is non-deterministic (from the perspective of a build-time analysis algorithm) because the input IO is not known at build time. However, the runtime machine can still be executed deterministically at build-time similarly to how an NFA can be converted to a DFA (see Powerset Construction on Wikipedia), where the non-deterministic state of the machine can be represented as the deterministic powerset of possible runtime states. The trick is in determining an efficient representation of the powerset since real machines can be in a practically-infinite number of possible states very quickly after only a few CPU instructions. This component of Microvium is closed-source and proprietary for the moment but I’m happy to discuss more details with anyone if they’re interested.

4. Easy Distributed Applications

Microvium snapshotting gives apps the ability to execute build-time code and then carry state to the runtime environment via the snapshot. This opens up the door for a new style of programming distributed systems which is much simpler and more robust than many approaches today. For example, a novel build-time host API could be designed which exposes methods to actually set up multiple runtime environments and resources (e.g. cloud infrastructure), and then the snapshot can be deployed to those multiple environments. A particular case might be a script that uses a build-time API to define an instance of a cloud-side microservice and database, and then deploy a snapshot of itself to both the IoT device and the microservice. See the unlisted page Distributed IoT Programs Using Microvium for an in-depth view.

Why create Microvium?

I had a client who’s using EmbedVM to introduce small, hot-swappable units of behavior to an existing C firmware. EmbedVM is indeed a very compact and portable VM for embedding many kinds of small scripts into a program, but it has an incredibly limited and non-standard scripting language. When using it to describe more complex behaviors, the resulting scripts are almost unmaintainable. A language like JavaScript would enable the scripts to be much more modular and maintainable.

I would have loved to have recommended that they incorporate a full JavaScript engine like that of XS, but the resource restrictions on the device were simply too constrained. This has continued to be the pattern everywhere I’ve worked as a firmware engineer: many real-world products are made with MCUs that are too small to fit most scripting engines, but yet their solution and development time could be greatly improved by having one.

Microvium is what I believe to be the exact solution for the problem at hand — a JavaScript engine that makes trade-offs in favor of compactness rather than language completeness, speed, or performance.

This was the original trigger that caused me to start writing Microvium, but it’s by no means the only situation where Microvium could be useful.

What about Microvium alternatives?

There are a number of alternative JavaScript engines on the market. But as a working firmware engineer in the IoT space, when I needed to find a JavaScript engine that ran on the devices I was developing for, there was nothing that would fit into the tiny space requirements.

Microvium makes different tradeoffs to achieve a smaller size than most (if not all) alternative JavaScript-like engines. Perhaps the most significant tradeoff is the 64kB memory limit — although Microvium runs fine on a host with more memory, the script will never be able to allocate more than 64kB of memory. See also: Microvium is very small.

The existing JavaScript engine that is most similar to Microvium in my opinion is Cesanta’s mJS, which has a similar design focus of fitting into a small memory footprint. However, Microvium matches most of the features of mJS in less than half the flash overhead and a small fraction of the RAM overhead. Variable slots in mJS are 4x bigger. But most importantly, mJS has no support for closures (nested functions) or snapshotting. No closures means no functional-style programming and no callback-based async programming. See here for a more detailed comparison.

Is it really JavaScript?

Yes and no. Average JavaScript code in the wild will not run on Microvium because the language subset is significantly restricted. This is true of all of the smallest JavaScript engines. The intention is that Microvium provides the most useful subset of JavaScript features.

Why is it open-source?

The reasons are mostly personal. I think Microvium would be almost as useful if it were closed-source, but by making it open-source, I don’t need to develop the project on my own in silence and isolation like I did with MetalScript. I much prefer being able to openly share ideas and collaborate.

Not only have I open-sourced it, but I’ve also used a very permissive license (MIT) for both the compiler and bytecode interpreter, making it easy for anyone to incorporate Microvium into other projects, without needing to worry much about legal issues, security issues, or what happens if Microvium stops being maintained.

(I might change this at some point to a dual-licensing model, where you can use it for free for non-commercial use but contribute financially to its development if you use it for commercial use)

What about MetalScript?

MetalScript is a JavaScript-to-machine-code compiler I’ve been working on, and is a much more ambitious project. Microvium does not replace the need for MetalScript, nor I have stopped working on MetalScript, but my focus is on Microvium for the time being.

Making money from Microvium

I’ve put a lot of time into Microvium and hope to make some money from it at some point. I’ll do a separate write-up sometime on the money side of things, but the general idea at the moment is that Microvium’s snapshotting capability facilitates a unique way of structuring distributed code, and I can capitalize on this capability by developing a platform I’m currently referring to as Microvium Cloud. This is not just another IoT cloud platform, but a completely unique way of developing code that is currently impossible with existing engines.

This is in the early stages of conception and development, but it opens a path to potentially making money. Let me know if you want to join me!

What’s planned for the future?

I’m intentionally trying to avoid committing formally or informally to any particular development path or schedule. It just adds unnecessary stress to my life. Just on a personal level, I’m starting to realize that always having my head in the future, on what-could-be-but-isn’t-yet, is detracting from the great things that already exist right now and putting me in a kind of psychological “debt” — a deep and unsatisfying sense that the current reality is always less than I want it to be.

Having said that, no doubt there will be many cool things coming to Microvium in the future — the list of possibilities of directions I could go with this seems endless. Subscribe to my blog to get updates on new developments.

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.