Note
Some of the information here is outdated. Follow the installation instructions in the README. Using a virtual machine is no longer recommended and the ghcjs-build repository is no longer maintained.
Updated examples can be found in the ghcjs-examples repository, or click the source link. The safety specifications in the JavaScript foreign function interface have been changed slightly, see GHCJS.Foreign for more information. In particular, you need "interruptible" instead of "safe" for asynchronous imports.
introduction
After many months of hard work, we are finally ready to show you the new version of GHCJS. Our goal is to provide a full-featured Haskell runtime in the browser, with features like threading, exceptions, weak references and STM, allowing you to run existing libraries with minimal modification. In addition we have some JavaScript-specific features to make communication with the JS world more convenient. GHCJS uses its own package database and comes with Cabal and Template Haskell support.
The new version (gen2) is almost a ground-up rewrite of the older (trampoline/plain) versions. We built on our experience with the trampoline code generator, by Victor Nazarov and Hamish Mackenzie. The most important changes are that we now use regular JavaScript objects to store Haskell heap objects (instead of JS closures), and that we have switched to explicit stacks in a JavaScript array. This helps reduce the amount of memory allocation considerably, making the resulting code run much faster. GHCJS now uses Gershom Bazerman’s JMacro library for generating JavaScript and for the Foreign Function Interface.
This post is a practical introduction to get started with GHCJS. Even though the compiler mostly works now, it’s far from finished. We’d love to hear some feedback from users and get some help preparing libraries for GHCJS before we make our first official release (planned to coincide with the GHC 7.8 release). I have listed some fun projects that you can help us with at the end of this post. Join #ghcjs
on freenode for discussion with the developers and other users.
Some highlights of the new version:
- Easier installation, the compiler is now standalone, with its own package database. Install the compiler with
cabal install ghcjs
, install packages withcabal install --ghcjs myPackage
, - Everything works on 64 bit machines, generated JavaScript is the same as on 32 bit,
- New JavaScript FFI calling convention, with convenient import patterns and asynchronous FFI,
- Much improved performance compared to the older
trampoline
version, while still doing full tail-call optimization, - Improved threading with black holes, asynchronous exceptions and STM,
- The scheduler yields during long computations, to keep the browser responsive and able to handle events, even during long calculations,
- Better and faster Integer/big number support through JSBN,
- Easier command line testing with Node.js and jsshell,
- An extensive testsuite with benchmarks (includes much of the GHC testsuite and nofib).
Some work still remains:
- Cabal support is incomplete, in particular bundling JS files with cabal packages is not yet supported,
- Our dataflow analyzer needs some more tweaks to make the generated code smaller, also some metadata is currently encoded inefficiently,
- The front end (
ghcjs
executable) needs some work, for example to only recompile changed files, - The linker is too simple, it only supports linking everything referenced by
main
to one big file, some improvements, including incremental loading, are planned, - Some debug tracing code cannot be fully disabled, making some parts of the RTS run slower than necessary.
- More optimization is required to increase compilation speed and reduce memory consumption when compiling very heavy packages like
haskell-src-exts
. - Unboxed arrays are implemented with DataView, using big endian byte ordering. Since most clients are little endian, and JavaScript libraries tend to expect native byte order, we are going to switch to typed array views and little endian.
installation
GHCJS depends on a patched GHC HEAD (7.8) to support some of the new improvements, like the javascript
calling convention. In addition to that, you need a patched Cabal
library and cabal-install
which adds the GHCJS compiler flavour. We hope to have these patches merged before the first release in September, but unfortunately that means that it’s not yet possible to just cabal install
the compiler.
The easiest way to install GHCJS is with the vagrant setup script from the ghcjs-build repository. This builds a virtual machine with a fully working installation, and includes some examples.
Building is very easy, just run the following commands after installing vagrant:
# git clone https://github.com/ghcjs/ghcjs-build.git
# cd ghcjs-build
# git checkout prebuilt
# vagrant up
This downloads prebuilt binaries from our server. If you want to build everything from source, use this instead (warning, can take 3-4 hours):
# git clone https://github.com/ghcjs/ghcjs-build.git
# cd ghcjs-build
# vagrant up
After the build has finished, log into the newly created virtual machine:
# vagrant ssh
GHCJS should now be installed:
# which ghcjs
/home/vagrant/.cabal/bin/ghcjs
All the examples in the rest of this post are available in /home/vagrant/ghcjs-examples/weblog
in the vm, and on github. Exchanging files between the vm and the host is easy: The directory containing the vagrant setup script is mounted under /vagrant
in the vm, so just copy files there.
first steps
You can now test the installation by compiling a simple Haskell file:
module Main where
main = putStrLn "Hello, world"
Compile it and run the result with Node.js and jsshell (both preinstalled on vagrant):
# ghcjs -o hello hello.hs
[1 of 1] Compiling Main ( hello.hs, hello.o )
[1 of 1] Compiling Main
linking hello.jsexe: Main
# node hello.jsexe/all.js
Hello, world
# js hello.jsexe/all.js
Hello, world
The example also works in the browser. You need a web server to run it. The warp
server from the warp-static package is very convenient for this. It comes preinstalled on the vagrant vm. Run the following on the host, from the ghcjs-build
directory:
# vagrant ssh -c warp
Warp now listens on port 3000 and serves the home directory in the vm. Port 3030 on the host is remapped to this port on the virtal machine, so you should open http://localhost:3030/
on the host to see the examples. You can find the hello example at http://localhost:3030/ghcjs-examples/weblog/hello/hello.jsexe/
.
putStrLn
will print to the JavaScript debug console in the default configuration (you can redirect the output, but that’s a topic for a later post), so you need to open that first to see the result. Here’s what it should look like in Chrome:
GHCJS produces several files when compiling an executable. The most important one is out.js
which contains all the compiled Haskell code. The GHCJS linker starts with the main
IO action and includes all its (function-level) dependencies in out.js
. We have plans to add more options to the linker, including incremental loading. If you have specific needs or wishes, please let us know!
lib.js
and lib1.js
contain non-Haskell dependencies, including most of the RTS. The contents depend on the packages used by the program: All foreign dependencies of the program are included here. Currently this is done through the yaml descriptions in the shims repository, but we want to add support to Cabal to include the .js
files directly in packages. File lists lib.js.files
and lib1.js.files
are included to support custom build systems, for example if you want to load some of the dependencies from a different location.
rts.js
is a static file, generated by the compiler, which has lots of functions used by the Haskell runtime system. It’s the same for every program, but it might change depending on the debug options GHCJS is compiled with.
file | description |
---|---|
all.js |
combined lib.js , lib1.js , out.js , and rts.js , runnable with Node.js and jsshell. |
lib.js |
RTS and non-Haskell functions (this contains most of the RTS, including the scheduler) |
lib.js.files |
list of files in lib.js |
lib1.js |
RTS and non-Haskell functions that must be included after rts.js |
lib1.js.files |
list of files in lib1.js |
out.js |
compiled Haskell code |
rts.js |
JMacro generated part of the RTS |
index.html |
a sample HTML file that runs the main IO action |
The HTML file below is the default HTML generated by GHCJS. It runs the main IO action h$mainZCMainzimain
(z-encoded main:Main.main prefixed with h$
) with h$main
, which starts a new thread labeled “main” . The thread is started in the background, h$main
returns immediately. The Haskell runtime scheduler is started if it’s not yet running.
You can customize the HTML file, GHCJS does not overwite the file if it already exists.
<!DOCTYPE html>
<html>
<head>
<script language="javascript" src="lib.js"></script>
<script language="javascript" src="rts.js"></script>
<script language="javascript" src="lib1.js"></script>
<script language="javascript" src="out.js"></script>
</head>
<body>
</body>
<script language="javascript">
h$main(h$mainZCMainzimain);
</script>
</html>
foreign function interface
Note: The foreign function interface has been changed and the information below is out of date. You need "interruptible" now for asynchronous imports. See GHCJS.Foreign for more information
One of the improvements in the new GHCJS is support for the new foreign import javascript
calling convention. This not only gives us more convenient import patterns, inspired by UHC-JS and Fay, but also asynchronous FFI. foreign import ccall
is still supported, for compatibility with existing libraries, which can be used unchanged if you provide JavaScript implementations of the foreign imports.
Let’s look at the next example. If you open it in a browser (http://localhost:3030/ghcjs-examples/weblog/ffi/ffi.jsexe/
if you use the vagrant build) it prints the numbers 1. . 1000 to the document, one number per second.
{-# LANGUAGE JavaScriptFFI, CPP #-}
module Main where
#ifdef __GHCJS__
foreign import javascript unsafe "document.write($1+'<br/>');"
writeNumber :: Int -> IO ()
foreign import javascript safe "setTimeout($c, $1);"
delay :: Int -> IO ()
#else
writeNumber = error "writeNumber: only available from JavaScript"
delay = error "delay: only available from JavaScript"
#endif
main :: IO ()
main = mapM_ (\x -> writeNumber x >> delay 1000) [1..1000]
writeNumber
is a regular, synchronous, import. Placeholder $1
is replaced with the first function argument, an Int
. It writes this integer and an HTML linebreak to the document using document.write
.
The second import, delay
, is an asynchronous import, indicated by the safe
specification. An asynchronous imports get a callback parameter $c
. The calling Haskell thread is suspended until $c
is called, but other Haskell threads and JavaScript code continue to run (and the suspended thread can still receive asynchronous exceptions). This fits nicely into the common JavaScript pattern of doing IO asynchronously, reporting the results with a callback.
In the example, delay
uses the JavaScript function setTimeout
to call $c
after $1
milliseconds, so it works more or less like threadDelay (GHCJS supports threadDelay more efficiently through its own scheduler though). Another example of this pattern can be found in the ghcjs-jquery ajax function.
FFI argument and result types
GHCJS FFI supports the same types as native GHC does for ccall
foreign imports, with one addition, the JSRef type, defined in ghcjs-prim. JSRef
is used to store arbitrary JavaScript values. When you enable the JavaScriptFFI
extension, you can pass JSRef
values to both javascript
and ccall
imports. The ghcjs-base package has some tools to manipulate JSRef
values.
The FFI does marshalling for arguments the same way as the native GHC FFI does: If your foreign import has an Int argument, it gets a JavaScript number and not some thunk object.
Most numeric types are represented as a JavaScript number. Since JS doesn’t support single precision floating point, double precision is used everywhere. This can lead to results that differ slightly from GHC’s. If you return an Int
or Word
value, make sure that it’s an integer that fits in 32 bits. An easy way to do that is to return x|0
instead of x
. The bitwise or operator forces the result to be a 32 bit integer.
Some FFI types don’t fit in a single JavaScript variable. For example the JS number type has only 53 bits of precision, not enough for Word64#
and Int64#
. These types are passed in two arguments with the ccall
convention. With the javascript
calling convention they get two placeholders, see below for an example.
JavaScript only supports signed 32 bit operands for its bitwise operations. Therefore any Word#
value greater than 232 − 1 is represented by a negative number. The same goes for the two words in a Word64#
and the least significant word in an Int64#
.
Since JavaScript has no pointers, support for Ptr
/Addr#
is limited: Comparing pointers only works properly for pointers referring to the same data object, only the offsets are compared.
types | primitive | size | JavaScript type |
---|---|---|---|
Int, Int32, Int16, Int8 | Int# | 1 | number |
Word, Word32, Word16, Word8 | Word# | 1 | number (signed) |
Char | Char# | 1 | number |
Int64 | Int64# | 2 | number × number |
Word64 | Word64# | 2 | number × number |
Ptr | Addr# | 2 | object × number |
StablePtr | StablePtr | 2 | object × number |
Array | Array#, MutableArray#, MutableArrayArray# | 1 | array |
UArray, Vector, Text | ByteArray#, MutableByteArray# | 1 | object (DataView) |
other | MVar#, MutVar#, TVar#, Weak#, ThreadId#, StableName# | 1 | object |
JSRef | ByteArray# | 1 | anything |
When using the ccall
calling convention, size-two values get passed in two arguments. If you return such a value, store the second part in the special global variable h$ret1
.
Both safe
and unsafe
ccall
imports can call back into the Haskell runtime, but ccall
imports are always synchronous.
foreign import ccall unsafe "plus"
plus :: Int -> Int -> Int
foreign import ccall unsafe "complement"
negate :: Int64 -> Int64
foreign import ccall unsafe "f"
f :: Ptr a -> IO (Ptr b)
To avoid clashing with existing JavaScript names, all ccall
imported functions (like the rest of the GHCJS runtime) are prefixed with h$
, which leads to the following JS implementation for the above imports:
function h$plus(x,y) {
return (x+y)|0;
}
function h$complement(x_msw, x_lsw) {
h$ret1 = ~x_lsw;
return ~x_msw;
}
function h$f(x_data, x_offset) {
...
h$ret1 = new_ptr_offset;
return new_ptr_data;
}
If you use size-two values with the javascript
calling convention, you get multiple placeholders, for example $1
and $1_2
($1_1
also works for the first value). Results are assigned to $r
and $r_2
.
If the result size is 1, and the foreign import is synchronous, you can import a JavaScript expression and the result will be assigned automatically, like in the sin
example below.
Imports are not restricted to simple statements and expressions. You can use loops, local variables and other JavaScript constructs. Thanks to JMacro, local variable names in the import are converted to hygienic names, so you don’t need to worry about existing local variables in the code.
foreign import javascript unsafe
"$r = f($1, $1_2); $r_2 = h$ret1;"
g :: Ptr a -> IO (Ptr b)
foreing import javascript unsafe
"Math.sin($1)" -- the same as "$r = Math.sin($1);"
sin :: Double -> Double
foreign import javascript unsafe
"for(var i=0;i<$3;i++) { $1[i] = $2[i]; }"
copyArray :: JSArray -> JSArray -> Int -> IO ()
See the ghcjs-jquery and ghcjs-canvas packages for more examples of foreign imports.
text and JavaScript strings
It is not possible to pass a Haskell String directly to JavaScript. The best way to do it is to convert the text to a JSString
(which is a JSRef
) using the classes in GHCJS.Foreign.
toJSString
converts the value to Text first, and then converts the underlying UTF-16 ByteArray#
buffer to a JavaScript UCS-2 string. This means that working with Text is much faster than working with String.
foreign functions and packages
If your program or library requires some foreign code, the code must be added to the lib.js
or lib1.js
files for everything that depends on the package (or included in the HTML file manually). Unfortunately it’s not yet possible to include the foreign code in a Cabal package yet (it’s a planned feature).
As a workaround, we have the shims repository. It contains a yaml description file for every package, which references the required JavaScript files. When a package is then used, the files are automatically included in lib.js
or lib1.js
(in the same order as they are listed in the yaml file). Files referenced by multiple packages are included only once.
Please send us a pull request if you have added GHCJS support for a library. Package-specific support should go into pkg
, general functionality to src
. lib
contains third party JavaScript libraries.
concurrency and exception handling
Threading in GHCJS is implemented in a way similar to the single-threaded GHC runtime: There is one system thread on top of which multiple lightweight Haskell threads run. Long Haskell computations are interleaved by the preemptive scheduler in the runtime system, which also yields occassionally to let the browser run event handlers and other JavaScript code. Since JavaScript does not have shared-memory parallellism, operations like par
are no-ops in GHCJS.
Keep in mind that long-running JavaScript computations (called through the FFI or directly outside of Haskell code) will block all Haskell threads, as well as event handling for the browser itself. It’s best to run these things in an incremental way, or to move the whole calculation to a Web Workers process.
Other than this, threading works exactly the same as it does for native code with GHC, including support for MVar
, STM (atomically
, TVar
) and more advanced data structures like Chan
and TChan
based on those.
The example below (/home/vagrant/ghcjs-examples/weblog/race
) starts 10 threads that each update the position of a red square on a “race track”. The call to threadDelay
after each update makes the scheduler switch to a new thread immediately, creating a smooth animation. Other than the GUI, which is browser specific, this is exactly how you would write a native Haskell program.
{-# LANGUAGE JavaScriptFFI, CPP #-}
module Main where
import Control.Monad
import Control.Concurrent
import GHCJS.Foreign
import GHCJS.Types
#ifdef __GHCJS__
foreign import javascript unsafe
"document.getElementById($1).style.left = '' + $2 + 'px'"
setPos :: JSString -> Int -> IO ()
#else
setPos = error "setPos: only available in JavaScript"
#endif
main :: IO ()
main = mapM_ runRacer [1..10]
runRacer :: Int -> IO ()
runRacer n = void $ forkIO $ do
doRace (toJSString $ "racer" ++ show n)
doRace :: JSString -> IO ()
doRace str = go (0::Int)
where
go n | n > 800 = go 0
| otherwise = do
setPos str n
threadDelay 1
go (n+1)
The HTML for this example contains the race track and some extra elements for the red squares:
<html>
<head>
<script language="javascript" src="lib.js"></script>
<script language="javascript" src="rts.js"></script>
<script language="javascript" src="lib1.js"></script>
<script language="javascript" src="out.js"></script>
<style>
div.track { width: 800px; height: 30px; border: 1px solid black; position: relative }
div.racer { background-color: rgb(255,0,0); width: 20px; height: 20px; position: absolute; top: 5px }
</style>
</head>
<body>
<div class=track><div class=racer id=racer1></div></div>
<div class=track><div class=racer id=racer2></div></div>
<div class=track><div class=racer id=racer3></div></div>
<div class=track><div class=racer id=racer4></div></div>
<div class=track><div class=racer id=racer5></div></div>
<div class=track><div class=racer id=racer6></div></div>
<div class=track><div class=racer id=racer7></div></div>
<div class=track><div class=racer id=racer8></div></div>
<div class=track><div class=racer id=racer9></div></div>
<div class=track><div class=racer id=racer10></div></div>
</body>
<script language="javascript">
h$main(h$mainZCMainzimain);
</script>
</html>
exception handling and blackholes
New additions in the latest GHCJS version are support for blackholing and asynchronous exceptions. Blackholes are important for avoiding some kinds of memory leaks, help detect infinite loops (the <<loop>>
exception) and prevent duplicate work when multiple threads try to evaluate the same thunk. Asynchronous exceptions are used to interrupt threads during a computation or while waiting on a blocking operation (async foreign call or MVar operation for example).
The example below demonstrates these features. main
runs a loop, starting a thread to compute fib38
and print the result. After one second, the main thread throws an asynchronous exception to the forked thread. If the thread hasn’t finished computing and printing fib38
by that time, the running computation is suspended and the exception handler prints “not finished”. The next iteration, the fib38
computation is resumed from where it was interrupted.
Therefore, the example should print “not finished” a few times (depending on the speed of your computer and JavaScript engine). Eventually, the fib38
thunk is updated with the result, so the forked thread will just print the value every time from then on.
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Control.Concurrent
import qualified Control.Exception as E
import Control.Monad
-- this is inefficient on purpose
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
fib38 = fib 38
printFib = print fib38 `E.catch` \(_::E.SomeException) ->
putStrLn "not finished"
main = forever $ do
tid <- forkIO printFib
threadDelay 1000000
throwTo tid E.Overflow
System.Timeout
uses the same technique as the example, and also works with GHCJS. Interrupting computations not only works for pure code like fib38
, but also for IO operations like asychronous FFI calls and waiting for an MVar.
For more details on black holes, see Runtime Support for Multicore Haskell. GHCJS implements eager blackholing (the default setting for GHC is lazy blackholing), since it has no control over the JS garbage collector.
synchronous threads
The threads we have seen so far were all asynchronous: They are started with h$main
or h$run
, both of which take an IO action and start a new thread that runs the action in the background. This allows the Haskell runtime to provide asynchronous IO and keep the browser responsive by yielding during long calculations.
Usually this is what we want, but it does have one disadvantage: We cannot get any result back from the call, the Haskell thread might not even have started yet when h$run
returns. In most cases this is no problem, since Haskell can call back into JavaScript and we can supply a callback function.
Sometimes we need results immediately though, for example if we want an event to stop propagating from an event handler, we must call event.stopPropagation()
before returning from the handler. Clearly, h$run
will be of no use if we need to call into Haskell to decide whether to stop propagation of the event.
The alternative is to start the thread with h$runSync
, which tries to run the thread to completion before returning. If the thread is not finished before the h$runSync
call returns, it throws an exception:
call | result |
---|---|
h$runSync(action,false) |
run action in a synchronous thread, abort the thread if it tries to do an unsupported operation |
h$runSync(action,true) |
run action in a synchronous thread, continue the thread asynchronously after it does an unsupported operation |
Unfortunately, synchronous threads have a few limitations due to JavaScript’s single threaded execution model. The most important limitations are listed below:
operation | result |
---|---|
block on MVar | synchronous thread is interrupted (If the MVar operation does not block, it works as expected). |
threadDelay , yield |
synchronous thread is interrupted. |
asynchronous FFI or IO | synchronous thread is interrupted. |
enter a blackhole from another thread | The other thread is continued temporarily to finish computing the blackhole (might switch to other threads if this thread is also waiting for other blackholes). Synchronous thread is interrupted if the other thread tries to do an unsupported operation or has pending async exceptions. |
async exception | not supported in synchronous threads. |
In general it should be predictable whether an action is safe to run in a synchronous thread. Be careful with lazy IO and unsafePerformIO
though.
The example below takes a JavaScript object, wrapped in a JSRef
, it reads the input from the input
property, calculates the factorial, and stores the result in the output
property of the same object.
{-# LANGUAGE OverloadedStrings #-}
module Main where
import GHCJS.Types
import GHCJS.Foreign
import Data.Text (Text)
import Control.Applicative
import Text.Read (readMaybe)
main = do
o <- newObj
setProp ("input"::Text) ("123"::JSString) o
factorial o
r <- getProp ("output"::Text) o
putStrLn (fromJSString r)
factorial :: JSRef () -> IO ()
factorial ref = do
x <- fromJSString <$> getProp ("input"::Text) ref
let r = case readMaybe x of
Just n | n < 0 || n > 5000 -> "invalid input"
Just n -> toJSString . show . fact $ n
Nothing -> "parse error"
setProp ("output"::Text) r ref
where
fact :: Integer -> Integer
fact n = product [1..n]
Our HTML sets up an event listener for a text field. The event listener builds an IO action by applying the factorial
function to a JSRef
object, which it then executes in a synchronous thread.
See below for more information about calling Haskell from JavaScript.
<!DOCTYPE html>
<html>
<head>
<script language="javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script language="javascript" src="lib.js"></script>
<script language="javascript" src="rts.js"></script>
<script language="javascript" src="lib1.js"></script>
<script language="javascript" src="out.js"></script>
</head>
<body>
<input type="text" id="factorial_input" />
<div id="factorial_output"></div>
</body>
<script language="javascript">
$('#factorial_input').on('keyup change', function(e) {
var o = { input: $('#factorial_input').val() };
h$runSync( h$c2( h$ap1_e
, h$mainZCMainzifactorial
, h$c1(h$ghcjszmprimZCGHCJSziPrimziJSRef_con_e, o)
)
, false
);
$('#factorial_output').text(o.output);
});
</script>
</html>
event handling and the DOM
A straightforward way to handle events is by registering an event handler that runs an IO action. If we run the action in an asynchronous thread, we get no guarantees about the order in which the events are handled, each handler runs independently in its own thread. With a synchronous thread, we would have to be really careful with blocking operations.
That’s why we take a different approach in the ghcjs-jquery
package: Instead of starting a new thread to handle each event, we let the (JavaScript) event handler post the event to an MVar. A Haskell thread keeps consuming the events by taking the values from the MVar. This way, all events are always processed in order, and the overhead for processing an event is kept low.
This approach cannot always be used, since no Haskell code can be run to decide whether to stop propagating the event.
Below is a simple example using ghcjs-jquery
to handle events, using the MVar approach under the hood:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import JavaScript.JQuery
import JavaScript.JQuery.Internal
import Data.Default
import qualified Data.Text as T
main = do
b <- select "body"
append "<div class='mouse'></div>" b
m <- select ".mouse"
mousemove (handler m) def b
handler :: JQuery -> Event -> IO ()
handler m e = do
x <- pageX e
y <- pageY e
setText (T.pack $ show (x,y)) m
return ()
And the HTML:
<!DOCTYPE html>
<html>
<head>
<script language="javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script language="javascript" src="lib.js"></script>
<script language="javascript" src="rts.js"></script>
<script language="javascript" src="lib1.js"></script>
<script language="javascript" src="out.js"></script>
<style>html,body { height: 100%; padding: 0px; margin: 0px; }</style>
</head>
<body>
</body>
<script language="javascript">
h$main(h$mainZCMainzimain);
</script>
</html>
calling Haskell from JavaScript
The current API for calling Haskell from JavaScript is a bit primitive, this section will be updated when the API has been polished a bit more.
We have three functions that can run an IO action:
function | result |
---|---|
h$run |
run the IO action in a new asynchronous thread. |
h$main |
run the IO action in a new asynchronous thread labeled “main”. In Node.js and jsshell, the application exits when this thread finishes. |
h$runSync |
run a synchronous thread |
If you don’t have an IO action, but a function that produces one, you can use the following method to apply the function:
x :: Int -> JSRef a -> IO ()
var x = 5;
var y = h$c1(h$ghcjszmprimZCGHCJSziPrimziJSRef_con_e, o)
var action = h$c3(h$ap2_e, h$MainZCMainzix, x, y);
h$runSync(action, false);
The first argument, x
is an Int
, which can be passed directly to the Haskell function without additional wrapping. The second argument is a JSRef
, which is constructed with the h$c1
function, producing a Haskell heap object with one field. The IO action is then created with h$c3
and h$ap2_e
, creating a thunk that applies the h$MainZCMainzix
function to the two arguments.
Since we work with Haskell data structures directly here, without going through the FFI, we often have to wrap arguments in a Haskell heap object. Some argument types can be passed directly though, like x
in the example. Here is a list of types, and how they should be wrapped:
type | wrapping |
---|---|
Int, Int8, Int16, Int32, Word, Word8, Word16, Word32, Char, Double, Float | Pass directly as a JavaScript number. Make sure that the number is a 32 bit signed integer for the integral types. All datatypes with one constructor and one Int# , Word# or Char# field should be passed this way, |
Bool | Pass directly as false or true , |
Enumerations | Enumeration ADTs: multiple constructors without fields. Use the constructors directly, or false , true for the first two constructors, respectively, |
primitive types | No wrapping is needed, but make sure you use the right apply functions for size-2 values, |
everything else | wrap in a Haskell heap object, using the h$c functions. |
tasks and projects, help needed!
Here are some ideas for if you want to help us with the compiler and libraries. We also have some bigger projects going on. For example Dan Frumin is working on a pastebin site using diagrams and GHCJS to generate interactive widgets for Google Summer of Code. Hamish Mackenzie is working on JavaScriptCore and webkit binding to make building cross-platform applications that can run in the browser (with GHCJS) or in native code (with webkitgtk for the GUI) with minimal changes.
Contact us in #ghcjs
on freenode for more project ideas, or if you want some pointers to work on the GHCJS core itself.
project | description |
---|---|
break it! | if you find something that doesn’t work as expected, please report the bug. a minimal testcase that we can add to our testsuite would be awesome (should run with Node.js and jsshell, print deterministic results to stdout) |
tell us what features or changes you need | join us in #ghcjs on freenode, or create a ticket on github. In particular ideas that might need a change to the GHC patch should be reported quickly, we don’t want to be too late to merge in GHC 7.8! |
make your favourite Haskell library work with GHCJS | submit implementations of foreign functions required by your lib (or dependencies) to the (shims)[https://github.com/ghcjs/shims] repository |
make bindings for your favourite JavaScript library or API | See ghcjs-canvas and ghcjs-jquery for examples. |
extend the Cabal patch to support bundling and installing .js files with packages |
We can install packages with cabal install --ghcjs package , but JavaScript files cannot be installed and linked automatically yet. Add some fields to the .cabal file format for this, and make sure that they get installed correctly. |
more DOM support in ghcjs-base | Add a new module with DOM functions like getElementById to ghcjs-base. |
split the gloss package | Gloss works with GHCJS, (examples: zen tree styrene lens pong ) we have a backend that draws on a Canvas, but the package depends on OpenGL, which makes installation more difficult. Split Gloss into an OpenGL part (gloss) and a non-OpenGL part (gloss-base) to make installation with GHCJS easier. |
Web GUI framework bindings | Make a high-level Web GUI library, either doing low-level DOM directly, or by binding an existing GUI library like YUI or jQuery-UI |
FRP GUI bindings | FRP libraries like sodium or reactive-banana run out of the box, or with minor changes, with GHCJS, but we don’t have a convenient way to set up a reactive user interface yet. |
a better stepeval/debugger | we have a few examples showing the heap and stack when reducing a computation with step by step: ghcjs reduce. It would be nice to add a user interface to step through the evaluation, and to be able to interact more with the objects on the heap/stack (expanding/collapsing the view of the objects, changing the values) |
This really looks great!
I ported some of my code to see if it works and surprisingly big part actually does. Currently the performance is bit low but I understand it will get better. Do you have any estimates on how faster you can get? Right now I have about 10x slowdown compared to pure Haskell code.
It’s a bit hard to make estimates since there are so many variables. Some things are just inherently slower in JavaScript, unless we would go the really low level asm.js route, which would make JS interaction a lot less convenient, and require us to do all memory management manually. Due to the dynamic nature of JS, objects have a bit more overhead than GHC’s heap objects, and we cannot do tricks like pointer tagging in JS since we don’t have pointers.
Some remarks:
- When JS engines get native tail calls (ECMAScript 6), we can switch to direct function calls, instead of the current main loop, which would hopefully give us a nice speed boost (all our Haskell calls are in tail position already)
- In Firefox we expect a good speedup when they add a generational garbage collector (they’re working on that). It’s often 2-3x slower than v8 at the moment.
- JSBN (the bignumber library we use for integer-gmp support) is extremely slow compared to GMP, a lot can be gained here
- direct tail calls are currently not yet converted to loops, this can give some improvement in specific cases
- ByteArray# (used by Text, vector, UArray etc) is currently relatively slow, hardcoded big-endian in a DataView. That’s going to be switched to typed arrays, which should be a lot faster.
- we can remove one level of indirection when checking object types (checking whether something is already evaluated, this is done extremely often), but effect on performance is unknown so far.
- there is probably a lot of opportunity for low level optimizations, improving bit twiddling code etc.
I haven’t done any optimization work recently (when I started writing the new code generator i did some bytecode tracing with v8 to see if our code would be optimized decently), so there might be more opportunities for optimization. Specific situations can probably be optimized quite a bit, but I wouldn’t bet on spectacular general improvements until JavaScript engines help us a bit.
How about compiling haskell through Emscripten? That gives free optimizations and ams.js output.
Unfortunately the free optimizations come at the cost of being limited to a very small subset of JavaScript. GHCJS currently compiles Haskell closures to JavaScript objects, relying on the JavaScript garbage collector for most of the memory management work.
This means that we can also easily stick arbitrary JS values in Haskell objects, pass Haskell thunks around in JS, etc. With asm.js everything would have to go through some layer that maps between the JavaScript and the asm.js Haskell heap objects.
Also we would need to port GHC’s garbage collector and RTS. Emscripten might help here again, since they are written in Cmm and C, both of which can be compiled to LLVM.
A last issue is that compiled Haskell code does lots of tail calls, which are not yet supported directly by JavaScript. I’m not sure if emscripten can deal with the generated code at all. Perhaps ECMAScript 6 will solve this if native tail calls are added to JS.
So it’s not quite free optimizations…. That said, we have supported multiple code generators in the past (the one described in this post was a secondary experimental generator for the past 6 months), so if someone wants to experiment with LLVM/emscripten compilation we would welcome that.
From a CoffeeScript use:
Is there (will there be) sourceMap support for GHCjs?
There is no support yet, and doing it accurately is rather hard due to all the optimizations like inlining and specialization that happen to the code. I think we will add something a bit more limited, mapping only to the function but not the exact line.
> Port 3030 on the host is remapped to this port on the virtal machine, so you should open http://localhost:3030/ on the host to see the examples.
I just tried and got a blank page, the right port seems to be 3031:
config.vm.network :forwarded_port, guest: 3000, host: 3031
Thanks for the write-up! I’m a beginner at Haskell, so the step-by-step guide is really helpful, especially the combination with this preinstalled VM!