5

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.

anthumchris
  • 8,245
  • 2
  • 28
  • 53
Timothy Miller
  • 1,527
  • 4
  • 28
  • 48

1 Answers1

4

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
anthumchris
  • 8,245
  • 2
  • 28
  • 53
  • I figured out that my problem is that I can't use SIDE_MODULE or MAIN_MODULE options. My working command line is: `emcc memset.c decompress.c fastlz.c -O3 -s TOTAL_MEMORY=2147418112 -s WASM=1 -s USE_PTHREADS=1 -s "EXPORTED_FUNCTIONS=['_pcf_decompress_zlib', '_pcf_decompress_fastlz']" -s USE_ZLIB=1 -o decompress.wasm` – Timothy Miller May 19 '20 at 15:44