Compile Clang statically for WebAssembly

I am trying to build clang statically with only the WebAssembly target. The purpose is to have a portable executable that would work on any modern linux and able to compile C/C++ to WASM.

For example, I should be able to compile something like this:

#include <cstdint>
#include <cstddef>

void vec_inc(uint32_t *v, size_t len) {
  for (auto i = 0; i < len; ++i) {
    v[i]++;
  }
}

I was able to build clang and can compile simple freestanding program, but I’d like to be able to use at least part of the runtime like the basic type definitions and ideally the STL. Is this doable?

After some investigation, this is where I stand right now:

cmake ../llvm \
  -DLLVM_ENABLE_PROJECTS='clang;lld' \
  -DCMAKE_BUILD_TYPE=MinSizeRel \
  -DCMAKE_EXE_LINKER_FLAGS=-static \
  -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
  -DLLVM_TARGETS_TO_BUILD=WebAssembly \
  -DLLVM_ENABLE_RUNTIMES='libcxx;libcxxabi' \
  -DLLVM_RUNTIME_TARGETS=wasm32 \
  -DLLVM_INCLUDE_BENCHMARKS=OFF \
  -DLLVM_INCLUDE_EXAMPLES=OFF \
  -DLLVM_INCLUDE_TESTS=OFF \
  -DLLVM_BUILD_TOOLS=OFF \
  -DLLVM_ENABLE_EH=ON \
  -DLLVM_ENABLE_RTTI=ON

I am not knowledgeable in the LLVM build system so this is the result of a trial and error process which might have produced something sub-optimal. Please let me know if anything stands out.

Now I am hitting a wall. The runtime does not build because:

llvm-project-release_15.x/build/include/c++/v1/__config:924:8: error: "No thread API"

Looking at the code:

// Thread API
// clang-format off
#  if !defined(_LIBCPP_HAS_NO_THREADS) &&                                                                              \
      !defined(_LIBCPP_HAS_THREAD_API_PTHREAD) &&                                                                      \
      !defined(_LIBCPP_HAS_THREAD_API_WIN32) &&                                                                        \
      !defined(_LIBCPP_HAS_THREAD_API_EXTERNAL)

#    if defined(__FreeBSD__) ||                                                                                        \
        defined(__wasi__) ||                                                                                           \
        defined(__NetBSD__) ||                                                                                         \
        defined(__OpenBSD__) ||                                                                                        \
        defined(__NuttX__) ||                                                                                          \
        defined(__linux__) ||                                                                                          \
        defined(__GNU__) ||                                                                                            \
        defined(__APPLE__) ||                                                                                          \
        defined(__sun__) ||                                                                                            \
        defined(__MVS__) ||                                                                                            \
        defined(_AIX) ||                                                                                               \
        defined(__EMSCRIPTEN__)
// clang-format on
#      define _LIBCPP_HAS_THREAD_API_PTHREAD
#    elif defined(__Fuchsia__)
// TODO(44575): Switch to C11 thread API when possible.
#      define _LIBCPP_HAS_THREAD_API_PTHREAD
#    elif defined(_LIBCPP_WIN32API)
#      define _LIBCPP_HAS_THREAD_API_WIN32
#    else
#      error "No thread API"
#    endif // _LIBCPP_HAS_THREAD_API
#  endif   // _LIBCPP_HAS_NO_THREADS

#  if defined(_LIBCPP_HAS_THREAD_API_PTHREAD)
#    if defined(__ANDROID__) && __ANDROID_API__ >= 30
#      define _LIBCPP_HAS_COND_CLOCKWAIT
#    elif defined(_LIBCPP_GLIBC_PREREQ)
#      if _LIBCPP_GLIBC_PREREQ(2, 30)
#        define _LIBCPP_HAS_COND_CLOCKWAIT
#      endif
#    endif
#  endif

Here I guess we are trying to assess which threading library/method we are going to use. Note that I don’t necessarily need threading. Setting LLVM_ENABLE_THREADS=OFF didn’t change anything though.

If anyone can help, it’d be appreciated.

There’s LIBCXX_ENABLE_THREADS?

Indeed that could help:

libcxx/CMakeLists.txt:840:config_define_if_not(LIBCXX_ENABLE_THREADS _LIBCPP_HAS_NO_THREADS)

And then _LIBCPP_HAS_NO_THREADS is used in the aforementioned condition.

But -DLIBCXX_ENABLE_THREADS=OFF doesn’t change anything. I get the same error. Debugging the runtime CMakeFile it seems the variable is not passed to the cmake being called on runtime.

To pass variables there is a specific scheme you do:

-DRUNTIMES_<target>_LIBCXX_ENABLE_THREADS=OFF

See examples here: llvm-project/Fuchsia.cmake at 9415aad6a40fec74296008a25f34164a95c857f4 · llvm/llvm-project · GitHub

Hi Tobias, nice seeing you here.

So again, you come to the rescue. This solved my immediate problem, now that the _LIBCPP_HAS_NO_THREADS flag is set, I no longer have the mentioned errors.

Now I get missing headers when compiling the runtimes.

fatal error: 'time.h' file not found
fatal error: 'stdlib.h' file not found
etc...

I have indeed those standard headers installed in the build directory:

$ find . -name 'stdlib.h'
./include/c++/v1/stdlib.h

But in those, I see #include_next macros:

#include_next <stdlib.h>

My understanding is that this is a pattern to “overload” standard headers. You include the patch header, which going to define a couple of stuff, and then from that header you include the original header with #include_next which is supposed to look in the next include path.

The failing command is invoked this way:

/home/user/tmp/wasm-toolkit/llvm-project-release_15.x/build/./bin/clang++ --target=wasm32 -DLIBCXX_BUILDING_LIBCXXABI -D_LIBCPP_BUILDING_LIBRARY -D_LIBCXXABI_BUILDING_LIBRARY -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/home/user/tmp/wasm-toolkit/llvm-project-release_15.x/libcxxabi/../libcxx/src -I/home/user/tmp/wasm-toolkit/llvm-project-release_15.x/build/include/c++/v1 -I/home/user/tmp/wasm-toolkit/llvm-project-release_15.x/build/include/wasm32/c++/v1 -I/home/user/tmp/wasm-toolkit/llvm-project-release_15.x/libcxxabi/include -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wdelete-non-virtual-dtor -Wno-comment  -Os -DNDEBUG -fPIC -D_DEBUG -std=c++20 -MD -MT libcxxabi/src/CMakeFiles/cxxabi_shared_objects.dir/cxa_aux_runtime.cpp.o -MF CMakeFiles/cxxabi_shared_objects.dir/cxa_aux_runtime.cpp.o.d -o CMakeFiles/cxxabi_shared_objects.dir/cxa_aux_runtime.cpp.o -c /home/user/tmp/wasm-toolkit/llvm-project-release_15.x/libcxxabi/src/cxa_aux_runtime.cpp

So my include order is the following:

-I/home/user/tmp/wasm-toolkit/llvm-project-release_15.x/libcxxabi/../libcxx/src
-I/home/user/tmp/wasm-toolkit/llvm-project-release_15.x/build/include/c++/v1
-I/home/user/tmp/wasm-toolkit/llvm-project-release_15.x/build/include/wasm32/c++/v1
-I/home/user/tmp/wasm-toolkit/llvm-project-release_15.x/libcxxabi/include

But there is no other ‘stdlib.h’ to include though:

$ find . -name 'stdlib.h' | xargs md5sum
348942847c73b28e3a0de2e6a9429bda  ./libcxx/include/stdlib.h
348942847c73b28e3a0de2e6a9429bda  ./build/include/c++/v1/stdlib.h
e3aee166f4c57e61a78aaf410bf94990  ./clang/test/Modules/Inputs/System/usr/include/stdlib.h
8ad2823e340f79f7c001f84f34de757f  ./clang/test/Modules/Inputs/libc-libcxx/sysroot/usr/include/c++/v1/stdlib.h
91d1ab7d41b43c3bb1fe8be1c73a4023  ./clang/test/Modules/Inputs/libc-libcxx/sysroot/usr/include/stdlib.h
3446395c94e7fb45391c11223643d299  ./clang/test/Modules/Inputs/libc-libcxx/include/c++/stdlib.h
91d1ab7d41b43c3bb1fe8be1c73a4023  ./clang/test/Modules/Inputs/libc-libcxx/include/stdlib.h
8bf4e60eba0d954dbe3e2f250aec19ec  ./clang/test/Headers/Inputs/include/stdlib.h
d41d8cd98f00b204e9800998ecf8427e  ./clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/deprecated-headers/stdlib.h
d41d8cd98f00b204e9800998ecf8427e  ./clang-tools-extra/test/clang-tidy/checkers/llvmlibc/Inputs/system/stdlib.h
df15a5ade2078eba4d7e2ef8a1bdbcab  ./clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/stdlib.h

The first two are identical to the one failing (with the #include_next). The others seems to be part of test/extra tools folders. Not relevant I guess.

If stdlib.h is missing does it mean I need to include a c library in LLVM_ENABLE_PROJECT ?

-DLLVM_ENABLE_PROJECTS='clang;lld;libc'

It doesn’t seem to change anything either.

Anyway, the libc present in the llvm folder does not seem to contain this file…