Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Link External C Library to WebAssembly Build

Tags:

I was reading this article (https://www.smashingmagazine.com/2019/04/webassembly-speed-web-app/) that explained how they used zlib, among other things, to speed up their web project:

To support the zlib library, we use the flag USE_ZLIB; zlib is so common that it’s already been ported to WebAssembly, and Emscripten will include it for us in our project

I would like to use zlib in my own WASM module.

In my C code (compiled with emcc), I wrote this interfacing function:

#include <zlib.h>

int pcf_decompress_zlib(unsigned char *input, int input_length, unsigned char *output, int output_length)
{
    uLongf output_length_result = output_length;
    int result = uncompress(output, &output_length_result, input, input_length);
    if (result != Z_OK) {
        return 0;
    } else {
        return output_length_result;
    }
}

I compiled it like so:

emcc decompress.c -O3 -s WASM=1 -s SIDE_MODULE=1 -s "EXPORTED_FUNCTIONS=['_pcf_decompress_zlib']" -s USE_ZLIB=1 -o decompress.wasm

When I did that, emcc automatically downloaded in a zlib library, so it seemed to know how to handle this.

Then in the browser, I have this class:

export class Decompressor {
    wasmOnLoad(obj) {
        this.instance = obj.instance;
        console.log("Loaded WASM");
        console.log(obj.instance);
        // Don't do anything else yet
    }

    constructor() {
        this.memory = new WebAssembly.Memory({
            initial: 1
        });
        this.heap = new Uint8Array(this.memory.buffer);
        this.imports = {
            env: {
                __memory_base: 0,
                memory: this.memory,
                abort: function(err) {
                    throw new Error('abort ' + err);
                },
            }
        };
    }

    start() {
        console.log("startWasm");
        WebAssembly.instantiateStreaming(fetch('decompress/decompress.wasm'), this.imports)
            .then(this.wasmOnLoad.bind(this));
    }
}

And then this in my main JS code loaded from my HTML:

import { Decompressor } from "./decompress/decompress.js";
var l = new Decompressor();
l.start();

When I load the page, Firefox gives me this error:

LinkError: import object field '_uncompress' is not a Function

It appears that the wasm code being emitted doesn't include zlib, and zlib is also not built into the browser. I thought about changing SIDE_MODULE to MAIN_MODULE, but that resulted in dozens of undefined symbols, making the problem even worse.

There would be no point in having emcc provide a USE_ZLIB=1 option if it didn't automatically make zlib available. So what am I missing t make this work? How do I get emcc to statically include the zlib code that it already has into the wasm module I'm compiling?

Thanks.

like image 451
Timothy Miller Avatar asked May 15 '20 16:05

Timothy Miller


People also ask

Can you compile C to WebAssembly?

When you've written a new code module in a language like C/C++, you can compile it into WebAssembly using a tool like Emscripten.

Can GCC compile to WebAssembly?

Once GCC supports '--target=[WebAssembly]', it can then be ported so that it can itself be compiled for/on a WebAssembly system (GCC native build), and then run inside a WebAssembly "machine" (web browser). >

Can you compile C++ to Wasm?

A program that can take our existing C++ code and compile it into a WebAssembly module. For this, we'll use Emscripten's emcc compiler, the most popular C++ to WebAssembly compiler of the bunch. To use the WebAssembly functionality from JavaScript, a binding is required.

Can WebAssembly interact with the DOM?

By itself, WebAssembly cannot currently directly access the DOM; it can only call JavaScript, passing in integer and floating point primitive data types. Thus, to access any Web API, WebAssembly needs to call out to JavaScript, which then makes the Web API call.


1 Answers

One way is to include the zlib source during the emcc build. I tested below. First, create this file structure (include the zlib source folder you downloaded)

$ tree -L 2 .
.
├── build.sh
├── dist
├── lib
│   └── zlib-1.2.11
└── src
    └── decompress.c

build.sh

ZLIB="lib/zlib-1.2.11"

emcc \
  -O3 \
  -s WASM=1 \
  -s EXPORTED_FUNCTIONS="[ \
      '_free', '_malloc' \
    , '_pcf_decompress_zlib' \
  ]" \
  -I $ZLIB \
  -o dist/decompress.wasm \
  $ZLIB/*.c \
  src/decompress.c

Now, configure zlib and build!

$ lib/zlib-1.2.11/configure  `# you only need to run this once`
$ ./build.sh
like image 190
AnthumChris Avatar answered Oct 02 '22 16:10

AnthumChris