Microvium has Classes!
TL;DR: Microvium now has support for classes. Here are some details for the curious.
I’ve been putting off adding support for classes in Microvium because you can do a lot without them, and other features have been more important in my opinion. I’m also personally more of a fan of functional-style programming rather than object-orientated programming, so I seldom write classes myself.
But I kept running into little things that are difficult to do without support for classes. With the recent addition of try-catch, for example, you could now throw
, but you couldn’t throw new Error(...)
because Error
is a class. Another example is the ECMA-419 API for embedded systems, which is an API standard for things like GPIO, but which relies heavily on support for classes.
Microvium now has support for classes, which I think makes it the only JavaScript engine under 100kB of flash that has all the features required to implement ECMA-419 (and Microvium is currently under 10kB at the moment).
Microvium functions are not objects
Classes in JavaScript are more or less just syntactic sugar for old-style prototype-based code. For example, the following are more or less equivalent in normal JavaScript (but the prototype style doesn’t work in Microvium):
// ES6 class style class Foo { bar() { console.log('Method bar called') } } // Old prototype-style function Foo() {} Foo.prototype.bar = function() { console.log('Method bar called') }
Both styles are setting up the Foo.prototype
object, but with the class
syntax, it is implicit. Then if you evaluate new Foo()
, the engine will create a new object whose __protype__
is Foo.prototype
.
This is all possible because functions in JavaScript are also objects, and can hold properties like any other object.
But Microvium functions do not support properties, so the function Foo
cannot have a prototype
property (or any other static class properties like Foo.myStaticProperty
).
I made the choice to omit this feature from the engine because this pattern is relatively uncommon outside of classes. It would be a massive waste of RAM to have every function also be an object. A closure function
in Microvium currently uses only uses 6 bytes of RAM, and most functions will use no RAM at all if the static analysis determines them not to be closures (e.g. all top-level functions). Whereas a pair of normal objects like { prototype: {} }
is already 16 bytes of RAM (6 bytes for each object plus 4 bytes for each property). It would be madness to require that every function declaration uses 16 bytes of RAM just in case someone wants to use it as a constructor.
So how do classes work in Microvium?
The way it works is quite simple: when you use the class
syntax, Microvium creates a function
that supports properties (i.e. “a class”), but when you use the normal function
syntax, it will keep its old behavior.
A class in Microvium is a distinct type. Although typeof MyClass
will return "function"
, as per the JS spec, if you use the C function mvm_typeOf
to probe the type, it will tell you VM_T_CLASS
not VM_T_FUNCTION
.
Internally, a class is a tuple of a props
object and constructor
function, as shown in the following diagram. When you access the class like an object (e.g. MyClass.myStaticProperty
or MyClass.prototype
), it delegates the property-access operation to the props
object.

When you construct an instance of the class using new MyClass()
, it creates a new object whose prototype (__proto__
) is props.prototype
, and then calls the constructor
function with the new object as the this
parameter.
The constructor function itself could just be a bytecode function in ROM, but it can, in general, be any function because it delegates the call to the normal calling machinery of Microvium. For example, if you have a class declaration nested inside a function then the constructor will be a closure, with access to the locals of the surrounding function. Or the constructor can be an imported host function. But the simplest case is a class declared at the top level of the code, in which case the constructor
part of the tuple just points directly to a function in the bytecode ROM image.
The key-value pair in the above diagram (where the key is "prototype"
) is how Microvium implements object properties. Each property is a 4-byte pair containing a 2-byte property key and a 2-byte value. Here I’m only assuming one property (MyClass.prototype
) but other properties would follow contiguously if there were more.
The next
field is unimportant for this example. When you add properties to an object, they’re actually added using a linked list rather than resizing the object structure (for CPU efficiency) but then the garbage collector compacts these linked lists into a contiguous form (for memory efficiency).
Classes and objects are expensive
Classes (and objects) are one of the more expensive features of Microvium. The above minimal/empty class is already 22 bytes of RAM. While each instance of the class is only 6 bytes of RAM, every property on an instance is an additional 4 bytes. So an object with just 2 properties is already 14 bytes.
Property lookup is also quite expensive:
- It is linear-time because it has to search through the property list.
- Many different things support property lookup, such as arrays, classes, buffers, and of course objects. The lookup code needs to figure out what it is in order to know how to find its properties.
- Properties use string keys. For hard-coded literal strings, like
x.y
orx['y']
, these strings don’t incur RAM overhead but they do add to the bytecode size. - For computed string properties like
x['a' + 'b']
, there is additional overhead to perform string interning — string interning is the process of consolidating different strings with the same content so that they also have the same reference identity, which makes property lookups more efficient. - String interning can potentially trigger a garbage collection cycle because it’s growing the intern table. Apart from the overhead of the collection itself, just the possibility of a garbage collection means that the implementation of property lookup needs to deal with the fact that all the memory may be shuffled around during the property access (e.g. all the temporaries need to be properly GC-reachable), which itself adds overhead.
- A property lookup instruction in bytecode involves multiple steps consuming at least 5 bytes of bytecode:
- Loading the object from which to get the property (a 1-byte instruction, at least)
- Loading the string that represents the property (typically a 3-byte instruction because it embeds a reference to the string)
- An instruction to actually trigger the property loading (a 1-byte instruction)
Closures, on the other hand, are asymptotically more efficient. A closure that closes over 2 variables is 14 bytes — the same size as an object with 2 properties. But:
- Each additional variable closed over is another 2 bytes, rather than the 4 bytes of a property.
- Closure variable access is typically
O(1)
because closures are random-access structures and the indexes are computed at compile time by static analysis (see my post on closure variable indexing). - Up to 15 different closure variables can be accessed by a single 1-byte instruction, compared to a minimum of 5 bytes for the instruction sequence for object property access.
Can objects be more efficient?
The experimental static analysis technique I designed for Microvium Boost a while back computes all the information that would be required to convert an object to a fixed-length array, in cases where it’s possible to infer the closed set of property keys that are ever used to access an object. My hope is that in the future I could implement this optimization (transforming objects and classes into fixed-length arrays) which could have some significant performance benefits:
- An object with 2 properties would be 6 bytes instead of 14.
- Each additional object property would take another 2 bytes.
- Property lookup would be
O(1)
. - Up to 15 different properties could be accessed using a single-byte instruction.
Conclusion
As it stands, classes are not a feature that I would want to use very often since they are so expensive. But having classes opens the door to different kinds of libraries and APIs, and in particular, is a stepping-stone towards implementing other standards-compliant features in Microvium.