WebGHC / wasm-cross

Nix expressions for cross compiling to WebAssembly
141 stars 5 forks source link

Try LLVM tailcall without ghccc #39

Open dfordivam opened 4 years ago

dfordivam commented 4 years ago

If we are able to use llvm using the already supported tailcall (musttail call, without the ghccc), then we will have better idea of the size of binary andd potential issues further in the compilation. Also we could test the runtime in the chromium (by enabling the tail-call), and get to know if there are any issues in our approach, etc.

dfordivam commented 4 years ago

For the simple "hello-world" haskell code, the following works fine

wasm32-unknown-unknown-wasm-llc -mattr=+tail-call main.ll

It generates the

    return_call_indirect    (i32, i32, i32, i64, i64, i64, i64, i64, i64, i64) -> ()

But in the machine code generation step it fails

wasm32-unknown-unknown-wasm-llvm-mc --triple wasm32-unknown-unknown-wasm --mc-relax-all --mcpu=generic --mattr=+tail-call --filetype=obj -o main.o main.s

main.s:6:9: error: data directive must occur in a data segment: 0
        .int64  0                       # @"s1gf_info$def"
                ^
main.s:8:9: error: data directive must occur in a data segment: 21
        .int32  21                      # 0x15

Files here -> https://gist.github.com/dfordivam/1a4551dc3ea8a084541586c138c70fac

dfordivam commented 4 years ago

The wip branch is https://github.com/WebGHC/wasm-cross/tree/llvm-tailcall-1

The binaries generated via llvm backend are huge (10mb for hello-world, 115 mb reflex-todomvc) as the linker --gc-sections is not removing most of the unused code. And this is not working because all the closure symbols being marked as NoStrip. I tried various things to stop this but nothing worked.

On doing a hack in linker to ignore NoStrip the sizes of the binaries reduced to 2mb for hello-world and 8mb for reflex-todomvc (after strip of debug symbols). These sizes are comparable / slightly bigger than the unregistered build (C based).

On further analysis of the closure of the binaries I found that they contain roughly the same number of symbols as the native binaries (indicating that the linker is indeed removing most of the dead code). And comparing the sizes of some of the APIs revealed that the size of wasm code is significantly bigger than the native.

API native size wasm size
base_GHCziConcziSignal_signalzuhandlers_entry 104 178
base_GHCziForeignPtr_mallocForeignPtrBytes2_entry 88 199
base_GHCziIOziBuffer_Buffer_con_entry 16 45
base_GHCziIOziEncoding_getFileSystemEncoding6_entry 88 250

I also tried the x86 llvm binary with NO_REGS and TABLES_NEXT_TO_CODE disabled. With this size of the binary (hello-world) increased by about 25%

So a combination of these two factors are behind the 2~3 times size difference between native x86 and wasm binaries.