Porting a Linux Program to Run in Browser using Emscripten
2023 December 21

On my infinitely long list of things I wanted to implement one of them was taking a binary and stuffing it into a webpage to run entirely client side. Today I have accomplished just that with my new Atari 2600 IDE. This allows you to write an assembly program entirely in browser and run it without setting up any kind of toolchain on your own machine. The following are my notes on getting emscripten, a way to compile c/c++ to run in browser using llvm and wasm, to generate a binary and interfacing that binary with some vanilla Javascript.

What I Built

In order to write programs for the Atari 2600, you must first set up an assembler and text editor. The recommended tool seems to be dasm, a command line assembler for 6502 assembly. This is what we’re going to compile to run entirely in browser. However, this program has some issues that interfere with emscripten. It uses a lot of global variables, and was never really meant to run without being reset.

I was inspired by Retro Game Mechanics Explained to make an easier way to write code to run on these vintage systems. I always wanted to write code for these vintage systems, and now I can with my little IDE I built using emscripten. The image is clickable, and this program will only run on Chrome on desktop. I haven’t tested it any where else.

The Atari 2600 IDE

Special thanks to the Javatari project for making a fantastic easy to use and embed emulator.

Emscripten Setup

The first step is grab the emsdk tool and set it up. We are not doing advanced setup so just follow the defaults. The commands I used are listed below:

git clone https://github.com/emscripten-core/emsdk
cd emsdk
./emsdk install latest
./emsdk activate latest
source emsdk_env.sh # For bash users, other env files are generated.

This gives us a new C compiler and a couple other tools to use. We’re only interested in compiling a C program, so just export CC=emcc to fix it for most makefiles you’ll encounter in the wild. There’s other things to help with complex programs that use automake and etc.

Compiling Your Program

Next you’ll have to probably modify the makefile at the linker stage. This is after all the object files have been built, and now they’re being strung together. Various flags can be passed to emcc, to make the runtime do what you want. You might want to bundle some local files with the binary or run the binary multiple times inside of your program. You’ll also probably have to export some functions for your use. I’ve listed the flags I set below, and why as a definition list. The following all use the -s flag and argument like such.

EXPORTED_FUNCTIONS=_main
This allows the main function from our program to be exported and callable.
EXPORTED_RUNTIME_METHODS=FS,callMain
We need to use the FS and callMain functions on the returned object. FS is the file system use by emscripten. It’s a fake virtual thing that is not persisted. We can add things to it using the -embed-file path flag during linking. callMain allows actually executing the thing.
EXIT_RUNTIME=1
This causes the runtime to properly terminate instead of persisting. DASM uses a lot of global state, and this isn’t cleaned up fully at the end of execution. Which is the right way to write command line programs. The operating system will take care of it for you.
MODULARIZE
This allows us to get a factory function to handle creating our runtime repeatably.
EXPORT_NAME="createDasmUtil"
What the factory function should be called.
INVOKE_RUN=0
By default when you create a runtime it will be executed automatically as soon as it’s created with no arguments to main. We don’t want that to happen since we need to callMain with real arguments.

Once we have configured our flags in the linker line, we are ready to go. We’ll build the program with make and then copy over the wasm and associated javascript file to our website. Now comes the fun part with integrating into the html.

Integrating into the Web Page

Now it’s time to write some JavaScript and explain how everything works. You could probably use a bundler here, but I did everything with vanilla JS to keep it as simple as humanly possible. I spent a lot of time using the console interactively to figure everything out.

First load your friendly emscripten wrapper script..

<script src="your_wasm.js"></script>
<script src="my_integration.js"></script>

Next, remember what you used as your EXPORT_NAME, we’ll use what I defined above as an example. We’ll also make the function be an event handler for invoking our program. You can attach it however you would like.

async function invokeMyProgram() {
    let dasmBinary = await createDasmUtil({
        "print": function (text) {
            let output = text + '\n';
            document.getElementById('output').innerText += output;
        },
        "printErr": function (text) {
            let output = 'Error: ' + text + '\n';
            document.getElementById('output').innerText += output;
        }
    });
    // setup and inject whatever you need to in the file system here
    dasmBinary.FS.writeFile('inputFile.txt', 'my program input here');

    // actually invoke main
    // rc is our return code exactly like it works in a shell.
    // 0 indicates success anything else is an error.
    let rc = dasmBinary.callMain(['inputFile.txt', 'myArg2', '-oYourOutputFile.txt']);

    // do something with the file output and pass it where it needs to go
    // stuff is going to be a raw uint8array
    let stuff = dasmBinary.FS.readFile("YourOutputFile.txt");
}

As we can see it’s not that much boilerplate to actually invoke our C code. We just need to be careful with what we pass around, and how we operate on these byte buffers.

The filesystem is strictly in memory, but there are ways to override and persist it that I’m not going to cover since it wasn’t needed for my project. The file system can easily be viewed with instance.FS.readdir(path), this is your best ls alternative here. I highly recommend understanding where your program is running from and what are the relevant paths. At the end of the day, its the same troubles you might have with docker. What is my current working directory, and did I copy everything in place where it needs to go?

Wrapping Up

Hopefully you found this to be an enlightening introduction to emscripten, and hopefully it helps you setup and compile your own programs to run on the web with it. It’s quite neat that we can take some fairly sophisticated programs and open them up to the general internet without any server interaction. I have other projects I want to work on with this stuff including getting other old school programs to run in browser and be interactive. I also want to make some physics simulations available here. Especially the stuff I worked on at NIST, as it would be really cool to open that up to the general public.


Remember you can also subscribe using RSS at the top of the page!

Share this on → Mastodon Twitter LinkedIn Reddit

A selected list of related posts that you might enjoy:

*****
Written by Henry J Schmale on 2023 December 21
Hit Counter