没有 Emscripten,如何使用标准库编译C++到 WebAssembly

No Emscripten, How to Compile C++ With Standard Library to WebAssembly

本文关键字:编译 C++ WebAssembly 标准 Emscripten 何使用 没有      更新时间:2023-10-16

我在构建独立的 Web Assembly 时遇到了问题,我想要完全控制内存和布局。我不想使用 emscripten,因为正如下面的帖子所说,它没有给我所有我想要的编译时选项(例如堆栈大小控制、能够选择以独立模式导入内存等(我一直在浏览诸如以下内容的页面:如何使用 emscripten 生成独立的 webassembly 此外,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。

我尝试了一个"黑客",我下载了Emscripten,并让它构建了自己的libc ++和libc++abi文件。然后我尝试将这些文件和标题复制到正确的位置。 然后我收到错误消息,提到缺少线程API,这显然是由于未使用EMSCRIPTEN编译引起的。所以我定义了EMSCRIPTEN宏,这有点工作。然后我想也许我可以删除 wasi 依赖项并使用 emscripten 的 libc 版本保持一致,但随后也有冲突/丢失的标头。

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

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

编辑:

这是我现在拥有的超级黑客构建脚本(它是我在网上找到的大量修改版本(:

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 &

我不再确定这是否有效,因为系统可能会意外编译特定于平台的内容。我真正想要的是一个垫片,它只能让我主要使用标准容器。 这已经变得有点难以管理。接下来我该怎么做?

编辑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++的函数上使用"visible"属性动态导出时,项目进行了编译,但 wasm 模块无法在 JavaScript 中加载,所以我认为问题仍然存在。

这是据我所知的。问题是否与我使用的编译器与用于创建静态库的编译器不同有关?(我正在使用自制的叮当 9(。希望不是。那时我会有点卡住,因为我找不到其他方法来获取图书馆。手动 llvm 编译似乎失败了。

优秀的wasi-sdk将上游llvm-project(提供clang++(和wasi-libc作为git子模块,并使用合适的标志编译它们(最值得注意的是禁用wasi-libc中尚不支持的pthread(。

然后,您可以使用以下最小选项集编译自己的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。