如何在 webassembly 中构建和使用 libicu

How to build and use libicu in webassembly

我对 JavaScript 项目(在浏览器中)中 ICU63 库的单词迭代器感兴趣。所以在阅读文档后,我相信 ICU 默认使用 UTF-16,这与 JS 相同,它会避免我将 JS 字符串编码成其他东西。

第一步是用我需要的唯一功能构建一个包装器(我还不知道它是否有效):

#include "emscripten.h"
#include <string.h>
#include <unicode/brkiter.h>
#include <unicode/unistr.h>
#include <unicode/errorcode.h>

using namespace icu_63; 

EMSCRIPTEN_KEEPALIVE
int splitWords(const char *locale, const uint16_t *text, uint16_t *splitted) {
    //Note that Javascript is working in UTF-16
    //icu::
    UnicodeString result = UnicodeString();
    UnicodeString visibleSpace = UnicodeString(" ");
    int32_t previousIdx = 0;
    int32_t idx = -1;

    //Create a Unicode String from input
    UnicodeString uTextArg = UnicodeString(text);
    if (uTextArg.isBogus()) {
        return -1; // input string is bogus
    }

    //Create and init the iterator
    UErrorCode err = U_ZERO_ERROR;
    BreakIterator *iter = BreakIterator::createWordInstance(locale, err);
    if (U_FAILURE(err)) {
        return -2; // cannot build iterator
    }
    iter->setText(uTextArg);

    //Iterate and store results
    while ((idx = iter->next()) != -1) {
        UnicodeString word = UnicodeString(uTextArg, idx, idx - previousIdx);
        result += word;
        result += visibleSpace;
        previousIdx = idx;
    }
    result.trim();
    //The buffer contains UTF-16 characters, so it takes 2 bytes per point
    memcpy(splitted, result.getBuffer(), result.getCapacity() * 2);
    return 0;
}

除了尝试 link 时缺少符号外,它编译并看起来不错,因为我不知道如何继续。

LibICU 看起来需要大量内置数据。对于我的情况,频率表对于使用单词迭代器是强制性的。

我是否应该尝试将我的包装器复制到源文件夹中并尝试弄清楚如何使用 emconfigure。或者当我尝试编译我的包装器时是否可以 link libicu?第二个选项看起来像是浪费数据,因为我对库的大部分不感兴趣。

根据我的经验,处理库最简单的方法是先使用 emconfigure/emmake 构建库,然后 link 使用您自己的代码静态构建它们。像下面这样:

$ emcc your_wrapper.cpp \
       your_compiled_libICU_static_lib.a \
       -o result.js

使用 emconfigure/emmake 编译库有时非常困难,因为您可能需要修改源代码才能使其在 WebAssembly 中运行。

但是...好消息! Emscripten 提供了一些流行和复杂的库的端口 and ICU is one of them.

您可以使用 -s USE_ICU=1 标志编译您的代码,而无需自己编译 ICU:

$ emcc your_wrapper.cpp \
       -s USE_ICU=1 \
       -s ERROR_ON_UNDEFINED_SYMBOLS=0 \
       -std=c++11

需要注意的是,Emscripten ICU 端口是 ICU 62。所以你需要将 using namespace icu_63; 更改为 using namespace icu_62;

虽然 -s USE_ICU=1 在您可以轻松修改构建标志时很方便,但我发现从源代码安装 ICU 更方便,因为我还必须构建其他 configure/make/build 进程的库不要很好地使用 -s USE_ICU=1(至少在没有大量修改的情况下),而是期待一种更传统的方式来查找和 link 到 icu 库。

不幸的是,如果不进行一些调整,构建 libicu 似乎无法使用通常的 configure && make install。为此,首先您必须执行“常规”本机构建 (./configure && make) 以创建必要的本地文件。

然后,如果您不需要 PTHREADS,您可以按照以下相当简单的方式构建,假设 /opt/wasm 是您的 PREFIX。

PKG_CONFIG_LIBDIR=/opt/wasm/lib/pkgconfig emconfigure ./configure --prefix=/opt/wasm --with-cross-build=`pwd` --enable-static=yes --enable-shared=no --target=wasm32-unknown-emscripten --with-data-packaging=static --enable-icu-config --enable-extras=no --enable-tools=no --enable-samples=no --enable-tests=no
emmake make clean install

如果您确实需要 PTHREADS 供 lib 的某些下游使用者使用,您可能必须从一开始就启用它来重建 lib。这比较棘手,因为配置脚本在执行需要构建和 运行 C 代码片段的测试时会中断,这是由于关于需要额外节点标志的警告(参见 https://github.com/emscripten-core/emscripten/issues/15736),configure脚本意味着错误。我找到的最简单的解决方案是临时修改 emcc.py 中的 make_js_executable:

  ...
  with open(script, 'w') as f:
    # f.write('#!%s\n' % cmd); ## replaced with the below line
    f.write('#!%s --experimental-wasm-threads --experimental-wasm-bulk-memory\n' % cmd)
    f.write(src)
  ...

完成该 hack 后,您可以继续进行类似下面的操作(尽管可能并非所有这些与线程相关的标志都是绝对需要的)

CXXFLAGS='-s PTHREAD_POOL_SIZE=8 -s USE_PTHREADS=1 -O3 -pthread' CFLAGS='-s PTHREAD_POOL_SIZE=8 -s USE_PTHREADS=1 -O3 -pthread' FORCE_LIBS='-s PTHREAD_POOL_SIZE=8 -s USE_PTHREADS=1 -pthread -lm' PKG_CONFIG_LIBDIR=/opt/wasm/lib/pkgconfig emconfigure ./configure --prefix=/opt/wasm --with-cross-build=`pwd` --enable-static=yes --enable-shared=no --target=wasm32-unknown-emscripten --with-data-packaging=static --enable-icu-config --enable-extras=no --enable-tools=no --enable-samples=no --enable-tests=no
emmake make clean install

之后,将您的 emcc.py 设置回其原始状态。请注意,如果您尝试构建这些工具,它们将失败——我还没有找到解决方案——但 lib 确实成功安装了上述内容。