From e099bbcffd30695563c99af58f0df9e5bbc2c530 Mon Sep 17 00:00:00 2001 From: Alexandre Plateau Date: Wed, 25 Jun 2025 17:54:13 +0200 Subject: [PATCH] feat(compiler): adding basic emscripten support --- .github/workflows/ci.yml | 33 ++++++++++++++++ .gitignore | 8 ++++ CMakeLists.txt | 77 +++++++++++++++++++++++++++----------- public/index.html | 63 +++++++++++++++++++++++++++++++ public/server.js | 7 ++++ src/arkemscripten/main.cpp | 27 +++++++++++++ 6 files changed, 194 insertions(+), 21 deletions(-) create mode 100644 public/index.html create mode 100644 public/server.js create mode 100644 src/arkemscripten/main.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e75b4324..63e328e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ concurrency: env: BUILD_TYPE: Debug SQLITE_VERSION: 3390100 # 3.39.1 + EMSDK_VERSION: 4.0.10 jobs: check: @@ -66,6 +67,38 @@ jobs: with: folder: ${{ matrix.path }} + build-with-emscripten: + runs-on: ubuntu-24.04 + name: "Build with emscripten" + needs: [ check ] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup emsdk + uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14 + with: + version: ${{ env.EMSDK_VERSION }} + actions-cache-folder: 'emsdk-cache' + + - name: Verify + run: emcc -v + + - name: Compile + shell: bash + run: | + emcmake cmake -Bbuild \ + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ + -DARK_SANITIZERS=Off \ + -DARK_COVERAGE=Off \ + -DARK_BUILD_EXE=Off \ + -DARK_BUILD_MODULES=Off \ + -DARK_TESTS=Off \ + -DARK_UNITY_BUILD=On + cmake --build build --config ${{ env.BUILD_TYPE }} -j $(nproc) + build: runs-on: ${{ matrix.config.os }} name: ${{ matrix.config.name }} diff --git a/.gitignore b/.gitignore index d1bf528d..0bc2f1f6 100644 --- a/.gitignore +++ b/.gitignore @@ -28,11 +28,19 @@ include/Ark/Constants.hpp /fct-ok/ /fct-bad/ +# WASM +/public/node_modules +/public/*.wasm.wasm +/public/*.wasm.js +/public/package.json +/public/package-lock.json + # Folders .vs/ .idea/ .cache/ build/ +build_emscripten/ ninja/ cmake-build-*/ out/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 16759c23..caef5800 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,11 +23,18 @@ option(ARK_TESTS "Build ArkScript unit tests" Off) option(ARK_BENCHMARKS "Build ArkScript benchmarks" Off) option(ARK_COVERAGE "Enable coverage while building (clang, gcc) (requires ARK_TESTS to be On)" Off) option(ARK_UNITY_BUILD "Enable unity build" Off) +option(ARK_JS_ONLY "Compiles to native JS (No WASM). Can be used only when compiling with emsdk" OFF) -include(cmake/link_time_optimization.cmake) -include(cmake/sanitizers.cmake) +if (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") + set(ARK_EMSCRIPTEN TRUE) +endif () + +if (NOT ARK_EMSCRIPTEN) + include(cmake/link_time_optimization.cmake) + include(cmake/sanitizers.cmake) + include(GNUInstallDirs) # Uses GNU Install directory variables +endif () include(cmake/CPM.cmake) -include(GNUInstallDirs) # Uses GNU Install directory variables # configure installer.iss configure_file( @@ -48,7 +55,9 @@ file(GLOB_RECURSE SOURCE_FILES ${ark_SOURCE_DIR}/lib/fmt/src/format.cc) add_library(ArkReactor SHARED ${SOURCE_FILES}) -enable_lto(ArkReactor) +if (NOT ARK_EMSCRIPTEN) + enable_lto(ArkReactor) +endif () target_include_directories(ArkReactor PUBLIC ${ark_SOURCE_DIR}/include) @@ -59,6 +68,31 @@ if (ARK_UNITY_BUILD) set_source_files_properties(src/arkreactor/VM/VM.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION true) endif () +if (ARK_EMSCRIPTEN) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/public") + + # ArkScript lib needs to know if we are building an exe to enable/disable specific code for embedding + target_compile_definitions(ArkReactor PRIVATE ARK_BUILD_EXE=1) + + add_executable(ArkEmscripten ${ark_SOURCE_DIR}/src/arkemscripten/main.cpp) + target_link_libraries(ArkEmscripten PUBLIC ArkReactor) + target_compile_features(ArkEmscripten PRIVATE cxx_std_20) + + # enable_lto(ArkEmscripten) + + if (ARK_JS_ONLY) + message(STATUS "Setting compilation target to native JavaScript") + set(CMAKE_EXECUTABLE_SUFFIX ".js") + set_target_properties(ArkReactor PROPERTIES LINK_FLAGS "-s WASM=0 -s NO_DISABLE_EXCEPTION_CATCHING") + set_target_properties(ArkEmscripten PROPERTIES LINK_FLAGS "-s WASM=0 -s NO_DISABLE_EXCEPTION_CATCHING -s EXPORTED_FUNCTIONS='[_main]' -s EXPORTED_RUNTIME_METHODS=['run'] -lembind") + else () + message(STATUS "Setting compilation target to WASM") + set(CMAKE_EXECUTABLE_SUFFIX ".wasm.js") + set_target_properties(ArkReactor PROPERTIES LINK_FLAGS "-s WASM=1 -s NO_DISABLE_EXCEPTION_CATCHING") + set_target_properties(ArkEmscripten PROPERTIES LINK_FLAGS "-s WASM=1 -s NO_DISABLE_EXCEPTION_CATCHING -s EXPORTED_FUNCTIONS='[_main]' -s EXPORTED_RUNTIME_METHODS=['run'] -lembind") + endif () +endif () + if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG OR APPLE) message(STATUS "Enabling computed gotos") target_compile_definitions(ArkReactor PRIVATE ARK_USE_COMPUTED_GOTOS=1) @@ -81,7 +115,7 @@ if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG OR APPLE) -fvisibility=hidden -fno-semantic-interposition ) - if (NOT ARK_BENCHMARKS) + if (NOT ARK_BENCHMARKS AND NOT ARK_EMSCRIPTEN) target_compile_options(ArkReactor PRIVATE -Werror) endif () @@ -150,22 +184,23 @@ configure_file( ${ark_SOURCE_DIR}/include/Ark/Constants.hpp) # Installation rules - -# Installs the dynamic library file. -install(TARGETS ArkReactor - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -# Install header files -install(DIRECTORY ${ark_SOURCE_DIR}/include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - -# Install the standard library -if (NOT ARK_NO_STDLIB) - install(DIRECTORY ${ark_SOURCE_DIR}/lib/std/ - DESTINATION ${CMAKE_INSTALL_LIBDIR}/Ark/std - FILES_MATCHING PATTERN "*.ark" - PATTERN "std/tests" EXCLUDE - PATTERN "std/.github" EXCLUDE) +if (NOT ARK_EMSCRIPTEN) + # Installs the dynamic library file. + install(TARGETS ArkReactor + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + + # Install header files + install(DIRECTORY ${ark_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + # Install the standard library + if (NOT ARK_NO_STDLIB) + install(DIRECTORY ${ark_SOURCE_DIR}/lib/std/ + DESTINATION ${CMAKE_INSTALL_LIBDIR}/Ark/std + FILES_MATCHING PATTERN "*.ark" + PATTERN "std/tests" EXCLUDE + PATTERN "std/.github" EXCLUDE) + endif () endif () # COMPILATION RELATED diff --git a/public/index.html b/public/index.html new file mode 100644 index 00000000..e3686e3d --- /dev/null +++ b/public/index.html @@ -0,0 +1,63 @@ + + + + ArkScript emscripten + + +
+ + +
+
+ +
+ + + + + + diff --git a/public/server.js b/public/server.js new file mode 100644 index 00000000..286412ca --- /dev/null +++ b/public/server.js @@ -0,0 +1,7 @@ +// For development purpose only + +const express = require('express'); +const app = express(); + +app.use(express.static('./')); +app.listen(3000); diff --git a/src/arkemscripten/main.cpp b/src/arkemscripten/main.cpp new file mode 100644 index 00000000..db8f1b2b --- /dev/null +++ b/src/arkemscripten/main.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +#include + +extern "C" { +void __attribute__((noinline)) EMSCRIPTEN_KEEPALIVE run(const std::string& code) +{ + Ark::State state; + state.doString(code); + + Ark::VM vm(state); + vm.run(); +} +} + +EMSCRIPTEN_BINDINGS(arkscript) +{ + using namespace emscripten; + function("run", &run); +} + +int main() +{ + return 0; +}