symengine / symengine

SymEngine is a fast symbolic manipulation library, written in C++
https://symengine.org
Other
1.18k stars 282 forks source link

WebAssembly? #1750

Open colbyn opened 3 years ago

colbyn commented 3 years ago

Any thoughts on compiling this to web assembly?

Unlike most other CAS implementations that are tried to some high level runtime, this happens to be implemented in a language that -in theory- can target the web VIA e.g. Emscripten.

(Also I kinda wanna experiment with porting this over to Rust VIA an embedded WASM interpreter for the desktop.)

Anyway I started working on a docker file for this:

FROM ubuntu:18.04

RUN apt update -y
RUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
RUN apt install -y build-essential git tree cmake make
RUN apt install -y software-properties-common \
    && add-apt-repository -y ppa:deadsnakes/ppa \
    && DEBIAN_FRONTEND="noninteractive" apt install -y \
        python3.9 \
        python3.9-distutils \
        python3-setuptools \
        python3-pip \
    && python3.9 -m pip install pip
RUN git clone https://github.com/emscripten-core/emsdk.git \
    && cd emsdk \
    && git pull \
    && ./emsdk install latest \
    && ./emsdk activate latest \
    && chmod +x ./emsdk_env.sh \
    && ./emsdk_env.sh

RUN bash -c "source '/emsdk/emsdk_env.sh'"
RUN echo "/emsdk/emsdk_env.sh" >> /etc/bash.bashrc 
RUN echo "/emsdk/emsdk_env.sh" >> ~/.bashrc
RUN echo "/emsdk/emsdk_env.sh" >> ~/.profile

# # Fallback in case Emscripten isn't activated.
# # This will let use tools offered by this image inside other Docker images
# # (sub-stages) or with custom / no entrypoint
ENV EMSDK=/emsdk \
    EM_CONFIG=/emsdk/.emscripten \
    EMSDK_NODE=/emsdk/node/14.15.5_64bit/bin/node \
    PATH="/emsdk:/emsdk/upstream/emscripten:/emsdk/upstream/bin:/emsdk/node/14.15.5_64bit/bin:${PATH}"

ENV CMAKE_TOOLCHAIN_FILE=/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake

# libgmp-dev

RUN apt install -y libboost-all-dev
RUN python3 /emsdk/upstream/emscripten/embuilder.py build boost_headers

# RUN git clone https://github.com/symengine/symengine.git \
#     && cd symengine \
#     && mkdir build \
#     && cd build \
#     && cmake .. -DINTEGER_CLASS:STRING=boostmp \
#     && make \
#     && make install

# RUN git clone https://github.com/emscripten-ports/boost.git \
#     && cd boost

RUN apt install -y vim

ENV BOOST_INCLUDEDIR=/emsdk/upstream/emscripten/cache/ports/boost_headers

RUN git clone https://github.com/symengine/symengine.git

ADD override/CMakeLists.txt /symengine/CMakeLists.txt

RUN cd symengine \
    && mkdir build \
    && cd build \
    && emcmake cmake .. -DINTEGER_CLASS:STRING=boostmp -DBOOST_INCLUDEDIR=/emsdk/upstream/emscripten/cache/ports/boost_headers -DBoost_INCLUDE_DIR=/emsdk/upstream/emscripten/cache/ports/boost_headers -DDISABLE_EXCEPTION_CATCHING=0 \
    && make

WORKDIR /symengine

(But I’m having issues building such.)

Overall, any thoughts on how best to accommodate this (and on reducing dependencies)?

isuruf commented 3 years ago

(But I’m having issues building such.)

What are the issues?

colbyn commented 3 years ago

Hey @isuruf, well when I run this:

# FROM ubuntu:18.04
# FROM emscripten/emsdk
FROM emscripten/emsdk:2.0.5
# FROM emscripten/emsdk:1.40.1

RUN apt update -y
# RUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
RUN apt install -y \
    build-essential \
    git \
    tree \
    cmake \
    make \
    python3 \
    python3-pip \
    python3-setuptools \
    python3-distutils \
    libgmp-dev \
    binutils-dev \
    vim

RUN git clone https://github.com/symengine/symengine.git

# NOTE: Shouldn’t Emscripten provide this by default?
RUN python3 /emsdk/upstream/emscripten/embuilder.py build boost_headers

# ADD ./wasm/CMakeLists.txt symengine/CMakeLists.txt
# ADD ./wasm/UserOverride.cmake symengine/cmake/UserOverride.cmake

RUN cd symengine && mkdir build

# NOTE:
# -DINTEGER_CLASS:STRING=gmp
# Choose storage type for Integer. one of gmp, gmpxx, flint, piranha, boostmp

RUN cd symengine/build \
    && emcmake cmake \
        -DWITH_BF=OFF \
        -DWITH_SYMENGINE_ASSER=OFF \
        -DWITH_SYMENGINE_RC=ON \
        -DWITH_SYMENGINE_THREAD_SAF=OFF \
        -DWITH_SYMENGINE_ASSERT=OFF \
        -DWITH_EC=OFF \
        -DWITH_PRIMESIEV=OFF \
        -DWITH_FLIN=OFF \
        -DWITH_AR=OFF \
        -DWITH_TCMALLO=OFF \
        -DWITH_OPENM=OFF \
        -DWITH_PIRANH=OFF \
        -DWITH_MPF=OFF \
        -DWITH_MP=OFF \
        -DWITH_LLV=OFF \
        -DBUILD_TEST=OFF \
        -DBUILD_BENCHMARK=OFF \
        -DBUILD_BENCHMARKS_NONIU=OFF \
        -DBUILD_BENCHMARKS_GOOGL=OFF \
        -DINTEGER_CLASS=boostmp \
        -DBUILD_SHARED_LIBS=OFF \
        -DCMAKE_INSTALL_RPATH_USE_LINK_PAT=OFF \
        -DBOOST_INCLUDEDIR=/emsdk/upstream/emscripten/cache/ports/boost_headers \
        -DBoost_INCLUDE_DIR=/emsdk/upstream/emscripten/cache/ports/boost_headers \
        -DDISABLE_EXCEPTION_CATCHING=0 \
        -DERROR_ON_UNDEFINED_SYMBOLS=0 \
        ..

RUN cd symengine/build \
    && emmake make

I get the following error:

#14 0.325 [  1%] Generating CXX unity source symengine/cotire/symengine_CXX_unity.cxx
#14 0.369 [  1%] Generating CXX prefix source symengine/cotire/symengine_CXX_prefix.cxx
#14 1.542 [  2%] Generating CXX prefix header symengine/cotire/symengine_CXX_prefix.hxx
#14 1.574 [  2%] Building CXX precompiled header symengine/cotire/symengine_CXX_prefix.hxx.pch
#14 6.577 Scanning dependencies of target symengine
#14 6.781 [  2%] Building CXX object symengine/CMakeFiles/symengine.dir/add.cpp.o
#14 8.027 In file included from /src/symengine/symengine/add.cpp:1:
#14 8.027 In file included from /src/symengine/build/symengine/cotire/symengine_CXX_prefix.hxx:4:
#14 8.027 In file included from /src/symengine/build/symengine/cotire/symengine_CXX_prefix.cxx:14:
#14 8.027 /emsdk/upstream/emscripten/cache/ports/boost_headers/boost/multiprecision/cpp_int.hpp:1236:7: error: static_assert failed due to requirement 'cpp_int_base<0, 4294967295, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, std::__2::allocator<unsigned long long>, false>::internal_limb_count >= 2' "base_type::internal_limb_count >= 2"
#14 8.027       BOOST_STATIC_ASSERT(base_type::internal_limb_count >= 2);
#14 8.027       ^                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If I remove BOOST_STATIC_ASSERT(base_type::internal_limb_count >= 2);, then I run into undefined symbol: sigaltstack.

I think the issue is with me and not using Emscripten properly.

isuruf commented 3 years ago

I don't know if boost::multiprecision supports emscripten. You can try building gmp and use that instead.

colbyn commented 3 years ago

@isuruf Okay I got it building 🙂

# FROM ubuntu:18.04
# FROM emscripten/emsdk
# FROM emscripten/emsdk:2.0.5
# FROM emscripten/emsdk:1.40.1
FROM emscripten/emsdk:2.0.15

RUN apt update -y
RUN apt install -y \
    build-essential \
    git \
    tree \
    cmake \
    make \
    python3 \
    python3-pip \
    python3-setuptools \
    python3-distutils \
    libgmp-dev \
    binutils-dev \
    nodejs \
    wget \
    lzip \
    file \
    wabt \
    vim

# BUILD BOOST:
RUN python3 /emsdk/upstream/emscripten/embuilder.py build boost_headers
# REMOTE THIS ASSERT?
RUN sed --in-place '/      BOOST_STATIC_ASSERT(base_type::internal_limb_count >= 2);/d' /emsdk/upstream/emscripten/cache/ports/boost_headers/boost/multiprecision/cpp_int.hpp

RUN git clone https://github.com/symengine/symengine.git
RUN wget https://gmplib.org/download/gmp/gmp-6.1.2.tar.lz \
    && tar xf gmp-6.1.2.tar.lz \
    && cd gmp-6.1.2 \
    && emconfigure ./configure --disable-assembly --host none --enable-cxx --prefix=${HOME}/opt/gmp \
    && make \
    && make install

# RUST TOOLCHAIN
RUN curl https://sh.rustup.rs -o rustup.sh && sh rustup.sh --default-toolchain stable -y
ENV PATH="$PATH:/root/.cargo/bin"
RUN rustup default stable
# INSTALL WASM-NM TOOL
RUN cargo install wasm-nm

# INIT BUILD DIR
RUN cd symengine && mkdir build

ADD ./wasm/CMakeLists.txt symengine/CMakeLists.txt
ADD ./wasm/symengine/CMakeLists.txt symengine/symengine/CMakeLists.txt
# ADD ./wasm/UserOverride.cmake symengine/cmake/UserOverride.cmake

RUN cd symengine/build \
    && emcmake cmake \
        -DWITH_BF=OFF \
        -DWITH_SYMENGINE_ASSER=OFF \
        -DWITH_SYMENGINE_RC=OFF \
        -DWITH_SYMENGINE_THREAD_SAF=OFF \
        -DWITH_EC=OFF \
        -DWITH_PRIMESIEV=OFF \
        -DWITH_FLIN=OFF \
        -DWITH_AR=OFF \
        -DWITH_TCMALLO=OFF \
        -DWITH_OPENM=OFF \
        -DWITH_PIRANH=OFF \
        -DWITH_MPF=OFF \
        -DWITH_MP=OFF \
        -DWITH_LLV=OFF \
        -DBUILD_TESTS=OFF \
        -DBUILD_BENCHMARK=OFF \
        -DBUILD_BENCHMARKS=OFF \
        -DBUILD_BENCHMARKS_NONIU=OFF \
        -DBUILD_BENCHMARKS_GOOGL=OFF \
        -DINTEGER_CLASS=gmp \
        -DBUILD_SHARED_LIBS=ON \
        -DCMAKE_INSTALL_RPATH_USE_LINK_PAT=OFF \
        -DBOOST_INCLUDEDIR=/emsdk/upstream/emscripten/cache/ports/boost_headers \
        -DBoost_INCLUDE_DIR=/emsdk/upstream/emscripten/cache/ports/boost_headers \
        -DGMP_LIBRARY=/root/opt/gmp/lib/libgmp.a \
        -DGMP_INCLUDE_DIR=/root/opt/gmp/include \
        -DDISABLE_EXCEPTION_CATCHING=0 \
        -DERROR_ON_UNDEFINED_SYMBOLS=0 \
        -DCMAKE_CXX_FLAGS="-s STANDALONE_WASM -s LINKABLE=1 -s EXPORT_ALL=1" \
        ..

RUN cd symengine/build \
    && emmake make

For some reason, the output is .o instead of .wasm. E.g.

$ file ./symengine/CMakeFiles/symengine.dir/cwrapper.cpp.o
./symengine/CMakeFiles/symengine.dir/cwrapper.cpp.o: WebAssembly (wasm) binary module version 0x1 (MVP)

I'm still trying to figure out how to clean things up a bit, and get the linker (I believe) to output explicit WASM files.

Qix- commented 3 years ago

@colbyn there's a CMake target property SUFFIX exactly for that: https://cmake.org/cmake/help/latest/prop_tgt/SUFFIX.html

Though webassembly in CMake isn't super polished yet, and that isn't a problem of symengine's. For now, using the cmake hacks like SUFFIX should suffice.

rikardn commented 3 years ago

This looks interesting! @colbyn did you manage to call the generated wasm?

Perhaps we should create a symengine.wasm repo with build scripts and perhaps some test code

richardotis commented 2 years ago

Building in part on the discussion this thread, I've made progress in getting symengine (the library) and symengine.py to build emscripten binary packages for use with pyodide: https://github.com/materialsgenomefoundation/mgf-dist-pyodide/tree/6c8267a750e602f35f4d0a90821a1ebf5354ab92/packages

The GHA build workflow (based on pyodide's workflow): https://github.com/materialsgenomefoundation/mgf-dist-pyodide/blob/6c8267a750e602f35f4d0a90821a1ebf5354ab92/.github/workflows/main.yaml

Here's a built zip containing a pyodide distribution: https://github.com/materialsgenomefoundation/mgf-dist-pyodide/suites/6132834909/artifacts/213722216 (link will expire in less than 60 days, I'm working on stable artifact generation)

If you extract that zip to a folder, then serve up the root of that folder with python -m http.server 8000, you can visit http://localhost:8000/console.html and import symengine.