11

I am having trouble building standalone webassembly with the full control I want over memory and layout. I don't want to use emscripten because, as the following post says, it doesn't give me all of the compile-time options I want (e.g. stack size control, being able to choose to import memory in standalone mode, etc.) I've been folowing pages such as: How to generate standalone webassembly with emscripten Also, emscripten is overkill.

What I've done so far: I have a fully working llvm 9 toolchain downloaded via homebrew (I am on macos 10.14.) I was following a mix of https://aransentin.github.io/cwasm/ and https://depth-first.com/articles/2019/10/16/compiling-c-to-webassembly-and-running-it-without-emscripten/ I used wasi to get the C standard library. Using linker flags like -Wl,-z,stack-size=$[1024 * 1024] I could control the stack size. Compilation was successful. Great!

However, I need to use C++ standard libraries to support some of my own and other third party libraries. As far as I can tell, there doesn't seem to be any easy way to get libc++ and libc++abi.

I tried a "hack" in which I downloaded Emscripten and had it build its own libc++ and libc++abi files. Then I tried copying those files and headers into the right spot. Then I got error messages referring to a missing threading API, which apparently were caused by not compiling with EMSCRIPTEN. So I defined the EMSCRIPTEN macro and that sort of worked. Then I thought that maybe I could remove the wasi dependency and use emscripten's version of libc to be consistent, but then there were conflicting / missing headers too.

In short, I think I got somewhat close to where I needed to be, but things just got terribly messy. I doubt I took the simplest non-emscripten approach.

Has anyone successfully created a build system for standalone webassembly that lets you use the c and c++ standard libraries?

EDIT:

This is the super hacky build script I have now (it's a heavily modified version of something I found online):

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

I dropped-in libc, libc++, and libc++abi from emscripten (but honestly this is a terrible installation process.)

I've been incrementally trying to fill in gaps that I guess emscripten would've normally done, but now I'm stuck again:

./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 &

I am no longer sure if this will even work since the system might accidentally compile something platform-specific in. Really what I'd like is a shim that would just let me use the standard containers mostly. This has become kind of unmanageable. What might I do next?

EDIT 2: Right so that's missing C++17 type trait content, and when I go to C++14 (I still want C++17) I end up with more missing things. Definitely stuck.

EDIT 3:

I sort of started over. The libraries are linking, and I'm able to use the standard, but I'm seeing errors like the following if I try to use e.g. an std::chrono's methods (I can instantiate the object):

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

I'm currently using the static library abi from emscripten and the static library C++ standard library from my homebrew installation of llvm (I tried the emscripten one but that didn't work either).

I'm not really sure if this is related to name mangling. I'm currently exporting all symbols from webasm so malloc and co. get exported as well.

Here is my build script:

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

My code:

#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

When I tried exporting dynamically with the "visible" attribute on only the functions that had no C++, the project compiled, but the wasm module failed to load in JavaScript, so I think the problem was still there.

This is as far as I've gotten. Might the issue be related to the fact that I'm using a different compiler from the one used to create the static libraries? (I'm using homebrew clang 9). Hopefully not. I'd be kind of stuck then because I couldn't find another way to get the libraries. Manual llvm compilation seemed to fail.

synchronizer
  • 1,955
  • 1
  • 14
  • 37
  • Not posting an answer because it's not answering your specific question, but have you tried the new Emscripten's standalone mode instead? https://v8.dev/blog/emscripten-standalone-wasm It should still take care of most of the things for you in standard Emscripten fashion, but produce just a Wasm file that you have more control over than with standard generated JavaScript. You mentioned couple of limitations, but it's not clear how you tried to pass those options and what failed - perhaps worth posting a separate question for them. – RReverser Jul 08 '20 at 15:24
  • Yes, I ended up using that option. It's okay for now. Thanks for the reply. – synchronizer Jul 08 '20 at 18:43

1 Answers1

6

The excellent wasi-sdk pulls upstream llvm-project (which provides clang++) and wasi-libc as git submodules and compiles them using suitable flags (most notably disabling pthreads which is not yet supported in wasi-libc).

You can then compile your own C++ source using the following minimal set of options:

/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

If you want to import functions from the runtime, I would suggest adding an additional line:

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

You then can put function names separated by newlines into wasm-import.syms so that the linker won't complain about undefined functions.

Note that all this is completely independent of Emscripten.

Michael Franzl
  • 1,341
  • 14
  • 19
  • Thanks! This is a bit late, but it might also be helpful to post an example of minimal js boilerplate. I remember having to implement stubs for some of the standard library under a different env name, but the details are fuzzy. – synchronizer Apr 22 '21 at 21:28
  • 2
    Here is a minimal but complete example of C++ with Standard Library compiled to WebAssembly and executed in the browser using minimal JavaScript boilerplate (about 40 lines of code in total, excluding JS libraries and HTML): https://github.com/michaelfranzl/clang-wasm-browser-starterpack/tree/dev/examples/11 Live version here: https://michaelfranzl.github.io/clang-wasm-browser-starterpack/ – Michael Franzl Jun 15 '21 at 06:32