Behind Microvium

Behind Microvium

Updated: Aug 2021

Microvium is 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).

I’ve created this page to informally explain some behind-the-scenes points about the development of Microvium — things that probably don’t belong in the repository or formal documentation, but which nevertheless may be interesting to some people.

I’ll treat my normal blog posts as essentially an immutable history of things I’ve been doing (Microvium or otherwise), while I’ll try to instead keep this page updated with the latest information1.

How old is Microvium?

The project started in February 2020 and 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 all your initialization code at build time so that startup is instant at runtime.
    • Producing supplimentary 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 application (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 us 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, 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, and 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-determinisic (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 a in a practically-infininte 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 have 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 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-ish engine that makes trade-offs in favor of compactness rather than language-completeness, speed, or performance. And if it works for this client, it will likely work for other people who face a similar problem.

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.

Some engines make a tradeoff with the standard library, such as with the function Array.filter. Generally, the choice with engine design is either to favor completeness (i.e. include Array.filter in the engine), or compactness (i.e. leave Array.filter out of the engine). The intended future roadmap for Microvium is to get the best of both worlds by including Array.filter in the user-space bytecode image if it is used but omitting it if it is not used, so the standard library will only cost MCU resources on a pay-as-you-go basis (and likewise for any third-party libraries). This will be facilitated by a component called Microvium Boost which enables the accurate elimination (“tree shaking”) of unused parts of the scripted application.

How Small? (ROM/RAM)

Expect these numbers to change a bit over time.

  • The engine currently uses about 12 kB of flash space2. More features are being added, so consider that this number may go up.
  • A bytecode image has a fixed overhead of 32 B of ROM, including things like the built-in CRC, and then care has been taken to keep the bytecode small.
  • Each instance of a instantiated VM has a fixed overhead of about 22 B of RAM while idle and an additional 14 B RAM for the allocation of the virtual registers when running (when the host calls into the VM), plus the size of the virtual call stack (customizable but defaults to 256 B).
  • Beyond that, the usage depends on how big your script is and what it does.
  • Microvium uses a 16-bit slot size, meaning that every variable has an associated 16-bit slot in memory. This 16-bit slot can directly hold a 14-bit integer, a boolean, or various other special values such as null or undefined (see here for details). Contrast this to some other engines which may use a 64-bit or 128-bit slot size and so some variables are 4x or 8x more expensive.

See also memory-usage.md

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, especially at this early stage of release. There are also some edge cases where a supported feature is implemented with slightly different semantics for performance reasons. For the intended use cases at this stage, this will rarely be a problem: the majority of working Microvium scripts will run on a full JavaScript engine with exactly the same behavior as they have on Microvium.

Why is it open-source?

The reasons are mostly personal. I think Microvium would be almost exactly 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.

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.


  1. But I’m not committing to actually remember to do so — just check the date at top of this post to know long it’s been since I’ve looked at this post 

  2. Measured as compiled for an MSP430X architecture