diff --git a/README.md b/README.md index c51dc4ddd8..106340751b 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Js_of_ocaml is composed of multiple packages: - js_of_ocaml-tyxml, tyxml support. - js_of_ocaml-toplevel, lib and tools to build an ocaml toplevel to javascript. +- wasm_of_ocaml-compiler, [the Wasm_of_ocaml compiler](README_wasm_of_ocaml.md). ## Requirements diff --git a/README_wasm_of_ocaml.md b/README_wasm_of_ocaml.md index b50568ed72..299c83bb37 100644 --- a/README_wasm_of_ocaml.md +++ b/README_wasm_of_ocaml.md @@ -1,10 +1,10 @@ # Wasm_of_ocaml -Wasm_of_ocaml is a fork of Js_of_ocaml which compiles OCaml bytecode to WebAssembly. +Wasm_of_ocaml is an alternative compiler which compiles OCaml bytecode to WebAssembly. ## Supported engines -The generated code works with Chrome 119, Node.js 22 and Firefox 122 (or more recent versions of these applications). +The generated code works with Chrome 119, Node.js 22, Firefox 122, and Safari 18.2 (or more recent versions of these applications). In particular, the output code requires the following [Wasm extensions](https://webassembly.org/roadmap/) to run: - [the GC extension](https://github.com/WebAssembly/gc), including functional references and 31-bit integers @@ -12,25 +12,40 @@ In particular, the output code requires the following [Wasm extensions](https:// - [the exception handling extension](https://github.com/WebAssembly/exception-handling/blob/master/proposals/exception-handling/Exceptions.md) OCaml 5.x code using effect handlers can be compiled in two different ways: -One can enable the CPS transformation from `js_of_ocaml` by passing the +one can enable the CPS transformation from `js_of_ocaml` by passing the `--effects=cps` flag. Without the flag `wasm_of_ocaml` will instead default to `--effects=jspi` and emit code utilizing -- [the JavaScript-Promise Integration extension](https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md) +- [the JavaScript-Promise Integration extension](https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md). ## Installation The following commands will perform a minimal installation: ``` -git clone https://github.com/ocaml-wasm/wasm_of_ocaml -cd wasm_of_ocaml +git clone https://github.com/ocsigen/js_of_ocaml +cd js_of_ocaml opam pin add -n --with-version 6.0.0 . opam install dune.3.17.0 wasm_of_ocaml-compiler ``` You may want to install additional packages. For instance: ``` -opam install js_of_ocaml-ppx js_of_ocaml-lwt +opam install js_of_ocaml js_of_ocaml-ppx js_of_ocaml-lwt +``` + +## Running the test suite + +The following commands can be used to set up an opam switch and run the test suite. +``` +opam switch create wasm-tests 4.14.0 +eval $(opam env --switch=wasm-tests) +opam pin add -n base.v0.16.1 git@github.com:ocaml-wasm/base#wasm +opam pin add -n time_now.v0.16.1 git@github.com:ocaml-wasm/time_now#wasm +opam pin add -n ppx_inline_test.v0.16.1 git@github.com:ocaml-wasm/ppx_inline_test#wasm +opam pin add -n ppx_expect.v0.16.1 git@github.com:ocaml-wasm/ppx_expect#wasm +opam pin add -y -n --with-version 6.0.0 . +opam install . --deps-only --with-test +WASM_OF_OCAML=true dune build @runtest-wasm ``` ## Usage @@ -44,10 +59,10 @@ ocamlfind ocamlc -package js_of_ocaml,js_of_ocaml-ppx,js_of_ocaml-lwt -linkpkg - Then, run the `wasm_of_ocaml` compiler to produce WebAssembly code: ``` -wasm_of_ocaml cubes.byte +wasm_of_ocaml cubes.byte -o cubes.bc.js ``` -This outputs a file `cubes.js` which loads the WebAssembly code from file `cube.wasm`. For debugging, we currently also output the generated WebAssembly code in text file to `cube.wat`. Since Chrome does not allow loading from the filesystem, you need to serve the files using some Web server. For instance: +This outputs a file `cubes.bc.js` which loads the WebAssembly code from file `cube.bc.wasm`. For debugging, we currently also output the generated WebAssembly code in text file to `cube.wat`. Since Chrome does not allow loading from the filesystem, you need to serve the files using some Web server. For instance: ``` python3 -m http.server 8000 --directory . ``` diff --git a/manual/menu.wiki b/manual/menu.wiki index 8e530d8f44..83d5e7af3e 100644 --- a/manual/menu.wiki +++ b/manual/menu.wiki @@ -6,6 +6,11 @@ ==[[rev-bindings|Export OCaml code to JavaScript]] ==<> +=Wasm_of_ocaml - Reference Manual +==[[wasm_overview|Overview]] +==[[wasm_runtime|Binding a JS library]] +==<> + =Js_of_ocaml_lwt - Reference Manual ==[[lwt|Lwt support]] ==<> diff --git a/manual/wasm_overview.wiki b/manual/wasm_overview.wiki new file mode 100644 index 0000000000..2a7653bfec --- /dev/null +++ b/manual/wasm_overview.wiki @@ -0,0 +1,56 @@ += Wasm_of_ocaml = + +== Overview == + +Wasm_of_ocaml is a compiler from OCaml bytecode programs to WebAssembly. +It provides an alternative way to run pure OCaml programs in JavaScript environments like browsers and Node.js. + +The compiler is provided by the wasm_of_ocaml-package. The <> are compatible with this compiler. + +== Installation + +The easiest way to install wasm_of_ocaml is to use opam. +{{{opam install wasm_of_ocaml-compiler js_of_ocaml js_of_ocaml-ppx js_of_ocaml-lwt}}} + +== Usage == + + Your program must first be compiled using the OCaml bytecode + compiler {{{ocamlc}}}. JavaScript bindings are provided by the + {{{js_of_ocaml}}} package and the syntax extension by the + {{{js_of_ocaml-ppx}}} package +{{{ + ocamlfind ocamlc -package js_of_ocaml -package js_of_ocaml-ppx \ + -linkpkg -o cubes.byte cubes.ml +}}} + Then, run the {{{wasm_of_ocaml}}} compiler to produce Wasm code: +{{{ + wasm_of_ocaml cubes.byte +}}} + +This produces a Javascript loading script {{{cube.js}} and a directory +{{{cube.assets}} containing the Wasm code. + +=== with dune === + Dune has native support for wasm_of_ocaml (starting with dune 3.17.0). + It supports both standard and separate compilation. See https://dune.readthedocs.io/en/latest/wasmoo.html + +== Supported features == + +Most of the OCaml standard library is supported. However, + * Most of Sys module is not supported. + +Extra libraries distributed with Ocaml (such as Thread) are not +supported in general. However, + * Bigarray: bigarrays are supported using Typed Arrays + * Str: supported + * Graphics: partially supported using canvas (see also js_of_ocaml-lwt.graphics) + * Unix: time related functions are supported + +Compared to Js_of_ocaml, dynlink is not supported, and it is not possible to build an OCaml toplevel. The virtual filesystem is not implemented either. + +OCaml 5.x code using effect handlers can be compiled in two different ways: +one can enable the CPS transformation from `js_of_ocaml` by passing the +`--effects=cps` flag. Without the flag `wasm_of_ocaml` will instead default to +`--effects=jspi` and emit code utilizing + [the JavaScript-Promise Integration extension](https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md). +The CPS transformation is not the default since the generated code is slower, larger and less readable. On the other hand, the JSPI extension is not yet enabled by default in Web browsers, and performing effects is slower when using this extension. diff --git a/manual/wasm_runtime.wiki b/manual/wasm_runtime.wiki new file mode 100644 index 0000000000..485cd94898 --- /dev/null +++ b/manual/wasm_runtime.wiki @@ -0,0 +1,74 @@ += Writing Wasm primitives + +User-defined primitives can be implemented by writing Wasm modules. +This modules (".wat" extension for text modules and ".wasm" extension +for binary modules) can be passed on the command-line. +With dune, you can use the option +{{{(wasm_of_ocaml (wasm_files ...))}}} to specify these runtime files. + +It still makes sense to link JavaScript files to specify the possible +side-effects of some primitives (see <>), or to implement some functionalities in +JavaScript. +With dune, you can use the option +{{{(wasm_of_ocaml (javascript_files ...))}}} to specify these files. + +== Data representation == + +The type {{{(ref eq)}}} is used for all OCaml values. +Integers, chars, booleans and constant constructors are mapped to +{{{(ref i31)}}}. + +We use the following types for blocks, strings (and bytes), floats, +float arrays, and Javascript values. The first field of a block is its +tag, of type {{{(ref i31)}}}. +{{{ +(type $block (array (mut (ref eq)))) +(type $string (array (mut i8))) +(type $float (struct (field f64))) +(type $float_array (array (mut f64))) +(type $js (struct (ref null any))) +}}} + +You can import the following functions to access or allocate integers of type int32, int64, and nativeint. +{{{ +(import "env" "Int32_val" + (func $Int32_val (param (ref eq)) (result i32))) +(import "env" "caml_copy_int32" + (func $caml_copy_int32 (param i32) (result (ref eq)))) +(import "env" "Nativeint_val" + (func $Nativeint_val (param (ref eq)) (result i32))) +(import "env" "caml_copy_nativeint" + (func $caml_copy_int32 (param i32) (result (ref eq)))) +(import "env" "Int64_val" + (func $Int64_val (param (ref eq)) (result i64))) +(import "env" "caml_copy_int64" + (func $caml_copy_int64 (param i64) (result (ref eq)))) +}}} + +== Implementing primitives + +You define a primitive by exporting a Wasm function with parameters and return value of type {{{(ref eq)}}}. +{{{ +(func (export "input") (param $channel (ref eq)) (param $buffer (ref eq)) (param $offset (ref eq)) (param $length (ref eq)) (result (ref eq)) + ... +) +}}} + +== Linking with JavaScript code == + +If you need to use JavaScript functions to implement your Wasm primitives, you can import them using the {{{"js"}}} namespace: +{{{ +(import "js" "add" (func $add (param f64) (param f64) (result f64))) +}}} +The <> is used include them in the generated code. + +== Interfacing with JavaScript == + +You can use functions defined in `runtime/wasm/jslib.wat` for conversions between JavaScript values and OCaml values (for instance, the function {{{caml_js_to_float}}} converts a JavaScript to an OCaml float) and to perform various JavaScript operations such as property access (function {{{caml_js_get}}} below) or function call. +{{{ +(import "env" "caml_js_to_float" + (func $caml_js_to_float (param (ref eq)) (result (ref eq)))) +(import "env" "caml_js_get" + (func $caml_js_get (param (ref eq)) (param (ref eq)) (result (ref eq)))) +}}}