Open newtack opened 6 years ago
That's going to be a hard thing to do :) .
Yep, gluon threads (not to be confused with OS threads, gluon's are more like coroutines so perhaps I should rename them). If you omit threads there are still a lot of other problems.
Large parts of gluon's standard library is just Rust code called through gluon's FFI https://github.com/gluon-lang/gluon/blob/master/vm/src/primitives.rs . In theory you ought to be able to translate these Rust functions into WASM as well and simply call them from the gluon WASM though which would be a really cool solution for that problem.
Gluon has a GC but WASM does not. I guess this means that a GC needs to be generated in web assembly itself.
Gluon's type system may be a bit too advanced for WASM. Since WASM has no concept of generics, all type generic code needs to be monomorphized. The problem here is that it is possible to define functions such as this
let test f b c: (forall a . a -> a) -> b -> c -> () =
f b
f c
f ()
Here f
is applied to values of type b
, c
and ()
(and we could pass any other type to f
as well) thus it is not possible to monomorphize f
to make it work on only a single type. It is not impossible to generate WASM for the test
function though, but you have to muck around with tagged values which is going to be a bit awkward to implement in WASM.
There is probably more hard things to solve than the points I outlined above. If I think of any I will update the list.
Given all the hurdles and the fact that most problems to be solved do not benefit gluon as an embeddable language I am not looking to implement a WASM backed (or any other assembly-like) backend. It would certainly be an interesting project though so I am open to helping out if anyone is interested in attempting this (or making a LLVM backend for that matter, that might be even more useful). While I have only been listing problems so far there is one thing that should help a lot when writing a backend. The compiler provides an extremely small Intermediate Representation (IR) as output after all typechecking is done which should be very easy to translate into WASM, LLVM-IR, assembly etc https://github.com/gluon-lang/gluon/blob/483dfef2cc1e2ff58383713d26430bd39245c979/vm/src/core/mod.rs#L57-L101 . So it is just the runtime that is problematic 🙄
Thanks for your fast response.
Regarding monomorphization, I don't understand what problems WASM presents that wouldn't already be an issue with Rust. When compiling to native Rust binaries, you already have to monomorphize everything. How is WASM different?
@Storyyeller A function like this
let test f b c: (forall a . a -> a) -> b -> c -> () =
f b
f c
f ()
is impossible to write in Rust but you can do it in gluon. If you think about it, the argument f
is a function that needs to be able to take ANY type. The only general way to handle this is to make sure that all values have the same representation which would basically make all gluon values be a tagged union (and this is indeed the same representation that the current interpreter uses).
This uniform representation means some extra work when generating WASM/LLVM-IR/etc but it shouldn't be excessively bad. Having this extra tag might also make it possible to skip out on generating stack-maps for the garbage collector since this means that all values know if they are heap allocated or not.
That said, even though tagged values makes this possible, there is a tradeoff. To tag each value we either need an extra integer for each value store the tag (which costs memory and some speed), or we need to pack the tag into the value itself, sacrificing (fast) i64/u64
(no cost in memory but maybe a slightly larger speed loss).
Thinking about it, these kinds of functions that can't be monomorphized should be pretty uncommon. It might be possible to just return an error if a function that can't be monomorphized is encountered (to start with) and still be able to compile most real-world gluon code.
If/when it becomes necessary to compile these functions one could then generate extra code to tag and untag any values passed to and from these function. It makes the compiler more complex but it should be doable.
I understand that higher ranked types can't be efficiently compiled to native code. My question is why this is more of a problem for WASM than it is for Rust. It seems to me like the issues should be the same either way.
The problem here is the same for WASM/Rust/LLVM-IR/assembly . The only reason it is not a problem for Rust is that that Rust's type system do not allow these kinds of functions to be written. If Rust's type system where extended to support it then it would have the same problem.
(Rust RFC for Higher-ranked-types https://github.com/rust-lang/rfcs/issues/1481 )
I know that. I meant why is it not a problem when you're running the Gluon VM in Rust? Surely running Gluon in Rust and running it in WASM should be equivalent?
@Storyyeller Are you talking about compiling the gluon interpreter that is now written in Rust into WASM using rustc's WASM support or are you talking about compiling gluon code into WASM?
If it is the former then I don't think there is anything preventing that. The only platform specific code that is needed is for the REPL (or possibly in one of gluon's dependencies such as tokio).
What I wrote about above has only been about the latter, ie adding another back end which is capable emitting WASM (or LLVM-IR etc) from the gluon compiler itself instead of the custom bytecode that the current interpreter uses https://github.com/gluon-lang/gluon/blob/0909139a2a80389a56d6546e921aef9e52abf9cc/vm/src/types.rs#L17-L117
Oh, I thought you were talking about the first one. Sorry for the confusion.
@Storyyeller No worries 😆 .
Out of curiosity I tried compiling gluon with WASM. Currently it stops compilation due to https://crates.io/crates/iovec not having an implementation for WASM which is needed for tokio-core
. There are probably more platform specific things in tokio-core
(mio
for instance) even if that is fixed however so the best way to let gluon compile to WASM (using rustc/cargo) would be to make tokio-core
optional which should be possible at the cost of not being able to run all async code (only the futures
crate may be used).
@Storyyeller I made tokio_core
optional now so gluon actually compiles to WASM now (about 3.5 Mb). Still needs some boilerplate to deal with the pointers used in the exported C API but in theory everything should work.
Any news?
Not really, you can still compile the rust code in the gluon
crate to WASM https://travis-ci.org/gluon-lang/gluon/jobs/340155173 and run the interpreter in WASM.
For compiling gluon code directly to WASM I did a small investigation on using https://github.com/bytecodealliance/cranelift to JIT compile but i didn't take it further than compiling functions that only operate directly on integers/floats (no closures, records, indirect function calls etc etc).
@Marwes It's not working, I'm getting this runtime error when using gluon in wasm. Any idea why? :)
gluon = { version = "0.18", default-features = false, features = ["random"] }
nightly-2022-01-31
Using trunk
to build.
EDIT: I get the same error when not using the random
feature.
Also, another aspect of Wasm support would be that if a gluon script prints to stdout/stderr, it won't work. Is there a way to get all printed output from the script through a std channel or something? So that the host can display this output in appropriate ways (e.g. as part of the wasm UI, or log it to the browser console, or send it to the backend and print it to stdout/stderr there). How to do this? :)
What I do in my own (non-gluon) project is this: `// A macro to provide println!(..)-style syntax for console.log logging.
macro_rules! log { ( $( $t:tt ) ) => { web_sys::console::log_1(&format!( $( $t ) ).into()) } } ` This means all log! prints to web console.
@Zireael07 Sure, but it can't be used to redirect the output of gluon scripts.
@Storyyeller A function like this
let test f b c: (forall a . a -> a) -> b -> c -> () = f b f c f ()
is impossible to write in Rust but you can do it in gluon. If you think about it, the argument
f
is a function that needs to be able to take ANY type. The only general way to handle this is to make sure that all values have the same representation which would basically make all gluon values be a tagged union (and this is indeed the same representation that the current interpreter uses).This uniform representation means some extra work when generating WASM/LLVM-IR/etc but it shouldn't be excessively bad. Having this extra tag might also make it possible to skip out on generating stack-maps for the garbage collector since this means that all values know if they are heap allocated or not.
That said, even though tagged values makes this possible, there is a tradeoff. To tag each value we either need an extra integer for each value store the tag (which costs memory and some speed), or we need to pack the tag into the value itself, sacrificing (fast)
i64/u64
(no cost in memory but maybe a slightly larger speed loss).
The OCaml's runtime also has a uniform value representation, so it seems gluon program can be compiled to OCaml's IR. If so then we can easily build a gluon compiler and interpreter, by utilizing an IR like Malfunction, which is actually a thin abstraction of OCaml compiler's lambda IR. What do you think?
I want to use gluon in WebAssembly. Is this possible now (doubtful since I saw that threads are used and WASM doesn't support threads yet) and if not what would need to change to make it support WASM?