Very WIP: [wasm] Bidirectional Julia/JS object access
This is a start at what I think is the last major feature on the wasm
porting punch list: sharing/accessing objects between Julia and the
native javascript environment. This PR currently contains a rough sketch
of the interpreter version of this integration, though we should of
course try to get the compiled version going as soon as possible, since
we now ship a compiled system image for the wasm target.
For the compiled version of this we'd like to use the reference-types
(https://github.com/WebAssembly/reference-types) wasm extension, which
is currently in development, so even the interpreter parts are written
with that extension in mind. In particular, we have a wasm table of
javascript objects that represents our (indexed) managed heap of
javascript objects. A boxed reference to javascript is then the julia
type tag, plus an index into this table (in compiled mode an unboxed
reference would just be a raw `anyref` without going through the table).
The GC is responsible for managing this table of javascript objects,
marking any JS objects that are reachable and sweeping the unreachable
ones by nulling out the references (this part isn't implemented yet).
On the julia side, we provide one julia type for every javascript builtin
type ('number' corresponds to Float64 and 'boolean' to Bool, but otherwise
the types are dedicated), with the non-singleton types being primitive
types of pointer size to store the index into the table of javascript
objects (at least semantically, as mentioned codegen will not use the
table if possible). There is a bit of logic in javascript to convert these
boxed representations on the julia heap into proper javascript objects,
but otherwise the julia code is responsible for conversions from Julia
objects to javascript objects (e.g. `String` to `JSString`,
or `Ptr` to `JSNumber` - i.e. Float64).
A `@jscall` macro is provided to call javascript functions from within
julia. Under the hood this lowers to an `Expr(:foreigncall, ...)` with
`jscall` calling convention for which we implement support in the
interpreter. I don't forsee any problems with codegen for this
representation, but that part is not implemented yet.
For the reverse (referencing julia objects from javascript), I mostly
followed what pyodide does and used a javascript `Proxy` object that
wraps a boxed julia pointer (set/getproperty aren't implemented yet,
but that should be straightforward). For GC integration, we make use
of the (experimental) [JavaScript WeakRef](https://github.com/tc39/proposal-weakrefs)
support, which essentially implements finalizers. On the julia side,
I stole one of the remaining GC bits (at least one of the bits
documented to be remaining - it looks like we don't overalign types
sufficiently for this to actually work at the moment) to mark an object
as being borrowed by the external VM and thus being protected from
collection even if there is no remaining julia references. The JS side
finalizer clears this bit on an object when there are no remaining
references on the JS side. As always with these kinds of systems,
reference cycles are not collected. I don't think there's much to be
done about that, until WebAssembly gets more native integration with
the JavaScript JC and we can re-use that for Julia objects.
It should be noted that the WeakRef proposal is curently behind a
flag in both Firefox and Chrome, but my understanding is that the
timeline for this fetaure is comparable to that of the reference types
proposal this builds on, so it should be ok to use (and I don't know
of any other mechanism to learn whether the JS side has been collected).
Remaining TODOs:
- [ ] Hook up get/setproperty on the JS side
- [ ] Julia GC marking/sweeping slots in the JS object table
- [ ] Codegen
For codegen, the major obstacle is lack of support in the toolchains
(llvm, emscripten). I'm thinking it should be possible to just represent
anyref as a non-integral pointer type in LLVM and get somewhere, but
plumbing everything through is still a decent amount of work, that I'm
unlikely to have the time for - I'm hoping somebody else will take that on.