Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wasm_of_ocaml documentation #1771

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
33 changes: 24 additions & 9 deletions README_wasm_of_ocaml.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,51 @@
# 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
- [the tail-call extension](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md)
- [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
```

hhugo marked this conversation as resolved.
Show resolved Hide resolved
## 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 [email protected]:ocaml-wasm/base#wasm
opam pin add -n time_now.v0.16.1 [email protected]:ocaml-wasm/time_now#wasm
opam pin add -n ppx_inline_test.v0.16.1 [email protected]:ocaml-wasm/ppx_inline_test#wasm
opam pin add -n ppx_expect.v0.16.1 [email protected]: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
Expand All @@ -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 .
```
Expand Down
5 changes: 5 additions & 0 deletions manual/menu.wiki
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
==[[rev-bindings|Export OCaml code to JavaScript]]
==<<a_api subproject="js_of_ocaml" text="API"|index>>

=Wasm_of_ocaml - Reference Manual
==[[wasm_overview|Overview]]
==[[wasm_runtime|Binding a JS library]]
==<<a_api subproject="js_of_ocaml" text="API"|index>>

=Js_of_ocaml_lwt - Reference Manual
==[[lwt|Lwt support]]
==<<a_api subproject="js_of_ocaml-lwt" text="API"|index>>
Expand Down
56 changes: 56 additions & 0 deletions manual/wasm_overview.wiki
Original file line number Diff line number Diff line change
@@ -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 <<a_manual chapter="overview" |Js_of_ocaml libraries>> 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we mentioned the dune version or the dune lang required to enable the wasm support?

It supports both standard and separate compilation. See https://dune.readthedocs.io/en/latest/wasmoo.html

== Supported features ==
hhugo marked this conversation as resolved.
Show resolved Hide resolved

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.
74 changes: 74 additions & 0 deletions manual/wasm_runtime.wiki
Original file line number Diff line number Diff line change
@@ -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 <<a_manual chapter="linker" |Link
with JavaScript code>>), 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 <<a_manual chapter="linker" |Js_of_ocaml linker>> 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))))
}}}
Loading