0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Zstandard(zstd)のデコーダーをWASMでビルドする

Posted at

通信データ量を削減するため、圧縮したデータをダウンロードしてブラウザ側で解凍するという場面は結構あるかと思います。
はやりのZstandardのWASMでブラウザの解凍処理を試そうとしたところ、ビルドする際に色々はまったので作業ログを残しておきます。

この記事では解凍(decoder)だけを扱います

要約

Zstandardのビルド時にemcc-s MALLOC=dlmalloc or 指定なしでビルドすればOK
試行結果をGitHubに置いておきます。

WASMのビルドが通らない

先人たちがZstandardをWASMでビルドした例を公開してくれています。ありがたいですね。
https://github.com/donmccurdy/zstddec-wasm

しかし、2025年1月現在ではこのコマンドでビルドが通らなくなっているためコマンドを見直します。

$ git clone https://github.com/facebook/zstd.git
$ cd zstd/build/single_file_libs/
$ ./combine.sh -r ../../lib -o zstddeclib.c zstddeclib-in.c     # ここまではOK

# Emscriptenのビルドが通らない・・・
$ emcc zstddeclib.c -Oz \
    -s EXPORTED_FUNCTIONS="['_ZSTD_decompress', '_ZSTD_findDecompressedSize', '_ZSTD_isError', '_malloc', '_free']" \
    -s ALLOW_MEMORY_GROWTH=1 \
    -s MALLOC=emmalloc \
    -o zstddec.wasm
wasm-ld: error: /home/hogehoge/work/wasm/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libstandalonewasm-nocatch-memgrow.a(__main_void.o): undefined symbol: main
emcc: error: '/home/hogehoge/work/wasm/emsdk/upstream/bin/wasm-ld -o zstddec.wasm /tmp/emscripten_temp_z46po2ux/zstddeclib_0.o -L/home/hogehoge/work/wasm/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten /home/hogehoge/work/wasm/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/crt1.o -lGL-getprocaddr -lal -lhtml5 -lc_optz -lstandalonewasm-nocatch-memgrow -lstubs -lc -lemmalloc -lcompiler_rt -lc++-noexcept -lc++abi-noexcept -lsockets -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr /tmp/tmp13wdvq7blibemscripten_js_symbols.so --strip-debug --export=ZSTD_decompress --export=ZSTD_findDecompressedSize --export=ZSTD_isError --export=malloc --export=free --export=emscripten_stack_get_current --export=_emscripten_stack_restore --export-if-defined=__start_em_asm --export-if-defined=__stop_em_asm --export-if-defined=__start_em_lib_deps --export-if-defined=__stop_em_lib_deps --export-if-defined=__start_em_js --export-if-defined=__stop_em_js --export-table -z stack-size=65536 --max-memory=2147483648 --initial-heap=16777216 --table-base=1 --global-base=1024' failed (returned 1)

main関数がないというエラーなので--no-entryを追加します。

$ emcc zstddeclib.c -Oz \
    -s EXPORTED_FUNCTIONS="['_ZSTD_decompress', '_ZSTD_findDecompressedSize', '_ZSTD_isError', '_malloc', '_free']" \
    -s ALLOW_MEMORY_GROWTH=1 \
    -s MALLOC=emmalloc \
    --no-entry \
    -o zstddec.wasm
$ base64 -w 0 zstddec.wasm > zstddec.txt

無事にビルドできましたのでJavaScript側から利用するためにBase64に変換しておきます。

ビルドしたWASMでZstandardのテストが通らない

上記のリポジトリにはZstandardのWASMで正常に解凍できているか確認するテストコードがありますので、まずはgit cloneしたままの内容でテストが通ることを確認します。

$ git clone https://github.com/donmccurdy/zstddec-wasm.git
$ cd zstddec-wasm/
$ npm install
$ npm run dist
$ npm run test

> zstddec@0.1.0 test
> npm run test:node && npm run test:browser


> zstddec@0.1.0 test:node
> tape *.test.cjs | tap-spec


  zstddec

    ✔ decodes text


  total:     1
  passing:   1
  duration:  37ms

無事にテストが通りました。次は本命のビルドしたWASMで試してみましょう。
WASMをBASE64に変換したzstddec.txtの内容をzstddec-wasm/zstddec.tsconst wasm = '...'に上書きして、再度テストを実行します。

$ npm run dist
$ npm run test

> zstddec@0.1.0 test
> npm run test:node && npm run test:browser


> zstddec@0.1.0 test:node
> tape *.test.cjs | tap-spec


  zstddec


    ✖ RuntimeError: memory access out of bounds
    --------------------------------------------
      operator: error
      stack: |-
  RuntimeError: memory access out of bounds
  at wasm://wasm/00031c36:wasm-function[43]:0xbb14
  at wasm://wasm/00031c36:wasm-function[42]:0xb991
  at wasm://wasm/00031c36:wasm-function[44]:0xbc60
  at ZSTDDecoder.decode (/home/hoge/work/wasm/temp/zstddec-wasm/dist/zstddec.cjs:47:44)
  at Test.<anonymous> (/home/hoge/work/wasm/temp/zstddec-wasm/zstddec.test.cjs:17:20)




  Failed Tests: There was 1 failure

    zstddec

      ✖ RuntimeError: memory access out of bounds


  total:     1
  passing:   0
  failing:   1
  duration:  35ms

テストが失敗しました。✖ RuntimeError: memory access out of boundsのエラーですが、dist/zstddec.cjsの47行目のmallocが問題のようです。

// dist/zstddec.cjs
    var compressedPtr = instance.exports.malloc(compressedSize);   # このmallocはエラーにならない
    heap.set(array, compressedPtr);
    // Decompress into WASM memory.
    uncompressedSize = uncompressedSize || Number(instance.exports.ZSTD_findDecompressedSize(compressedPtr, compressedSize));
        var uncompressedPtr = instance.exports.malloc(uncompressedSize);  # 47行目 このmallocでエラー
    var actualSize = instance.exports.ZSTD_decompress(uncompressedPtr, uncompressedSize, compressedPtr, compressedSize);

ビルド時のMALLOCの指定をemmallocからデフォルト値のdlmallocに変えてみます。

$ emcc zstddeclib.c -Oz \
    -s EXPORTED_FUNCTIONS="['_ZSTD_decompress', '_ZSTD_findDecompressedSize', '_ZSTD_isError', '_malloc', '_free']" \
    -s ALLOW_MEMORY_GROWTH=1 \
    -s MALLOC=dlmalloc \
    --no-entry \
    -o zstddec.wasm
$ base64 -w 0 zstddec.wasm > zstddec.txt

再度zstddec.txtconst wasm = '...'に上書きしてテスト実行したところ、テストが通りました。とりあえず動いたのですが、なんでemmallocだと動かないんでしょうね…

$ npm run dist
$ npm run test

> zstddec@0.1.0 test
> npm run test:node && npm run test:browser


> zstddec@0.1.0 test:node
> tape *.test.cjs | tap-spec


  zstddec

    ✔ decodes text


  total:     1
  passing:   1
  duration:  39ms
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?