A javascript stanc3

I was playing around with stanc3 and js_of_ocaml (a compiler that compiles arbitrary ocaml code into javascript) and managed to make something that works with nodejs with basically the following commands, once I had nodejs installed and stanc3 was building locally:

opam install js_of_ocaml-compiler
dune build --profile=release src/stanc/stanc.bc.js

Amazingly well integrated with dune! To run it,

hal ~/scm/stanc3 (master) $ time node _build/default/src/stanc/stanc.bc.js  test/integration/good/code-gen/mother.stan 

Warning: deprecated language construct used in 'test/integration/good/code-gen/mother.stan', line 60, column 21:
   -------------------------------------------------
    58:  
    59:    void unit_normal_lp(real u) {
    60:      increment_log_prob(normal_log(u,0,1));
                              ^
    61:      u ~ uniform(-100,100);
    62:    }
   -------------------------------------------------

increment_log_prob(...); is deprecated and will be removed in the future. Use target += ...; instead.


Warning: deprecated language construct used in 'test/integration/good/code-gen/mother.stan', line 184, column 20:
   -------------------------------------------------
   182:  
   183:    real foo_lp(real x) {
   184:      return x + get_lp();
                             ^
   185:    }
   186:  
   -------------------------------------------------

get_lp() function is deprecated. It will be removed in a future release. Use target() instead.


real	0m1.817s
user	0m2.194s
sys	0m0.066s

Takes about 2s to transpile the (huge, intentionally egregious) mother model to C++ (compared with 0.081s for the native executable). Eight schools takes 0.871s with nodejs.

Given that there is a v8 port for R that runs Javascript, maybe this is a much quicker route to getting something up and running in RStan with stanc3? @bgoodri

Also this would make it easy to run the compiler and any future linters right in someone’s browser without having any server set up, which is kind of neat.

6 Likes

This seems promising. I had been aiming toward using

http://caml.inria.fr/pub/docs/manual-ocaml/intfc.html#s%3Aembedded-code

but Javascript would work as well as a C wrapper. My guess is @ariddell and @ahartikainen would agree?

I think Python is fine with standalone static binary executable, right?

Also worth noting the js_of_ocaml folks claim that the js version is often faster than the bytecode version :)

The V8 package seems good: https://cran.r-project.org/web/packages/V8/vignettes/v8_intro.html but apparently it requires libv8-dev to be installed… Why can’t we do the same thing for OCaml or Stan on CRAN with a helper to install it locally?

The guy who maintains the V8 R package is also the guy who maintains RTools, so he has already got Google’s v8 library onto CRAN for building binaries. Users only have to install Google’s v8 library on Linux, which is straightforward but differs slightly from one package manager to the next. So, we could bundle it or have a helper function to download it on Linux or just provide instructions, but it is not a big deal either way.

Getting the OCaml libraries built is probably possible too, but is more of a challenge on Windows than v8 was.

You mean getting them built and installed on CRAN Windows build machines?

Another consideration here - if we come out with a Stan REPL or interpreter-mode, that will link against C code and I doubt either of these methods will work for distribution. But at that point maybe we just release another package.

Just in case, I added stanc.js builds to Jenkins: https://github.com/stan-dev/stanc3/releases/tag/nightly

What I was saying in the meeting

library(V8)
ctx <- v8()
ctx$source("https://github.com/stan-dev/stanc3/releases/download/nightly/stanc.js")

Error in context_eval(join(src), private$context) :
Please specify one model_file.
Usage: stanc [option] … <model_file.stan>
…

1 Like

Good point, I need to add a javascript-specific endpoint for this to be used not-from-nodejs if we’re serious about going this route. Should I go ahead and do that? Shouldn’t be more than a couple hours work at most.

Sounds good

Okay, that took like twice as long as I thought it would - turns out you need to convert from and to JavaScript specific string representations at the API boundaries, but not for ints or numbers. Makes sense in retrospect; doc is totally absent on this use case.

Anyway, want to give this a try? https://github.com/stan-dev/stanc3/releases/download/nightly/stanc.js

I would if I knew how to call it.

library(V8)
ctx <- v8()
ctx$source("https://github.com/stan-dev/stanc3/releases/download/nightly/stanc.js")

seems to work now, but I don’t know where to go from there. Something like

> ctx$call("stanc", "foo.stan", "foo.cpp")

seems like it should work but says

Error in context_eval(join(src), private$context) : 0,248,Failure,-3,

Woops. try ctx$call("stanc", "model_name", "data {}")

Same error

If I jump into the JavaScript interpreter with

ctx$consolse()

then

This is V8 version 3.14.5.9. Press ESC or CTRL+C to exit.
~ stanc "foo" "data {}"

yields

SyntaxError: Unexpected string

If I create a new ctx, source, and then use ctx$eval("stanc('mn', 'data {}')") it works for me… not sure why call doesn’t or what the best way of using this V8 package is :/

You meant to type stanc("foo", "data {}")

OK. We can do ctx$eval rather than ctx$call. Can it read a file off the disk or do we need to read it from the disk in R and pass it to stanc as a string? Also, how do you specify additional arguments to the (old) stanc like --allow-undefined?

The previous version that works with nodejs can read files but that’s because node presents additional APIs for OS stuff that vanilla JS doesn’t. So it looks like in this V8-in-R context we have to communicate back and forth via strings :/

Hm, I can build in whichever ones of those you need as an additional flags dict/obj thingy. Which ones do you use?