|
| 1 | +# OCamlScript |
| 2 | + |
| 3 | +## Introduction |
| 4 | +OCamlScript is a Javascript backend for [the OCaml language](https://ocaml.org/) |
| 5 | +which aims to provide a better language for the Javascript platform. |
| 6 | + |
| 7 | +One OCaml module is mapped to one JS module, and no name mangling happens |
| 8 | +so that: |
| 9 | + |
| 10 | +1. The stacktrace is preserved, the generated code is debuggable. |
| 11 | +2. You can call `List.length` (List is a module in OCaml standard library) |
| 12 | + in a plain Javascript file. |
| 13 | + |
| 14 | + |
| 15 | +### A simple example |
| 16 | + |
| 17 | +``` ocaml |
| 18 | +let sum n = |
| 19 | + let v = ref 0 in |
| 20 | + for i = 0 to n do |
| 21 | + v := !v + i |
| 22 | + done; |
| 23 | + !v |
| 24 | +``` |
| 25 | + |
| 26 | +Generated code under is as below: |
| 27 | + |
| 28 | +``` js |
| 29 | +function sum(n) { |
| 30 | + var v = 0; |
| 31 | + for(var i = 0; i<= n; ++i){ |
| 32 | + v += i; |
| 33 | + } |
| 34 | + return v; |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +As you can see, there is no name mangling in the generated code, so suppose the |
| 39 | +module is called `M`, you can call `M.fib` in vanilla Javascript |
| 40 | + |
| 41 | +You can play the online [in-browser compiler](http://zhanghongbo.me/js-demo). |
| 42 | + |
| 43 | + |
| 44 | + |
| 45 | +# Disclaimer |
| 46 | + |
| 47 | +This project is currently released to exchange ideas outside |
| 48 | +Bloomberg and collect some early feedback from OCaml and Javascript community, |
| 49 | +it is in an *very early* stage and not production ready |
| 50 | +for your own projects *yet*. |
| 51 | + |
| 52 | + |
| 53 | +## Build |
| 54 | + |
| 55 | +Note that you have to clone this project with `--recursive` option, we can only distribute |
| 56 | +the patch of OCaml due to License restrictions. |
| 57 | + |
| 58 | + |
| 59 | +### Linux and Mac OS |
| 60 | + |
| 61 | + |
| 62 | +1. Apply the patch to OCaml compiler and build |
| 63 | + |
| 64 | + Please ignore the warnings generated by git apply |
| 65 | + |
| 66 | + ``` |
| 67 | + cd ocaml |
| 68 | + git apply ../js.diff |
| 69 | + ./configure -prefix `pwd` |
| 70 | + make world.opt |
| 71 | + make install |
| 72 | + ``` |
| 73 | +2. Build OcamlScript Compiler |
| 74 | + |
| 75 | + Assume that you have ocamlopt.opt in the PATH |
| 76 | + ``` |
| 77 | + cd ../jscomp |
| 78 | + ocamlopt.opt -I +compiler-libs -I bin -c bin/compiler.mli bin/compiler.ml |
| 79 | + ocamlopt.opt -g -linkall -o bin/ocamlscript -I +compiler-libs ocamlcommon.cmxa ocamlbytecomp.cmxa bin/compiler.cmx main.cmx |
| 80 | + ``` |
| 81 | + Now you have a binary called `ocamlscript` under `jscomp` directory, |
| 82 | + put it in your `PATH` |
| 83 | + |
| 84 | +3. Build the runtime with `ocamlscript` |
| 85 | + |
| 86 | + ```sh |
| 87 | + cd runtime |
| 88 | + make all |
| 89 | + ``` |
| 90 | + |
| 91 | +4. Build the standard library with `ocamlscript` |
| 92 | + |
| 93 | + ```sh |
| 94 | + cd ../stdlib |
| 95 | + make all |
| 96 | + ``` |
| 97 | +5. Test |
| 98 | + |
| 99 | + We first create a file called `hello.ml`: |
| 100 | + |
| 101 | + ```sh |
| 102 | + mkdir tmp # create tmp directory inside the stdlib |
| 103 | + cd tmp |
| 104 | + echo 'print_endline "hello world";;' >hello.ml |
| 105 | + ``` |
| 106 | + Then we compiled it with `ocamlscript` |
| 107 | + ```sh |
| 108 | + OCAML_RAW_JS=1 ocamlscript -I . -I ../ -c hello.ml |
| 109 | + ``` |
| 110 | + |
| 111 | + It should generate a file called `hello.js`, then we runt the `js` file |
| 112 | + |
| 113 | + ```sh |
| 114 | + node hello.js |
| 115 | + ``` |
| 116 | + If everything goes well, you will see `hello world` on your screen. |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | +# Licensing |
| 122 | + |
| 123 | +The [OCaml](./ocaml) directory is a submodule from OCaml's official repo(4.02.3), all its rights |
| 124 | +are reserved by [INRIA](http://www.inria.fr/) (see its QPL LICENSE for more details). |
| 125 | + |
| 126 | +Our compiler relies on a patch [(js.diff)](./js.diff) to the OCaml compiler. |
| 127 | + |
| 128 | +This project reused and adapted part of [js_of_ocaml](https://github.com/ocsigen/js_of_ocaml)'s: |
| 129 | +* Some small printing utilties in [pretty printer](./jscomp/js_dump.ml). |
| 130 | +* Part of the [Javascript runtime](./jscomp/runtime) support |
| 131 | + |
| 132 | +It adapted two modules [jscomp/lam_pass_exits.ml] and |
| 133 | +[jscomp/lam_pass_lets_dce] from OCaml's [bytecomp/simplif], the main |
| 134 | +reasons are those optimizations are not optimal for Javascript |
| 135 | +backend. |
| 136 | + |
| 137 | +[jscomp/js_main.ml] is adapted from [driver/main], it is not actually |
| 138 | +used, since currently we make this JS backend as a plugin instead, but |
| 139 | +it shows that it is easy to assemble a whole compler using OCaml |
| 140 | +compiler libraries and upon that we can add more compilation flags for |
| 141 | +JS backend. |
| 142 | + |
| 143 | +[jscomp/stdlib] is copied from [ocaml/stdlib] to have it compiled with |
| 144 | +the new JS compiler. |
| 145 | + |
| 146 | +Since our work is derivative work, we choose the GPL v2 license to |
| 147 | +make it compatible with |
| 148 | +[js_of_ocaml](http://ocsigen.org/js_of_ocaml/). |
| 149 | + |
| 150 | +Note that QPL license is not compatible with GPL V2, so we distribute |
| 151 | +our changes to the compiler as a patch instead. |
| 152 | + |
| 153 | +## Design goal |
| 154 | + |
| 155 | +1. Readability |
| 156 | + 1. No name mangling |
| 157 | + 2. Support Javascript modules systems |
| 158 | + 3. Integrate with existing javascript ecosystem, for example, |
| 159 | + [npm](https://www.npmjs.com/), [webpack](https://github.com/webpack). |
| 160 | + 4. Straight-forward FFI, generate tds file to target [Typescript](http://www.typescriptlang.org/) for better tooling |
| 161 | + |
| 162 | +2. Separate and *extremely fast* compilation. |
| 163 | + |
| 164 | +3. Better performance than hand-written Javascript: |
| 165 | + thanks to a sound type system in OCaml so that we |
| 166 | + can play more optimizations and pipe it to Google Closure Compiler |
| 167 | + for production mode. |
| 168 | + |
| 169 | +4. Smaller code than hand written JS code, compatible with Google Closure Compiler |
| 170 | + |
| 171 | +5. Support NodeJs, Web Browser and various Javascript target platform. |
| 172 | + |
| 173 | +6. Compatible with OCaml semantics modulo c-bindings and Obj, Marshal module |
| 174 | + |
| 175 | +## More examples |
| 176 | + |
| 177 | +### NodeJS support |
| 178 | + |
| 179 | +Below it is an idea how it would integrate with the existing JS |
| 180 | +eco-system: |
| 181 | + |
| 182 | +```js |
| 183 | + |
| 184 | +var $$Array = require('./array'); // OCaml Array module |
| 185 | +var List = require ('./list'); // OCaml List module |
| 186 | + |
| 187 | +List.iter(function(x){console.log('hi, nodejs '+x)}, |
| 188 | + $$Array.to_list ($$Array.init(5,function(x){return x}))) |
| 189 | +``` |
| 190 | + |
| 191 | +You get the output: |
| 192 | + |
| 193 | +```sh |
| 194 | +hi, nodejs 0 |
| 195 | +hi, nodejs 1 |
| 196 | +hi, nodejs 2 |
| 197 | +hi, nodejs 3 |
| 198 | +hi, nodejs 4 |
| 199 | +``` |
| 200 | + |
| 201 | +### A naive benchmark with Facebook immutable |
| 202 | + |
| 203 | + |
| 204 | +Below is a *contrived* example to demonstrate our motivation, |
| 205 | +it tries to insert 1000,000 keys to an immutable map and query it |
| 206 | + |
| 207 | +```Ocaml |
| 208 | +module IntMap = Map.Make(struct |
| 209 | + type t = int |
| 210 | + let compare (x : int) y = compare x y |
| 211 | + end) |
| 212 | +
|
| 213 | +let test () = |
| 214 | + let m = ref IntMap.empty in |
| 215 | + let count = 1000000 in |
| 216 | + for i = 0 to count do |
| 217 | + m := IntMap.add i i !m |
| 218 | + done; |
| 219 | + for i = 0 to count do |
| 220 | + ignore (IntMap.find i !m ) |
| 221 | + done |
| 222 | +
|
| 223 | +let () = test() |
| 224 | +
|
| 225 | +``` |
| 226 | + |
| 227 | +This is an equivalent JS version by using Facebook's |
| 228 | +[immutable](http://facebook.github.io/immutable-js/) library |
| 229 | + |
| 230 | + |
| 231 | +``` js |
| 232 | +'use strict'; |
| 233 | +var Immutable = require('immutable'); |
| 234 | +var Map = Immutable.Map; |
| 235 | +var m = new Map(); |
| 236 | +var test = function(){ |
| 237 | + var count = 1000000 |
| 238 | + for(var i = 0; i < count; ++i){ |
| 239 | + m = m.set(i, i ); |
| 240 | + } |
| 241 | + for(var j = 0; j < count ; ++j){ |
| 242 | + m.get(j) |
| 243 | + } |
| 244 | +} |
| 245 | + |
| 246 | +test () |
| 247 | +``` |
| 248 | + |
| 249 | +Runtime performance: |
| 250 | +- OCaml Immutable Map: 1186ms |
| 251 | +- Facebook Immutable Map: 3415ms |
| 252 | + |
| 253 | +Code Size: |
| 254 | +- OCaml (Prod mode): 899 Bytes |
| 255 | +- Facebook Immutable : 55.3K Bytes |
| 256 | + |
| 257 | + |
| 258 | +## Status |
| 259 | + |
| 260 | + |
| 261 | +It covers the most of OCaml language, given that it is a quite young |
| 262 | +project (5 men-months until Jan 2016), there are still plenty of work |
| 263 | +to be done. |
| 264 | + |
| 265 | +Some known issues are listed as below: |
| 266 | + |
| 267 | +1. Language features: |
| 268 | + |
| 269 | + Recursive modules, have not looked into it yet. |
| 270 | + |
| 271 | + Better Currying support. Currently, we have an inference engine for |
| 272 | + function curring and we do cross module inference, however, there |
| 273 | + are some more challenging cases, for example, high order functions, |
| 274 | + it can be resolved by either aggressive inlining or fall back to a |
| 275 | + slow path using `Function.prototype.length`. We prepared the |
| 276 | + runtime support in [module curry](jscomp/runtime/curry.ml), will support it in the near |
| 277 | + future. |
| 278 | + |
| 279 | + Int32 operations, currently, Int use Float operations, this should |
| 280 | + be fixed in the near future. |
| 281 | + |
| 282 | +2. Standard libraries distributed with OCaml: |
| 283 | + |
| 284 | + IO support, we have very limited support for |
| 285 | + `Pervasives.print_endline` and `Pervasives.prerr_endline`, it's |
| 286 | + non-trivial to preserve the same semantics of IO between OCaml and |
| 287 | + NodeJS, one solution is to functorize all IO operations. Functors |
| 288 | + are then inlined so there will no be performance cost or code size |
| 289 | + penalty. |
| 290 | + |
| 291 | + Bigarray, Unix, Num, Int64 |
| 292 | + |
| 293 | +## License |
| 294 | + |
| 295 | +The OCaml to JS compiler libraries are distributed under the GPL (version 2.0); |
| 296 | +see the LICENSE file at the top of the source tree for more information. |
| 297 | + |
| 298 | + |
| 299 | +The contents of some files in this distribution was derived from external |
| 300 | +sources with different licenses. The original copyright and license |
| 301 | +notice was preserved in the affected files. |
| 302 | + |
| 303 | +## Question, Comments and Feedback |
| 304 | + |
| 305 | +If you have questions, comments, suggestions for improvement or any other inquiries |
| 306 | +regarding this project, feel free to open an issue in the issue tracker. |
| 307 | + |
| 308 | + |
| 309 | + |
| 310 | + |
| 311 | + |
| 312 | + |
0 commit comments