【问题标题】:No Emscripten, How to Compile C++ With Standard Library to WebAssembly没有 Emscripten,如何使用标准库将 C++ 编译为 WebAssembly
【发布时间】:2020-04-22 12:27:24
【问题描述】:

我在构建独立的 WebAssembly 时遇到了麻烦,我想要完全控制内存和布局。我不想使用 emscripten,因为正如下面的帖子所说,它并没有为我提供我想要的所有编译时选项(例如堆栈大小控制、能够选择以独立模式导入内存等)。 ) 我一直在关注以下页面:How to generate standalone webassembly with emscripten 此外,emscripten 是矫枉过正。

到目前为止我做了什么: 我通过自制软件下载了一个完整的 llvm 9 工具链(我在 macos 10.14 上。) 我正在关注https://aransentin.github.io/cwasm/https://depth-first.com/articles/2019/10/16/compiling-c-to-webassembly-and-running-it-without-emscripten/ 的组合 我使用 wasi 来获取 C 标准库。使用像-Wl,-z,stack-size=$[1024 * 1024] 这样的链接器标志,我可以控制堆栈大小。编译成功。太好了!

但是,我需要使用 C++ 标准库来支持我自己的一些和其他第三方库。 据我所知,似乎没有任何简单的方法可以获取 libc++ 和 libc++abi。

我尝试了一个“hack”,在其中我下载了 Emscripten 并让它构建了自己的 libc++ 和 libc++abi 文件。然后我尝试将这些文件和标题复制到正确的位置。 然后我收到关于缺少线程 API 的错误消息,这显然是由于未使用 EMSCRIPTEN 编译造成的。所以我定义了 ESCRIPTEN 宏,并且这种方法奏效了。然后我想也许我可以删除 wasi 依赖并使用 emscripten 的 libc 版本保持一致,但随后也出现了冲突/丢失的标头。

简而言之,我认为我离我需要的地方有点近了,但事情变得非常混乱。我怀疑我采取了最简单的非授权方法。

有没有人成功地为独立的 WebAssembly 创建了一个构建系统,让您可以使用 c 和 c++ 标准库?

编辑:

这是我现在拥有的超级 hacky 构建脚本(它是我在网上找到的一个经过大量修改的版本):

DEPS = 
OBJ = library.o
STDLIBC_OBJ = $(patsubst %.cpp,%.o,$(wildcard stdlibc/*.cpp))
OUTPUT = library.wasm

DIR := ${CURDIR}

COMPILE_FLAGS = -Wall \
        --target=wasm32-unknown-wasi \
        -Os \
        -D __EMSCRIPTEN__ \
        -D _LIBCPP_HAS_NO_THREADS \
        -flto \
        --sysroot ./ \
        -std=c++17 \
        -ffunction-sections \
        -fdata-sections \
        -I./libcxx/ \
        -I./libcxx/support/xlocale \
        -I./libc/include \
        -DPRINTF_DISABLE_SUPPORT_FLOAT=1 \
        -DPRINTF_DISABLE_SUPPORT_LONG_LONG=1 \
        -DPRINTF_DISABLE_SUPPORT_PTRDIFF_T=1

$(OUTPUT): $(OBJ) $(NANOLIBC_OBJ) Makefile
    wasm-ld \
        -o $(OUTPUT) \
        --no-entry \
        --export-all \
        --initial-memory=131072 \
        --stack-size=$[1024 * 1024] \
        -error-limit=0 \
        --lto-O3 \
        -O3 \
        -lc -lc++ -lc++abi \
        --gc-sections \
        -allow-undefined-file ./stdlibc/wasm.syms \
        $(OBJ) \
        $(LIBCXX_OBJ) \
        $(STDLIBC_OBJ)


%.o: %.cpp $(DEPS) Makefile
    clang++ \
        -c \
        $(COMPILE_FLAGS) \
        -fno-exceptions \
        -o $@ \
        $<

library.wat: $(OUTPUT) Makefile
    ~/build/wabt/wasm2wat -o library.wat $(OUTPUT)

wat: library.wat

clean:
    rm -f $(OBJ) $(STDLIBC_OBJ) $(OUTPUT) library.wat

我从 emscripten 中加入了 libc、libc++ 和 libc++abi(但老实说,这是一个糟糕的安装过程。)

我一直在逐步尝试填补我猜 emscripten 通常会完成的空白,但现在我又被困住了:

./libcxx/type_traits:4837:57: error: use of undeclared identifier 'byte'
  constexpr typename enable_if<is_integral_v<_Integer>, byte>::type &
                                                        ^
./libcxx/type_traits:4837:64: error: definition or redeclaration of 'type'
      cannot name the global scope
  constexpr typename enable_if<is_integral_v<_Integer>, byte>::type &

我不再确定这是否会起作用,因为系统可能会意外编译特定于平台的东西。我真正想要的是一个 shim,它可以让我主要使用标准容器。 这已经变得有点难以控制了。接下来我可以做什么?

编辑 2:正确,所以缺少 C++17 类型特征内容,当我转到 C++14(我仍然想要 C++17)时,我最终会丢失更多的东西。 肯定卡住了。

编辑 3:

我有点重新开始。库正在链接,我可以使用标准,但是如果我尝试使用例如,我会看到如下错误。一个 std::chrono 的方法(我可以实例化对象):

wasm-ld: error: /var/folders/9k/zvv02vlj007cc0pm73769y500000gn/T/library-4ff1b5.o: undefined symbol: std::__1::chrono::system_clock::now()

我目前正在使用来自 emscripten 的静态库 abi 和来自我的自制安装的 llvm 的静态库 C++ 标准库(我尝试了 emscripten ,但也不起作用)。

我不确定这是否与名称修改有关。我目前正在从 webasm 中导出所有符号,所以 malloc 和 co。也可以导出。

这是我的构建脚本:

clang++ \
--target=wasm32-unknown-wasi \
--std=c++11 \
-stdlib=libc++ \
-O3 \
-flto \
-fno-exceptions \
-D WASM_BUILD \
-D _LIBCPP_HAS_NO_THREADS \
--sysroot /usr/local/opt/wasi-libc \
-I/usr/local/opt/wasi-libc/include \
-I/usr/local/opt/glm/include \
-I./libcxx/ \
-L./ \
-lc++ \
-lc++abi \
-nostartfiles \
-Wl,-allow-undefined-file wasm.syms \
-Wl,--import-memory \
-Wl,--no-entry \
-Wl,--export-all \
-Wl,--lto-O3 \
-Wl,-lc++, \
-Wl,-lc++abi, \
-Wl,-z,stack-size=$[1024 * 1024] \
-o library.wasm \
library.cpp

我的代码:

#include "common_header.h"

#include <glm/glm.hpp>
#include <unordered_map>
#include <vector>
#include <string>
#include <chrono>

template <typename T>
struct BLA {
    T x;
};
template <typename T>
BLA<T> make_BLA() {
    BLA<T> bla;

    std::unordered_map<T, T> map;
    std::vector<T> bla2;

    std::string str = "WEE";
    //str = str.substr(0, 2);
    return bla;
}


#ifdef __cplusplus
extern "C" {
#endif

char* malloc_copy(char* input)
{   
    usize len = strlen(input) + 1;

    char* result = (char*)malloc(len);
    if (result == NULL) {
        return NULL;
    }

    strncpy(result, input, len);

    return result;
}

void malloc_free(char* input)
{
    free(input);
}

float32 print_num(float val);

float32 my_sin(float32 val) 
{   
    float32 result = sinf(val);

    float32 result_times_2 = print_num(result);

    print_num(result_times_2);

    return result;
}

long fibonacci(unsigned n) {
    if (n < 2) return n;
    return fibonacci(n-1) + fibonacci(n-2);
}

void set_char(char* input)
{
    input[0] = '\'';

    uint8 fibonacci_series[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
    for (uint8 number : fibonacci_series) {
        input[0] = number;
    }

    auto WEE = make_BLA<int>();
    WEE.x = 18;

    glm::vec4 v(100.0f, 200.0f, 300.0f, 1.0f);

    glm::vec4 v_out = glm::mat4(1.0f) * v;

    input[0] = 5 + static_cast<int>(v_out.x) * input[1];



     auto start = std::chrono::system_clock::now();
    long out = fibonacci(42);
    auto end = std::chrono::system_clock::now();

    std::chrono::duration<double> elapsed_seconds = end-start;
    std::time_t end_time = std::chrono::system_clock::to_time_t(end);

              auto elapsed = elapsed_seconds.count();


}

#ifdef __cplusplus
}
#endif

当我尝试仅在没有 C++ 的函数上使用“可见”属性动态导出时,项目已编译,但 wasm 模块无法在 JavaScript 中加载,所以我认为问题仍然存在。

这是我所知道的。这个问题可能与我使用的编译器与用于创建静态库的编译器不同这一事实有关吗? (我正在使用自制clang 9)。希望不会。那时我会有点卡住,因为我找不到另一种获取图书馆的方法。手动 llvm 编译似乎失败了。

【问题讨论】:

  • 没有发布答案是因为它没有回答您的具体问题,但是您是否尝试过新的 Emscripten 独立模式? v8.dev/blog/emscripten-standalone-wasm 它仍然应该以标准的 Emscripten 方式为您处理大部分事情,但只生成一个 Wasm 文件,您可以比使用标准生成的 JavaScript 更好地控制它。您提到了一些限制,但不清楚您如何尝试通过这些选项以及失败的原因 - 也许值得为他们发布一个单独的问题。
  • 是的,我最终使用了该选项。现在没关系。感谢您的回复。

标签: c++ compilation llvm-clang webassembly


【解决方案1】:

优秀的wasi-sdkllvm-project(提供clang++)和wasi-libc 拉到上游作为git 子模块,并使用合适的标志编译它们(最明显的是禁用pthreads,即not yet supported in wasi-libc)。

然后您可以使用以下最小选项集编译您自己的 C++ 源代码:

/path/to/wasi-sdk/build/install/opt/wasi-sdk/bin/clang++ \
  -nostartfiles \
  -fno-exceptions \
  -Wl,--no-entry \
  -Wl,--strip-all \
  -Wl,--export-dynamic \
  -Wl,--import-memory \
  -fvisibility=hidden \
  --sysroot /path/to/wasi-sdk/build/install/opt/wasi-sdk/share/wasi-sysroot \
  -o out.wasm \
  source.cpp

如果你想从运行时导入函数,我建议添加一行:

  -Wl,--allow-undefined-file=wasm-import.syms \

然后您可以将用换行符分隔的函数名放入wasm-import.syms,这样链接器就不会抱怨未定义的函数。

请注意,这一切都完全独立于 Emscripten。

【讨论】:

  • 谢谢!这有点晚了,但发布一个最小的 js 样板示例也可能会有所帮助。我记得必须在不同的环境名称下为一些标准库实现存根,但细节很模糊。
  • 这是一个最小但完整的 C++ 示例,标准库编译为 WebAssembly,并使用最少的 JavaScript 样板在浏览器中执行(总共约 40 行代码,不包括 JS 库和 HTML):@987654323 @直播版在这里:michaelfranzl.github.io/clang-wasm-browser-starterpack
猜你喜欢
  • 2020-08-23
  • 1970-01-01
  • 1970-01-01
  • 2018-03-30
  • 2020-12-06
  • 2018-09-21
  • 1970-01-01
  • 2021-08-01
  • 2019-01-10
相关资源
最近更新 更多