Skip to content

tristanwagner/rescript-compiler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OCamlScript

Introduction

OCamlScript is a Javascript backend for the OCaml language which aims to provide a better language for the Javascript platform.

One OCaml module is mapped to one JS module, and no name mangling happens so that:

  1. The stacktrace is preserved, the generated code is debuggable.
  2. You can call List.length (List is a module in OCaml standard library) in a plain Javascript file.

A simple example

let sum n =
    let v  = ref 0 in
    for i = 0 to n do
       v := !v + i
    done;
    !v

Generated code under is as below:

function sum(n) {
  var v = 0;
  for(var i = 0; i<= n; ++i){
    v += i;
  }
  return v;
}

As you can see, there is no name mangling in the generated code, so suppose the module is called M, you can call M.fib in vanilla Javascript

You can play the online in-browser compiler.

Disclaimer

This project is currently released to exchange ideas outside Bloomberg and collect some early feedback from OCaml and Javascript community, it is in an very early stage and not production ready for your own projects yet.

Build

Note that you have to clone this project with --recursive option, we can only distribute the patch of OCaml due to License restrictions.

Linux and Mac OS

  1. Apply the patch to OCaml compiler and build

Please ignore the warnings generated by git apply

cd ocaml
git apply ../js.diff
./configure -prefix `pwd`
make world.opt
make install
  1. Build OcamlScript Compiler

Assume that you have ocamlopt.opt in the PATH

cd ../jscomp
ocamlopt.opt -I +compiler-libs -I bin -c bin/compiler.mli bin/compiler.ml
ocamlopt.opt -g -linkall -o bin/ocamlscript -I +compiler-libs ocamlcommon.cmxa ocamlbytecomp.cmxa  bin/compiler.cmx main.cmx

Now you have a binary called ocamlscript under jscomp directory, put it in your PATH

  1. Build the runtime with ocamlscript
cd runtime
make all
  1. Build the standard library with ocamlscript
cd ../stdlib
make all 
  1. Test

We first create a file called hello.ml:

mkdir tmp  # create tmp directory inside the stdlib
cd tmp 
echo 'print_endline "hello world";;' >hello.ml

Then we compiled it with ocamlscript

OCAML_RAW_JS=1 ocamlscript -I . -I ../ -c hello.ml

It should generate a file called hello.js, then we runt the js file

node hello.js

If everything goes well, you will see hello world on your screen.

Licensing

The OCaml directory is a submodule from OCaml's official repo(4.02.3), all its rights are reserved by INRIA (see its QPL LICENSE for more details).

Our compiler relies on a patch (js.diff) to the OCaml compiler.

This project reused and adapted part of js_of_ocaml's:

It adapted two modules [jscomp/lam_pass_exits.ml] and [jscomp/lam_pass_lets_dce] from OCaml's [bytecomp/simplif], the main reasons are those optimizations are not optimal for Javascript backend.

[jscomp/js_main.ml] is adapted from [driver/main], it is not actually used, since currently we make this JS backend as a plugin instead, but it shows that it is easy to assemble a whole compler using OCaml compiler libraries and upon that we can add more compilation flags for JS backend.

[jscomp/stdlib] is copied from [ocaml/stdlib] to have it compiled with the new JS compiler.

Since our work is derivative work, we choose the GPL v2 license to make it compatible with js_of_ocaml.

Note that QPL license is not compatible with GPL V2, so we distribute our changes to the compiler as a patch instead.

Design goal

  1. Readability

  2. No name mangling

  3. Support Javascript modules systems

  4. Integrate with existing javascript ecosystem, for example, npm, webpack.

  5. Straight-forward FFI, generate tds file to target Typescript for better tooling

  6. Separate and extremely fast compilation.

  7. Better performance than hand-written Javascript: thanks to a sound type system in OCaml so that we can play more optimizations and pipe it to Google Closure Compiler for production mode.

  8. Smaller code than hand written JS code, compatible with Google Closure Compiler

  9. Support NodeJs, Web Browser and various Javascript target platform.

  10. Compatible with OCaml semantics modulo c-bindings and Obj, Marshal module

More examples

NodeJS support

Below it is an idea how it would integrate with the existing JS eco-system:

var $$Array = require('./array'); // OCaml Array module
var List = require ('./list'); // OCaml List module

List.iter(function(x){console.log('hi, nodejs '+x)},
    $$Array.to_list ($$Array.init(5,function(x){return x})))

You get the output:

hi, nodejs 0
hi, nodejs 1
hi, nodejs 2
hi, nodejs 3
hi, nodejs 4

A naive benchmark with Facebook immutable

Below is a contrived example to demonstrate our motivation, it tries to insert 1000,000 keys to an immutable map and query it

module IntMap = Map.Make(struct
  type t = int
  let compare (x : int) y = compare x y
  end)

let test () =
  let m = ref IntMap.empty in
  let count = 1000000 in
  for i = 0 to count do
    m := IntMap.add i i !m
  done;
  for i = 0 to count  do
    ignore (IntMap.find i !m )
  done

let () = test()

This is an equivalent JS version by using Facebook's immutable library

'use strict';
var Immutable = require('immutable');
var Map = Immutable.Map;
var m = new Map();
var test  = function(){
    var count  = 1000000
    for(var i = 0; i < count; ++i){
        m = m.set(i, i );
    }
    for(var j = 0; j < count ; ++j){
        m.get(j)
    }
}

test ()

Runtime performance:

  • OCaml Immutable Map: 1186ms
  • Facebook Immutable Map: 3415ms

Code Size:

  • OCaml (Prod mode): 899 Bytes
  • Facebook Immutable : 55.3K Bytes

Status

It covers the most of OCaml language, given that it is a quite young project (5 men-months until Jan 2016), there are still plenty of work to be done.

Some known issues are listed as below:

  1. Language features:

    Recursive modules, have not looked into it yet.

    Better Currying support. Currently, we have an inference engine for function curring and we do cross module inference, however, there are some more challenging cases, for example, high order functions, it can be resolved by either aggressive inlining or fall back to a slow path using Function.prototype.length. We prepared the runtime support in module curry, will support it in the near future.

    Int32 operations, currently, Int use Float operations, this should be fixed in the near future.

  2. Standard libraries distributed with OCaml:

    IO support, we have very limited support for Pervasives.print_endline and Pervasives.prerr_endline, it's non-trivial to preserve the same semantics of IO between OCaml and NodeJS, one solution is to functorize all IO operations. Functors are then inlined so there will no be performance cost or code size penalty.

    Bigarray, Unix, Num, Int64

License

The OCaml to JS compiler libraries are distributed under the GPL (version 2.0); see the LICENSE file at the top of the source tree for more information.

The contents of some files in this distribution was derived from external sources with different licenses. The original copyright and license notice was preserved in the affected files.

Question, Comments and Feedback

If you have questions, comments, suggestions for improvement or any other inquiries regarding this project, feel free to open an issue in the issue tracker.

About

The compiler for ReScript.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • ReScript 43.0%
  • OCaml 38.4%
  • JavaScript 13.2%
  • C++ 3.5%
  • Rust 1.0%
  • TypeScript 0.4%
  • Other 0.5%