[ANN] GHCup-0.1.19.5 release candidate (GHC JS cross support)

@hsyl20 and me have been working to get GHCup up to speed wrt cross compilers and GHC JS backend. You can read more about it here as well: IOG GHC Update #14 | IOG Engineering

This is still experimental, which is why it’s a pre-release, so we can gather some user experience reports.

Here’s how to install GHCJS from a bindist and run a JS hello world:

1. Install emscripten

This is the required JS toolchain.

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

Instructions also here: Download and install — Emscripten 3.1.43-git (dev) documentation

2. Upgrade ghcup to pre-release

ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/develop/ghcup-prereleases-0.0.7.yaml
ghcup upgrade

3. Install cross bindist

ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/develop/ghcup-cross-0.0.8.yaml
emconfigure ghcup install ghc --set javascript-unknown-ghcjs-9.6.2

This will leave you with ghc binary called javascript-unknown-ghcjs-ghc.

3.1 Alternatively, you can also compile cross from source via ghcup:

ghcup install ghc 9.6.2
emconfigure ghcup -v compile ghc -j $(nproc) --git-ref ghc-9.6 -b 9.6.2 --hadrian -x javascript-unknown-ghcjs -o 9.6.2 --flavour=default+native_bignum
ghcup set ghc javascript-unknown-ghcjs-9.6.2

4. Compile hello world

Also see: building · Wiki · Glasgow Haskell Compiler / GHC · GitLab

Create a file HelloJS.hs with the following contents:

module Main where

main :: IO ()
main = putStrLn "Hello, JavaScript!"

Compile:

javascript-unknown-ghcjs-ghc -fforce-recomp HelloJS.hs

Run:

./HelloJS

Remarks

So far we only have 3 bindists: Index of /ghcup/unofficial-bindists/ghc/javascript-unknown-ghcjs-9.6.2/

These were all built manually by me. I couldn’t make it work on windows, there were build failures and emscripten doesn’t work too well there.

36 Likes

Congratulations to you two! This is extremely valuable work!

6 Likes

To be clear, does this refer to GitHub - ghcjs/ghcjs: Haskell to JavaScript compiler, based on GHC or to GHC’s Javascript backend? I think so far I’ve only heard the former described as GHCJS

1 Like

This.

Well, ghcjs is in the name of the target:

   Target platform       : javascript-unknown-ghcjs

I’m not sure how to distinguish it properly.

I changed the title to “GHC JS”.

3 Likes

Got it, thanks! And congrats on the release

this is cool! Though now i’m curious why emscriptem is a dependency? (this isn’t the web assembly backend right?)

I believe it uses wasm2js somewhere down the line.

This looks fantastic! Are there any plans to support the WASM backend as well?

From what I see there are already bindists, so that should be possible: autogen.json · master · Glasgow Haskell Compiler / ghc-wasm-meta · GitLab

I’ll try later today.

Cool!

For anyone who wants to try it, I think it’s worth emphasizing there’s a good reason GHC itself hasn’t released bindists for ghc-js yet. Namely, that it’s still incomplete!

@hsyl, can you say more about what you expect still doesn’t work? E.g. wired-in libraries like text are still broken because the C sources aren’t replaced by pure-Haskell functions yet, right?

I still think it’s very useful to have a bindist available for people who want to preview the technology (like I’m doing myself!)

Instructions for WASM cross compiler

Install toolchain

Also see Glasgow Haskell Compiler / ghc-wasm-meta · GitLab

git clone https://gitlab.haskell.org/ghc/ghc-wasm-meta.git
cd ghc-wasm-meta/
export SKIP_GHC=yes
sh setup.sh
source ~/.ghc-wasm/env

Upgrade ghcup and enable cross channel (if not done already)

ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/develop/ghcup-prereleases-0.0.7.yaml
ghcup upgrade
ghcup config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/develop/ghcup-cross-0.0.8.yaml

Install wasm alpine static bindist

ghcup install ghc --set wasm32-wasi-9.6.2.20230523 -- --host=x86_64-linux --with-intree-gmp --with-system-libffi

Build and run hello world

echo 'main = putStrLn "hello world"' > hello.hs
wasm32-wasi-ghc hello.hs -o hello.wasm
wasmtime ./hello.wasm
7 Likes

Amazing!

Sorry to be picky, but does it support wasm32-wasi-cabal yet? As far as I can tell, that’s currently what’s required to make any realistic programs.

Because Haskell codes are crippled with #include of C headers (+ use of .hsc files) so we rely on emscripten to provide headers that make sense for JavaScript and to provide a C compiler to run CPP. Also configure scripts expect working C toolchain (CC, LD…) so it is easier to give them one than to fix the whole ecosystem :slight_smile:

There’s nothing special about this. It’s just a simple wrapper:

cabal \
    --with-compiler=wasm32-wasi-ghc \
    --with-hc-pkg=wasm32-wasi-ghc-pkg \
    --with-hsc2hs=wasm32-wasi-hsc2hs \
    ${1+"$@"}

And you may also want to set CABAL_DIR to some other place. Those programs can also be configured in cabal.config I believe, so it really just boils down to a different cabal config or CABAL_DIR.

Current status of the backend is here: javascript backend · Wiki · Glasgow Haskell Compiler / GHC · GitLab

Josh has started this page recently https://gitlab.haskell.org/ghc/ghc/-/wikis/javascript-backend/ecosystem about the state of the ecosystem. Indeed many C sources haven’t been replaced with Haskell or JS sources yet (e.g. in text as you mention).

4 Likes

This is swell news! I’ll have to give this a go when I have some time!

Huh… somehow I never realised this. Thanks for pointing it out!

(And CABAL_DIR, too. Though since I’ve already used wasm32-wasi-cabal, it’s probably too late for me…)

Nice! I hadn’t really been expecting this as I thought you’d just wait for runtime-retargetability support to land (possibly due for GHC 9.10/10.0?), but I see that it was decided that this temporary approach is worth the effort.

Anyway, installation of both backends worked smoothly for me (for the record, I installed emscripten, wasmtime etc. from Arch Linux repositories, and had to qualify /usr/lib/emscripten/emconfigure since for some reason it’s not on PATH).

The exact purposes of the ghc-wasm-meta and ghc-wasm folders is slightly unclear. I’m guessing ghc-wasm-meta can be deleted after installation? And that ghc-wasm needs to be kept around, with env sourced in any shell in which one intends to use the wasm backend?

Getting off-topic slightly, my impression is that the JS backend will build most packages but often hits run-time issues. The WASM backend on the other hand seems more robust, but I’ve been unable to use it for a lot of what I’d like to since it can’t build the network library.

1 Like

I’m expecting that once GHC becomes runtime-retargetable, Cabal will grow a --target flag and that will be all that’s needed.

Anyway, I’ve often used a more complex wrapper script, based on this, but a lot has changed (i.e. improved) since that post.

The cross feature might still be needed for people who:

  • need a cross compiler that’s not distributed by upstream GHC at all (e.g. not supported target architecture)
  • need a cross compiler where some of the host platforms are not supported (e.g. FreeBSD is not supported by GHC HQ anymore and although some people build bindists… it’s unsure whether they want to expand that to JS/wasm backends)
  • build a cross compiler from source for whatever reason
2 Likes