Skip to content

Add support for WASM backend #2351

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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 ci.nix
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
|| (system == "aarch64-darwin" && !builtins.elem compiler-nix-name ["ghc902" "ghc928" "ghc948" "ghc966" "ghc967" "ghc96720250227" "ghc982" "ghc983" "ghc984"])
)) {
inherit (lib.systems.examples) ghcjs;
inherit (lib.systems.examples) wasi32;
} // lib.optionalAttrs (nixpkgsName == "unstable"
&& (__match ".*llvm" compiler-nix-name == null)
&& ((system == "x86_64-linux" && !builtins.elem compiler-nix-name ["ghc902" "ghc928" "ghc966" "ghc967" "ghc96720250227"]) # Not sure why GHC 9.6.6 TH code now wants `log1pf`
Expand Down
69 changes: 57 additions & 12 deletions compiler/ghc/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let self =
libffi ? null

, # we don't need LLVM for x86, aarch64, or ghcjs
useLLVM ? with stdenv.targetPlatform; !(isx86 || isAarch64 || isGhcjs)
useLLVM ? with stdenv.targetPlatform; !(isx86 || isAarch64 || isGhcjs || isWasm)
, # LLVM is conceptually a run-time-only dependency, but for
# non-x86, we need LLVM to bootstrap later stages, so it becomes a
# build-time dependency too.
Expand Down Expand Up @@ -117,14 +117,45 @@ let
INTEGER_LIBRARY = ${if enableIntegerSimple then "integer-simple" else "integer-gmp"}
'';

libffi-wasm = buildPackages.runCommand "libffi-wasm" {
nativeBuildInputs = [
(buildPackages.haskell-nix.tool "ghc912" "libffi-wasm" {
src = buildPackages.haskell-nix.sources.libffi-wasm;
})
buildPackages.clang
targetPackages.buildPackages.clang
targetPackages.buildPackages.llvm
targetPackages.buildPackages.binaryen
];
outputs = ["dev" "out"];
NIX_NO_SELF_RPATH = true;
} ''
mkdir cbits
cp ${buildPackages.haskell-nix.sources.libffi-wasm}/cbits/* cbits/
libffi-wasm
wasm32-unknown-wasi-clang -Wall -Wextra -mcpu=mvp -Oz -DNDEBUG -Icbits -c cbits/ffi.c -o cbits/ffi.o
wasm32-unknown-wasi-clang -Wall -Wextra -mcpu=mvp -Oz -DNDEBUG -Icbits -c cbits/ffi_call.c -o cbits/ffi_call.o
wasm32-unknown-wasi-clang -Wall -Wextra -mcpu=mvp -Oz -DNDEBUG -Icbits -c cbits/ffi_closure.c -o cbits/ffi_closure.o

mkdir -p $dev/include
cp cbits/*.h $dev/include
mkdir -p $out/lib
llvm-ar -r $out/lib/libffi.a cbits/*.o

wasm32-unknown-wasi-clang -Wall -Wextra -mcpu=mvp -Oz -DNDEBUG -Icbits -fPIC -fvisibility=default -shared -Wl,--keep-section=target_features,--strip-debug cbits/*.c -o libffi.so
wasm-opt --low-memory-unused --converge --debuginfo --flatten --rereloop --gufa -O4 -Oz libffi.so -o $out/lib/libffi.so
'';

# TODO check if this possible fix for segfaults works or not.
targetLibffi =
# on native platforms targetPlatform.{libffi, gmp} do not exist; thus fall back
# to the non-targetPlatform version in those cases.
let targetLibffi = targetPackages.libffi or libffi; in
# we need to set `dontDisableStatic` for musl for libffi to work.
if stdenv.targetPlatform.isMusl
then targetLibffi.overrideAttrs (_old: { dontDisableStatic = true; })
then targetLibffi.overrideAttrs (_old: { dontDisableStatic = true; })
else if stdenv.targetPlatform.isWasm
then libffi-wasm
else targetLibffi;

targetGmp = targetPackages.gmp or gmp;
Expand Down Expand Up @@ -195,13 +226,14 @@ let
# `--with` flags for libraries needed for RTS linker
configureFlags = [
"--datadir=$doc/share/doc/ghc"
] ++ lib.optionals (!targetPlatform.isGhcjs && !targetPlatform.isAndroid) ["--with-curses-includes=${targetPackages.ncurses.dev}/include" "--with-curses-libraries=${targetPackages.ncurses.out}/lib"
] ++ lib.optionals (targetLibffi != null && !targetPlatform.isGhcjs) ["--with-system-libffi" "--with-ffi-includes=${targetLibffi.dev}/include" "--with-ffi-libraries=${targetLibffi.out}/lib"
] ++ lib.optionals (!enableIntegerSimple && !targetPlatform.isGhcjs) [
] ++ lib.optionals (!targetPlatform.isGhcjs && !targetPlatform.isWasm && !targetPlatform.isAndroid) ["--with-curses-includes=${targetPackages.ncurses.dev}/include" "--with-curses-libraries=${targetPackages.ncurses.out}/lib"
] ++ lib.optionals (targetLibffi != null && !targetPlatform.isGhcjs && !targetPlatform.isWasm) ["--with-system-libffi" "--with-ffi-includes=${targetLibffi.dev}/include" "--with-ffi-libraries=${targetLibffi.out}/lib"
] ++ lib.optionals (targetPlatform.isWasm) ["--with-system-libffi"
] ++ lib.optionals (!enableIntegerSimple && !targetPlatform.isGhcjs && !targetPlatform.isWasm) [
"--with-gmp-includes=${targetGmp.dev}/include" "--with-gmp-libraries=${targetGmp.out}/lib"
] ++ lib.optionals (targetPlatform == hostPlatform && hostPlatform.libc != "glibc" && !targetPlatform.isWindows) [
"--with-iconv-includes=${libiconv}/include" "--with-iconv-libraries=${libiconv}/lib"
] ++ lib.optionals (targetPlatform != hostPlatform && !targetPlatform.isGhcjs) [
] ++ lib.optionals (targetPlatform != hostPlatform && !targetPlatform.isGhcjs && !targetPlatform.isWasm) [
"--with-iconv-includes=${targetIconv}/include" "--with-iconv-libraries=${targetIconv}/lib"
] ++ lib.optionals (targetPlatform != hostPlatform) [
"--enable-bootstrap-with-devel-snapshot"
Expand Down Expand Up @@ -229,9 +261,9 @@ let
;

# Splicer will pull out correct variations
libDeps = platform: lib.optional (enableTerminfo && !targetPlatform.isGhcjs && !targetPlatform.isAndroid) [ targetPackages.ncurses targetPackages.ncurses.dev ]
libDeps = platform: lib.optional (enableTerminfo && !targetPlatform.isGhcjs && !targetPlatform.isWasm && !targetPlatform.isAndroid) [ targetPackages.ncurses targetPackages.ncurses.dev ]
++ lib.optional (!targetPlatform.isGhcjs) targetLibffi
++ lib.optional (!enableIntegerSimple && !targetPlatform.isGhcjs) gmp
++ lib.optional (!enableIntegerSimple && !targetPlatform.isGhcjs && !targetPlatform.isWasm) gmp
++ lib.optional (platform.libc != "glibc" && !targetPlatform.isWindows) libiconv
++ lib.optional (enableNUMA && platform.isLinux && !platform.isAarch32 && !platform.isAndroid) numactl
++ lib.optional enableDWARF (lib.getLib elfutils);
Expand Down Expand Up @@ -306,12 +338,12 @@ let
# For build flavours and flavour transformers
# see https://gitlab.haskell.org/ghc/ghc/blob/master/hadrian/doc/flavours.md
hadrianArgs = "--flavour=${
(if targetPlatform.isGhcjs then "quick" else "default")
(if targetPlatform.isGhcjs || targetPlatform.isWasm then "quick" else "default")
+ lib.optionalString (!enableShared) "+no_dynamic_ghc"
+ lib.optionalString useLLVM "+llvm"
+ lib.optionalString enableDWARF "+debug_info"
+ lib.optionalString ((enableNativeBignum && hadrianHasNativeBignumFlavour) || targetPlatform.isGhcjs) "+native_bignum"
+ lib.optionalString targetPlatform.isGhcjs "+no_profiled_libs"
+ lib.optionalString ((enableNativeBignum && hadrianHasNativeBignumFlavour) || targetPlatform.isGhcjs || targetPlatform.isWasm) "+native_bignum"
+ lib.optionalString (targetPlatform.isGhcjs || targetPlatform.isWasm) "+no_profiled_libs"
} --docs=no-sphinx -j --verbose"
# This is needed to prevent $GCC from emitting out of line atomics.
# Those would then result in __aarch64_ldadd1_sync and others being referenced, which
Expand Down Expand Up @@ -425,9 +457,22 @@ haskell-nix.haskellLib.makeCompilerDeps (stdenv.mkDerivation (rec {
export EM_CACHE=$(mktemp -d)
mv config.sub.ghcjs config.sub
'')
+ lib.optionalString (targetPlatform.isWasm) ''
export CC="${targetCC}/bin/${targetCC.targetPrefix}cc"
export CXX="${targetCC}/bin/${targetCC.targetPrefix}c++"
export LD="${buildPackages.llvmPackages.lld}/bin/wasm-ld"
export AS="${targetCC.bintools.bintools}/bin/${targetCC.bintools.targetPrefix}as"
export AR="${targetCC.bintools.bintools}/bin/${targetCC.bintools.targetPrefix}ar"
export NM="${targetCC.bintools.bintools}/bin/${targetCC.bintools.targetPrefix}nm"
export RANLIB="${targetCC.bintools.bintools}/bin/${targetCC.bintools.targetPrefix}ranlib"
export READELF="${targetCC.bintools.bintools}/bin/${targetCC.bintools.targetPrefix}readelf"
export STRIP="${bintoolsFor.strip}/bin/${bintoolsFor.strip.targetPrefix}strip"
export NIX_CFLAGS_COMPILE_FOR_BUILD+=" -I${libffi.dev}/include -L${libffi.out}/lib"
export NIX_CFLAGS_COMPILE_FOR_TARGET+=" -I${targetLibffi.dev}/include -L${targetLibffi.out}/lib"
''
# GHC is a bit confused on its cross terminology, as these would normally be
# the *host* tools.
+ lib.optionalString (!targetPlatform.isGhcjs) (''
+ lib.optionalString (!targetPlatform.isGhcjs && !targetPlatform.isWasm) (''
export CC="${targetCC}/bin/${targetCC.targetPrefix}cc"
export CXX="${targetCC}/bin/${targetCC.targetPrefix}c++"
''
Expand Down
37 changes: 19 additions & 18 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 4 additions & 7 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,6 @@
url = "github:input-output-hk/cardano-shell";
flake = false;
};
"ghc-8.6.5-iohk" = {
type = "github";
owner = "input-output-hk";
repo = "ghc";
ref = "release/8.6.5-iohk";
flake = false;
};
hpc-coveralls = {
url = "github:sevanspowell/hpc-coveralls";
flake = false;
Expand All @@ -72,6 +65,10 @@
url = "github:stable-haskell/iserv-proxy?ref=iserv-syms";
flake = false;
};
libffi-wasm = {
url = "gitlab:haskell-wasm/libffi-wasm?host=gitlab.haskell.org";
flake = false;
};
};

outputs =
Expand Down
4 changes: 4 additions & 0 deletions lib/call-cabal-project-to-nix.nix
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ let
then "OSMinGW32"
else if pkgs.stdenv.targetPlatform.isGhcjs
then "OSGhcjs"
else if pkgs.stdenv.targetPlatform.isWasi
then "OSWasi"
else throw "Unknown target os ${pkgs.stdenv.targetPlatform.config}"
}")'
echo ',("target arch","${
Expand All @@ -345,6 +347,8 @@ let
then "ArchAArch32"
else if pkgs.stdenv.targetPlatform.isJavaScript
then "ArchJavaScript"
else if pkgs.stdenv.targetPlatform.isWasm
then "ArchWasm32"
else throw "Unknown target arch ${pkgs.stdenv.targetPlatform.config}"
}")'
echo ',("target platform string","${platformString pkgs.stdenv.targetPlatform}")'
Expand Down
Loading