WebAssemblyInterfaces.jl

Setup code to interface between JavaScript and Julia compiled to WebAssembly
Author tshort
Popularity
24 Stars
Updated Last
10 Months Ago
Started In
April 2023

WebAssemblyInterfaces

Build Status

NOTE: This is still experimental, and not all features have been tested with WebAssembly.

For a working example, see this Lorenz Attraction App in Julia.

This is a small package to write out definitions in JavaScript that correspond to Julia types and object definitions. This JavaScript code is meant to be used with the wasm-ffi package, a great package for interfacing between JavaScript and WebAssembly. This allows JavaScript to read and write to memory that is shared by the Julia code (after being compiled to WebAssembly). The wasm-ffi package writes to the same memory layout used by Julia.

The following types are supported:

  • Structs, tuples, named tuples
  • Concrete types that include: Bool, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float32, and Float64

Functions and other types that don't have a size are not written. For vectors, the MallocVector type from StaticTools works with the ffi.rust.vector type in wasm-ffi. The memory layouts do not match exactly, but it works for some uses.

wasm-ffi performs allocations when objects are created on the JavaScript side. It is also possible to do allocation on the Julia side. The WebAssembly file needs to include allocate and free functions.

Three functions are provided:

  • js_types(T): Return a string with the JavaScript definition of type T.
  • js_def(x): Return a string with the JavaScript code to define object x.
  • js_repr(x): Return a string with the JavaScript code with the types and the code to define x.

Here is an example of Julia code that defines a custom type and generates JavaScript interfacing code.

mutable struct X{A,B,C}
    a::A
    b::B
    c::C
end

struct Y{A,B,C}
    a::A
    b::B
    c::C
end

x = X(2, Y(1.1, 2, (1, 1.1)), Y(1, 2, 3))

using WebAssemblyInterfaces

print(js_repr(x))

Here is the JavaScript code that is printed:

const Y = new ffi.Struct({
    a: 'f64',
    b: 'int64',
    c: ffi.rust.tuple(['int64','f64']),
});

const YInt64_Int64_Int64 = new ffi.Struct({
    a: 'int64',
    b: 'int64',
    c: 'int64',
});

const X = new ffi.Struct({
    a: 'int64',
    b: Y,
    c: YInt64_Int64_Int64,
});

new X({
a: 2,
b: new Y({
a: 1.1,
b: 2,
c: new ffi.rust.tuple(['int64','f64'], [1, 1.1]),
}),
c: new YInt64_Int64_Int64({
a: 1,
b: 2,
c: 3,
}),
})

Here is a Julia function that could operate on this object. This can be compiled with StaticCompiler. The Julia code can read data from the object passed in, and it can write to this object in memory.

function f(x)
    x.a = x.b[2] * x.c[3]
    return x.c[1] + x.b.c[1]
end

# Compile it to WebAssembly:
using StaticCompiler
wasm_path = compile_wasm(f, Tuple{typeof(x)}, flags = `walloc.o`)

wasm-ffi

This repository also contains distribution code for the wasm-ffi package from this fork. This includes extensions for supporting Julia code. That includes:

  • ffi.julia.Array64
  • ffi.julia.Array32 (not done, yet)
  • ffi.julia.MallocArray64
  • ffi.julia.MallocArray32 (not done, yet)

The 32 and 64 indicate whether the pointers and integers involved are 32 or 64 bits. Match these to the version of Julia you are using. It also includes ffi.types.pointer64 for use on 64-bit versions of Julia in place of ffi.types.pointer.

Options going forward

  • We include a copy of wasm-ffi.browser.js. Can we make it easier to use?

  • Figure out where walloc.o should live. Should we add object code from other sources to make WebAssembly easier?

  • We could create and package a set of method overrides for StaticCompiler that are targeted at WebAssembly. We could also develop Mixtape passes to be able to compile more code.