Inform 6 says "Hello, WebAssembly!"

An asciicast is worth a thousand words, so click the box below.

asciicast

WebAssembly is a new VM designed for security, rapid downloads and JITting, and native-like speeds. It was originally designed for web browsers but several standalone implementations now exist.

As a way to gain more experience with WebAssembly, I’ve been working on a WebAssembly target for the Inform 6 compiler.

What works so far:

  • Printing a string
  • Arithmetic
  • Bitwise operators
  • Logical operators
  • for, while, and do loops
    • break and continue
  • if and else
  • Routine calls
    • You can have any number of parameters as long as it’s two.
  • return
    • rtrue and rfalse

What doesn’t yet:

  • Most of the features of the print statement
  • Debug info
    • Inform-style debug information not tested and will need adapting
    • native web assembly debug information not implemented
  • Omitting unused functions
  • Pretty much everything past the first chapter of DM4
    • objects
    • classes
    • global variables
  • Huffman decompression
  • The jump statement

Some parts are stubbed out, including (most shamefully) the number of functions generated.

You can check out my progress at

11 Likes

Nifty!

Have you thought about how I/O will work, beyond printing to stdout? Or is that too far over the horizon?

3 Likes

Cool project, Nathan – I look forward to seeing its progress!

1 Like

I like this.

1 Like

I thought I’d share the approach I used for I/O on the online Quill Adventure player tool I developed recently for playing ZX Spectrum Quill games online (take a look at my website link through my user details if you’re interested).

The backend of this was developed in the language Rust, targetting WebAssembly. Although I initially handled I/O just as character streams, it quickly became apparent that I needed something a bit more. Even on the fairly simplistic games that the Quill generates, there were still features, such as sound, that couldn’t be as easily handled through a character stream.

The approach I took was for the back-end to generate a JSON array as a string. Each element of the array could contain a different action.

I could then generate a range of actions, including things such as:
FreeText - can just rendered to the front-end output device directly
Debug - any debug information that I did not want to render to the front-end
Pause - a command instructing the front-end to pause for a number of milliseconds
Save - an array of bytes containing the data to write to a save file
Beep - the pictch and duration of a sound

Admittedly, my Quill Player is an interpreter, not a compiler but you may be able to apply similar concepts to the I/O runtime supporting the compiled code.

The advantage of course of using a JSON format is that the front-end component can take those elements that it likes and ignore the rest. My Javascript-based web interface outputs the FreeText onto a display canvas, outputs the Debug into the Javascript console and when seeing Save allows the user to select a filename to save to. At the same time I’ve developed a command-line interface using the same back-end (handy as I can write and test things easily on the move on a mobile phone). This can take the same output format and render it appropriately to this interface. Both interfaces currently ignore the ‘Beep’ command, but in principle the web interface could use this to generate sound. In the future, this is easy to extend. I may at some point get around to extracting Illustrator-format graphics and then I can easily add these to the output actions. The front-end components will ignore actions that they don’t understand, so the command line interface would carry on working as usual and just ignore the graphics commands.

I’m not so familiar with generating WebAssembly from C. I’d recommend Rust to anyone that hasn’t tried it yet, although I appreciate that there is quite a learning curve and it’s not really as mainstream as C yet. But it’s got great support for JSON; just include the ‘serde’ library, annotate output data structures with the [Serialize] attribute and it will automatically generate JSON from them.

1 Like

I’ve thought a lot about it, which is paradoxically why it’s taken so long to reply. There’s a lot to summarize.

In the long term I’d like it to be pluggable, using the WebAssembly dynamic linking mechanism if possible. For the short term, I’ll just support WASI, which is an immature libc-like standard interface that is supported by the major standalone WebAssembly engines but not yet by any browsers.

The mid term is the most challenging. It’s not currently possible for WebAssembly running on the main browser thread to block or yield. There are several proposals in the pipeline to address this, but it’s impossible to say how long that will take or what it will look like.

Emscripten, the C/C++ compiler to WebAssembly, uses a few different techniques to work around this limitation. They’re all based on the fact that the WebAssembly module’s memory stays the same after the code is finished running and code can be called again, reminiscent of TSRs back in the DOS days. We can emulate what Emscripten does with some challenge, and may have to in order to implement saving anyway.

Implementing a Vorple-like system where the Inform code generates JavaScript to run is fairly straightforward. You provide a function for the code to import that just takes a string and evals it. The only complication is that there isn’t a standardized way to pass strings, so you have to take a pointer, fish the characters out of memory, and turn it into a JavaScript string yourself.

Similarly, it should be possible for the Inform-produced module to import an interface source compatible with the one used by Glulx Inform.

Being able to call browser APIs directly (instead of through JavaScript) is an eventual goal for WebAssembly. It will still take some work on the Inform side to enable that.

Overall, I think there is a lot of opportunity to be flexible both on the author and on the users’ sides.

Thanks for the detailed reply. This is obviously a complicated and evolving area.

For a GUI-based IF interpreter, you generally want the VM to run in a background thread while the UI runs on the main thread. Is that a sensible approach here?

(I get that there’s a lot of use cases that aren’t “compile an IF game and then play it”, but I’m focusing on that one for now.)

1 Like

It’s not a very popular strategy, but it is well-supported by browsers at this point, so it can be done.

I’d like to chip in a couple of points that might be helpful;

I’m currently building interpreters for webassembly via emscripten. I’m not generating the WASM directly, only via transpiled C/C++.

I have had the same problems with how to block the interpreter and other I/O issues:

  1. My architecture separates the GUI from the interpreter, usually by running the “back-end” in another thread. I could not get threads to work under emscripten. They are meant to work(?), but i had all sorts of problems and they didn’t work for me.

  2. I reworked my front/back architecture to optionally support co-op multi-tasking. ie co-routine yielding.

  3. Turns out emscripten supports co-routines as “fibers”. This worked well for me with WASM.

  4. emscripten comes with simple file IO that appears to live in the download “package”, not sure where writes end up. Browser storage??

  5. Initially, i had everything in the same package. Obviously this was bad for the initial download size. Especially if you have a selection of fonts and worse when you have sounds and pictures. Basically you need async fetching!

  6. This was fixed when i switched to “sokol” as my renderer interface where now i use the sokol “fetch” interface, which i think calls out to JS to provide async fetching IO. Now i can demand load fonts, pictures etc.

  7. sokol also fixed text input from mobile. I had a problem where it would not pop up the virtual keyboard, making it basically broken on mobile. I mention this as this is a common problem!

  8. My next battle is animation :slight_smile:

1 Like

I’ve found Asyncify works quite well for blocking the interpreter. In Emglken there’s only one actual async function, getc, but dozens or hundreds of other functions which call it also need to be handled. Emscripten takes care of that for you.

And it might be possible to run the Asyncify algorithm on wasm code generated by Rust?

Emglken also has a custom file system which then access GlkOte’s Dialog library. Writing custom file systems would be possible for other storage models. But if you’re writing your own IO with Rust then you could just do whatever you want, it would be even simpler than this.

1 Like

Thread support under Emscripten is getting better. Until fairly recently you had to enable experimental options in some browsers, but that’s not necessary anymore. Still, the architecture of the web means that most web API calls can only be done on the main thread, so your have to arrange for that to happen either in your C or JavaScript code.

That’s highly configurable, but the default configuration stores the files in memory, and everything is lost when you exit the browser. See File System API — Emscripten 3.1.53-git (dev) documentation for the gory details.

I wasn’t aware of sokal. It looks very nice.

Theoretically you can run binaryen’s standalone wasm-opt tool in asyncify mode on it. As a practical matter, how well that will work depends on whether binaryen produces the WebAssembly constructs used by the Rust compiler. I’ve never seen it have a problem with any WebAssembly 1.0 instructions, but newer ones can come out of wasm-opt severely suboptimal or wrong.

For example:

(The code in question is almost verbatim from an Inform 6 for loop.)

Of course, there’s nothing stopping the Rust compiler from implementing Asyncify itself. The algorithm is pretty straightforward.