Coder Social home page Coder Social logo

ocaml-to-wasm-overview's Introduction

Ocaml to WASM - an Overview

This is a collection of links and ideas and notes on compiling OCaml to WASM.

General Notes

3- A language implementation like OCaml breaks down in four big parts:
        1- Front-end compiler
        2- Back-end compiler and code emitter
        3- Run-time system
        4- OS interface

[...]

6- Here is a schematic of the Caml compiler.  (Use a fixed-width font.)

             |
             | parsing and preprocessing
             v
          Parsetree (untyped AST)
             |
             | type inference and checking
             v
          Typedtree (type-annotated AST)
             |
             | pattern-matching compilation, elimination of modules, classes
             v
          Lambda
           /  \
          /    \ closure conversion, inlining, uncurrying,
         v      \  data representation strategy
      Bytecode   \
                  \
                 Cmm
                  |
                  | code generation
                  v
               Assembly code

A more recent high-level view of the compilation pipeline (from https://ocamllabs.slack.com/archives/C0JCHGE78/p1568626615023800, Sep 16, 2019):

 Source code
   |
   | parsing
   v
 Parsetree
   |
   | typing
   v
 Typedtree
   |
   | desugar pattern matching, modules, objects, etc; erase types,
   | make explicit memory layout in terms of blocks and values
   |
   v
 Lambda (higher order lambda calculus based IR)
   |
   | make closure construction and usage explicit
   | perform inlining
   |
   v
 Clambda (like Lambda but with explicit closures, direct/indirect calls)
   |
   | make block/value manipulation explicit
   | make allocation explicit
   |
   v
  Cmm (tree-structured, explicit memory manipulation, C calls, etc)
   |
   | perform instruction selection,
   | sequentialization into basic blocks,
   | assignment of pseudo-registers
   |
   v
  Mach (block structured IR)
   |
   | liveness, register allocation, dead code elimination
   | are Mach -> Mach transformations
   |
   v
 Linear (linear sequence of abstract assembly instructions, explicit register assignments)
   |
   | this step is heavily backend-dependent, implemented in `emit.mlp`
   |
   v
 Textual assembly code

Runtime / Garbage Collection

Both OCaml bytecode and OCaml native code comes with a runtime that provides functions needed to run the compiled program.

The runtime provides
  • a copying garbage collector, the caml_alloc-functions

  • the caml_apply-functions that implement curried function application

  • binary and unary operations on tagged integers/floats

  • exception handling

TODO: find out what else the runtime does.

Dealing with OCaml values' lifetimes in WASM
  • gc1) "Manual" garbage collection on the WASM linear memory, by maintaining a stack in WASM linear memory and porting the garbage collector to WASM

    This is apparently what the WASM backend of the Go language does.

  • gc2) Use the WASM garbage collector extension to allocate and manage OCaml values

    The WASM garbage collector specification is still at a very early stage, and it is not clear how exactly it will work. The only reasonable way, at this point in time, to attempt this is to prototype our own WASM GC extension.

    What if, while browser support is not there yet, we could compile to WASM+GC and then compile WASM+GC to WASM?

  • gc3) Allocate heap objects on "the JavaScript side of the world" via Reference Types

    This is more or less a work-around for not having a WASM GC extension.

  • gc4) Create a version of OCaml that has static lifetimes, similar to Rust.

    I will not do this, since pretty much all existing OCaml code would need to be rewritten in order to be compiled to WASM with this variant of the language. Also, this may be different enough to OCaml that this is essentially a new programming language.

Paths to WASM

Direct
  • 1a) translate Lambda → WASM

  • 1b) translate Cmm → WASM

  • 1c) translate OCaml bytecode → WASM

  • 1d) run a bytecode interpreter for OCaml in WASM

Indirect
  • 2a) Cmm → LLVM → WASM

  • 2b) OCaml bytecode → LLVM → WASM

  • 2c) Ocaml → machine code → WASM

Direct Roads to WASM

1a) Lambda → WASM

While there are currently no projects that translate OCaml’s lambda IR to WASM, there are these:

1b) Cmm → WASM

Starting from an already optimized version of the program is likely to result in a comparatively fast execution speed.

Generally, it appears that Cmm is a good starting point when compiling to WASM without using the WASM GC extension, since the memory representation has already been flattened at the Cmm stage.

1c) OCaml bytecode → WASM

I am not aware of any projects that attempt translating from OCaml bytecode to WASM. Please let me know if you are.

An advantage is that the bytecode interpreter hardly ever changes at all (it is said to still be quite similar to what is laid out in the original report on ZINC).

There is no dependency on compiler internals, as we can work on the bytecode output of ocamlc.

In the past, translating bytecode has proven to be a successful and maintainable strategy for compiling OCaml to different languages:

  • [production-ready] OCaml bytecode → JavaScript: https://github.com/ocsigen/js_of_ocaml

    https://www.irif.fr/~balat/publications/vouillon_balat-js_of_ocaml.pdf presents performance results from 2011: The code generated by js_of_ocaml running on the V8 JavaScript engine was faster than running the bytecode interpreter on the bytecode generated by ocamlc, and slower than the machine code generated by ocamlopt. Exceptions turned out to be very expensive.

    js_of_ocaml is being used in production systems, as far as I know, it is currently the best tool to compile OCaml to JavaScript.

    OCaml values are allocated on the JavaScript heap (gc3), thus, the calls to the garbage collector are just stubs: https://github.com/ocsigen/js_of_ocaml/blob/e7a34b8e0697a34b235ff121132c72121c16798d/runtime/gc.js

    Note: It is unlikely, that exceptions will be an issue when compiling to WASM, since the exception mechanism in WASM will be different from the one in JavaScript.

  • [inactive] Ocaml bytecode → C: https://github.com/bvaugon/ocamlcc

    According to http://michel.mauny.net/data/papers/mauny-vaugon-ocamlcc-oud2012.pdf, performance in 2012 was better than running the bytecode interpreter, and worse than running the machine code generated by ocamlopt, which essentially was to be expected. However, this comes at the cost of having large executables, roughly up to twice the size of machine code in the considered examples.

    I managed to compile this using an older version of the OCaml compiler.

    I can compile trivial test programs to C.

    Compiling that C code using Emscripten to WASM, I am stuck with this error on the JavaScript console:

    exception thrown: RuntimeError: index out of bounds,_caml_page_table_modify@http://127.0.0.1:8000/output.js:45026:1
    _caml_page_table_add@http://127.0.0.1:8000/output.js:44203:1
    _caml_set_minor_heap_size@http://127.0.0.1:8000/output.js:89253:1
    _caml_init_gc@http://127.0.0.1:8000/output.js:90849:1
    _caml_main@http://127.0.0.1:8000/output.js:99291:1
    _main@http://127.0.0.1:8000/output.js:110038:1
    Module._main@http://127.0.0.1:8000/output.js:6717:10
    callMain@http://127.0.0.1:8000/output.js:7005:15
    doRun@http://127.0.0.1:8000/output.js:7064:23
    run/<@http://127.0.0.1:8000/output.js:7075:7

    I’m having trouble debugging this because I don’t have source maps for the C files where the _caml_-functions come from. The reason seems to be that the files aren’t actually included, only the headers. So I need to figure out what parameters to provide to emcc. In order to do that, I need to figure out what parameters ocamlcc uses to compile the code with gcc.

    I was able to get the parameters from ocamlcc by using the -verbose option, now the error is this:

    shared:ERROR: emcc: cannot find library "curses"

    While I could continue here, I think that this is a dead end due to the large code size.

1d) bytecode interpreter in WASM

Indirect Roads to WASM

If there was a compiler from OCaml to LLVM, it would immediately enable compilation to WASM.

2b) OCaml bytecode → LLVM

2c) machine code → WASM

For compiling machine code to WASM, there apparently do not currently exist any solutions.

One would need to apply some kind of algorithm that transforms the control flow from a program-counter-based representation to the labeled continuations that can be seen in WASM, just like Emscripten’s "Relooper" algorithm does for LLVM.

If there is an architecture whose machine code can be translated to WASM in a reasonably efficient fashion, and it turns out that OCaml already compiles to this architecture, this could be interesting.

If successful, this could, in the long run, help getting many other languages to compile to WASM as well.

My Collection of Links to Sort Through

ocaml-to-wasm-overview's People

Contributors

phated avatar sabine avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ocaml-to-wasm-overview's Issues

Path to WASM

At the moment, I think that the most reasonable approach to compile OCaml to WASM is to pick a bunch of WASM extension specifications (e.g. GC, tail calls, extensions) and find an intermediate representation that is not far from that. Then, compile OCaml to this IR.
Simultaneously:

  1. test correctness of compilation by compiling from IR to a modified form of the WASM reference interpreter with the extensions.
  2. compile from IR to WASM1.0 so that we have something that runs in real browsers.

Obviously, the non-finalized specs can change, and in the long run we'd need to adapt to that. In the short run, however, breaking things up into pieces at a high level seems likely to make it simpler to compile.

Low-Level Memory Layout in OCaml's Cmm vs. Higher-Level Memory Model of the WASM GC Extension

I'm keeping track of my current understanding and thoughts here. Please comment if you can clarify or correct something.

In the current WASM GC proposal draft, we have a rather high level memory model. The main reason for this high level memory model appears to be:

  • having one representation for (a) references to the Embedder (in browsers, JavaScript) side of the world and (b) heap blocks allocated by the "WASM GC" (which effectively, in browsers, appears to be the same (or a part of the) GC that is used for JavaScript).

In the current Cmm intermediate representation of the OCaml compiler, we have a rather low level memory model. Low-level in the sense that integers are tagged, that the memory layout of the module being compiled is visible, and that the content of a heap block to be allocated is described by a list of expressions.

When references to the heap are represented by values of a non-numeric type (as opposed to being actual addresses of heap blocks, i.e. numbers), this either leads to a model where every heap blocks has a type (as opposed to being linear memories from which we can read numbers that either represent ints/floats or heap pointers) or a model where a heap block consists of a byte-array and an array of references to other heap blocks. Why? Because we need the capability to store references to other heap blocks inside a heap block and because we cannot store the heap references directly in linear memory.

What is the point of i31ref?
In my mental model of the world, integer/float tagging is being done in garbage-collected languages in order to be able to distinguish heap pointers and primitive values when crawling a heap to find the live blocks. But if WASM heap blocks are typed structs/arrays, we don't need to tag, we just look at the type and know whether something is a heap pointer or a primitive value. But in turn, the WASM emitter (compiler backend of a programming language) needs to provide individual types for all the heap blocks it generates -> so there is no uniform representation for values that would be needed to implement polymorphic functions.
So, apparently, as a proposed workaround, one can supposedly type heap blocks as anyref arrays and then use typecasts from there. In this special case, i31ref is a tagged integer that the WASM GC can distinguish from real heap references. Which is essentially the same technique that the OCaml GC uses.
I am not sure that this is the only feasible model that makes a uniform representation for values possible.

Hypothesis 1): In the current WASM GC spec, using anyref heap structs and i31ref is the only feasible way to create a uniform representation for values. This was clearly wrong, as @wingo pointed out. We can allocate integers in a runtime that lives in the JS-side of the world and get anyrefs into our WASM program this way.
It still seems useful to modify the OCaml compiler and make a variant of Cmm that does not tag integers, since the JS integers we create are tagged by their dynamic type. Similarly, when i31ref arrives, which will likely come with improved performance over this solution, having untagged integers in Cmm seems more natural.

Mutable global variables as "registers"?

Traditionally, OCaml has a single stack (https://github.com/ocaml-multicore/ocaml-multicore/wiki/Native-code-notes#stack-layout-at-caml_start_program-vanilla-amd64) that holds both information about control flow and the values of local variables.
Traditionally, there are only caller-save registers. Generally, mapping registers of the existing compilation toolchain to local or global variables in WASM intuitively intuitively seemed appropriate to me. Currently, it is not.

If we bring OCaml's GC to WASM, we need to find (and modify, since we have a moving GC) the roots of the heap. Thus, we must store any value that could be a root in a place where we the GC can find it. Consequence: we cannot use local variables to hold heap pointers, since there is no way to iterate over local variables of the other frames on the stack. We can store heap pointers in the linear memory. Currently, if we would store heap pointers in global variables, any time we call out of a module, we would need to dump these global variables to the linear memory, and restore them at the callee site, so that they can be shared across WASM modules (e.g., to the GC module). Dumping and restoring a set of variables at every call and return site obviously incurs a high cost - so using WASM variables like registers doesn't currently seem to be a reasonable strategy.

The proposal on importing/exporting mutable global variables seems related: https://github.com/WebAssembly/mutable-global/blob/89e5be9d69f2afac7243b6d2ff36b9c8723efb77/proposals/mutable-global/Overview.md Even sharing a stack pointer (to the WASM linear memory) across modules seems to be a pain point, at the moment.

If we had this, it would be quite simple to "use global variables like registers" and, thus, reuse many of the existing compilation strategies of the optimizing OCaml compiler. Currently, it looks to me like there is a performance benefit to using global/local variables over the linear memory in Chrome, but not in Firefox. Generally, in the long run, I would expect a performance advantage for using variables instead of linear memory.

There's this other strategy for fulfilling the purpose that registers serve: we could choose a bunch of addresses on the WASM linear memory and use that in a similar fashion to registers.

For reference, in the LLVM backend (https://github.com/llvm/llvm-project/blob/6088f84398847152ad97eb1bc0b139a28e879b48/llvm/lib/Target/WebAssembly/WebAssemblyExplicitLocals.cpp), registers are "stackified", where possible. Any non-stackifiable registers are compiled to WASM local variables. We could use the same strategy, but only if we drop the idea of bringing the OCaml GC to WASM. I.e. we would need a WASM GC.

@lukewagner please comment on whether this makes sense and correct where I am mistaken.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.