Julia in the Browser

Keno Fischer, Tom Short, Simon Danisch

There is a strong push to move everything to the web. There are many reasons for that: You don't need to install anything, there are powerful web frameworks and people want to show the results of their research online - as visual and interactive as possible. Especially the last part is important for Julia - a new scientific programming Language!

Julia can't fully run in the browser right now, but this article explains the different approaches and shows how close we are. This article is not only aimed at developers wanting to work on this, but also for interested users that want to learn about possible future directions.

1.
Approaches

1.1.
WebIO + JSExpr

JSExpr is the only option that works of today - but it's also the most restricted approach. It only offers execution of very simple Julia functions defined with the @js macro, or execution Javascript strings, but together with WebIO which manages the execution and communication, it already enables quite a lot of fun use cases:

# The bundled Javascript library that allows to execute Julia code usually gets served from a Julia server that WebIO starts. That works fine if you edit this article + run the Julia code, but after one publishes an article, the runner shuts down and so the server shuts down. An easy fix is to get the bundle from an online source:
ENV["WEBIO_BUNDLE_URL"] = "https://rawgit.com/JuliaGizmos/WebIO.jl/master/packages/generic-http-provider/dist/generic-http.js"
using WebSockets, WebIO
using WebIO, JSExpr
# import a javascript library
w = Scope(imports=["//cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.11/p5.js"])
# Define a Julia function with the @js macro, that will get translated to javascript
onimport(w, @js function (p5)
    function sketch(s)
        s.setup = () -> s.createCanvas(640, 200)

        s.draw = function ()
          if s.mouseIsPressed
            s.fill(0); s.stroke(255)
          else
            s.fill(255); s.stroke(0)
          end
          s.ellipse(s.mouseX, s.mouseY, 20, 20)
        end
    end
    @new p5(sketch, this.dom.querySelector("#container"))
end)

w(dom"div#container"())

Advantages & Disadvantages:

+ works today

+ already has a framework build around it to execute Javascript code from within Julia and communicate with it

- only works for a small subset of Julia

To compile already defined / more complex Julia functions, one needs more sophisticated approaches! Enter the world of compiling Julia to WebAssembly (Wasm) - the new assembly language running in the browser!

1.2.

All in with Wasm

Completely compile the Julia compiler to Wasm. This includes all of LLVM, the JIT and Julia's runtime. This way, everything will "just work™". A simple strategy, but needs lots of work in practise. There is some ongoing work to add wasm32-unknown-unknown and wasm32-unknown-emscripten targets to BinaryBuilder, which may make it easier to port libraries (including LLVM) to Wasm.

+ lets any Julia program run in the browser

+ performance should be good

- will likely produce a pretty big WebAssembly binary

- all Julia dependencies need to be compiled to WebAssembly, which is quite a lot of work

Instructions on how to compile Julia to Wasm are presented at the end of the article. In fact, it's directly set up in the articles runtime, and the resulting docker container can be downloaded here (this URL can be looked up for any Nextjournal article in the runner menu to the right).

1.3.

Half & Half

Compile Julia LLVM IR (Intermediate Representation) to Wasm via emscripten, seperately compile the Julia runtime to Wasm, and then link them together to enable calling into Julia's C runtime! A working prototype for this exists in ExportWebassembly.jl.

+ no need to compile everything to WebAssembly

+ clear separation between WebAssembly backend + Julia backend

+ will work nicely with WebIO to only compile selected callbacks

- cannot JIT code anymore, so code needs to be fully static and ahead of time compilable

1.4.
Pure Julia

Write a compiler that translates Julia's typed inferred IR to webassembly. This option would allow you to transpile dynamic subgraphs to Javascript (which has a pretty impressive performance for dynamic code), and compile the static, type-inferred parts to webassembly.

+ relatively simple with pure Julia tools & no dependencies

+ could yield decent performance for most cases

+ flexible on the compiler side, since one has full control of emitted code

- can't work naturally with C calls (only via emitting different calls)

- more prone to compiler bugs + wrong results

- likely less optimized WebAssembly. Julia's IR is not very optimized since LLVM usually does the difficult optimizations for Julia, so removing LLVM from the equation will also remove lots of optimizations.

This is implemented in Charlotte.jl (sadly Julia 0.6 only - but there is a branch for Julia 1.0).

2.
Compiling Julia to Wasm

2.1.
Start a Julia Webserver

To run the generated Wasm in this article we need to start a webserver. Bash can't actually reach the outside of a Nextjournal container, so we need to run the webserver in a Julia code cell! We also need to start it before compiling, because we need to get the proxy URL, to compile the wasm with a source map.

2.2.
Setup & Compilation

For now we just partially build Julia with emcc to Wasm, to get a REPL running. Downloading and compiling Julia is done here and frozen in a container, so that we never need to repeat that step. From here on, we can start compiling Julia's C dependencies to Wasm:

cd julia
make -C deps/ clean-utf8proc
make -C deps/ install-utf8proc
make -C src
#Now we can use emcc to link the program and generate javascript + html to #load our WebAssembly binary. Now that we have the server running, we can also reference the proxy url for the source map:
ref=nil
url=$(cat $ref)
emcc -Isrc/support -Lusr/lib -ljulia ui/repl-wasm.c --preload-file base/boot.jl --no-heap-copy --source-map-base $url -g4 -s -s WASM=1 -s ASSERTIONS=1 -s ALLOW_MEMORY_GROWTH=1 -s ERROR_ON_UNDEFINED_SYMBOLS=0 -o hello.html
cd julia
make clean
make -C deps/ clean-utf8proc
make -C deps/ install-utf8proc
touch src/julia_flisp.boot
touch src/julia_flisp.boot.inc
make -C src

Display the html we generate & host (will show 404 when article is not in edit mode):

HTML("""
<iframe src=$(repr(url)) style="width:100%;height=800px" frameborder="0">
</iframe>
""")

At this point, the WebAssembly code was started in the browser, but it failed. The failure is likely caused by using the wrong femto lisp version, since it was compiled from a different branch. I didn't get the correct branch (kf/wasm) to build right now. Anyone interested should try to build the kf/wasm branch and see how much further we can push it!