Adding anyref support in a C++ to WebAssembly compiler
WebAssembly in its current MVP form is shipped by all major browsers and is already capable of amazing things. This does not mean that its development has concluded: on the contrary, there are many post-MVP feature proposals at different stages of development.
One such proposal is about adding the so-called
externref type (and related instructions) to the specification. But what is
anyref, and why is it desirable to add it to WebAssembly?
The current specifications of
anyref are quite limiting, and in particular, they don’t allow to store these references in linear memory directly. They need to be stored in a table at an index, and then the index itself can be stored in linear memory. Doing this requires some runtime bookkeeping of tables, to “allocate” and “deallocate” slots and give out indices.
anyref support directly in languages like C/C++ and Rust (i.e. directly representing a DOM object as
anyref) is not straightforward, and the current mainstream opinion seems to be to use
anyref under the hood in autogenerated glue code while keeping the actual user-written code dealing with just indices (see here for an interesting thread discussing this).
There are some limitations: while linear memory objects (declared with the
cheerp::genericjs attribute) cannot be accessed by code compiled to WebAssembly. This is enforced by our compiler frontend.
Implementation of anyref support in Cheerp
As an experimental prototype of
anyref support, we decided to only allow classes defined in the special
client namespace to be used in WebAssembly functions: these classes have no layout on the C++ side, only methods. Special methods starting with
Since these objects can only be handled by pointer (the actual type is effectively opaque since only the declaration is visible), it is easier to reason about them, and they map perfectly to the current capabilities of
So how does this look like in practice?
The most basic feature we can enable with
anyrefis to allow pointer of types in the client namespace to be passed to WebAssembly function as arguments (and returned as well):
This can be handy, but just passing around pointers without being able to call methods is not too useful. There is currently no support for calling methods on
anyref as a first argument and forward the method call:
Now we can pretty much do anything with objects in the client namespace directly from WebAssembly!
But what if we want to use global objects, like console or document?
For now, we support limited access to global client objects: we only allow
extern declaration of object types (not pointers), and we implement access to them by generating wrapper functions that return the desired global as
anyref. This is read-only access, but it is enough to make console, document, etc work:
The main limitation that remains is that we have no way of storing anyref values anywhere (from pure WebAssembly code at least, we can always resort to using a
cheerp::genericjs function), so we can only handle them for the duration of the function call.
This is annoying, but with future expanded support for globals (using real WebAssembly globals instead of wrapper functions) and exposed access to WebAssembly tables we plan to improve the situation.
(IMPORTANT NOTE: you need a browser with
anyref support in order to try the game. For Chrome, run it with
You can find the repo with the source code here.
anyref feature looks very promising for Cheerp, and we plan to eventually expand its use to all
P.S: The official name of the feature seems to have recently changed from
externref (see this commit).