Skip to content

Commit ea4690f

Browse files
committed
feat(compiler): adding basic emscripten support
1 parent f71ee68 commit ea4690f

File tree

6 files changed

+194
-21
lines changed

6 files changed

+194
-21
lines changed

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ concurrency:
3030
env:
3131
BUILD_TYPE: Debug
3232
SQLITE_VERSION: 3390100 # 3.39.1
33+
EMSDK_VERSION: 4.0.10
3334

3435
jobs:
3536
check:
@@ -66,6 +67,38 @@ jobs:
6667
with:
6768
folder: ${{ matrix.path }}
6869

70+
build-with-emscripten:
71+
runs-on: ubuntu-24.04
72+
name: "Build with emscripten"
73+
needs: [ check ]
74+
75+
steps:
76+
- uses: actions/checkout@v4
77+
with:
78+
submodules: recursive
79+
80+
- name: Setup emsdk
81+
uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14
82+
with:
83+
version: ${{ env.EMSDK_VERSION }}
84+
actions-cache-folder: 'emsdk-cache'
85+
86+
- name: Verify
87+
run: emcc -v
88+
89+
- name: Compile
90+
shell: bash
91+
run: |
92+
emcmake cmake -Bbuild \
93+
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
94+
-DARK_SANITIZERS=Off \
95+
-DARK_COVERAGE=Off \
96+
-DARK_BUILD_EXE=Off \
97+
-DARK_BUILD_MODULES=Off \
98+
-DARK_TESTS=Off \
99+
-DARK_UNITY_BUILD=On
100+
cmake --build build --config ${{ env.BUILD_TYPE }} -j $(nproc)
101+
69102
build:
70103
runs-on: ${{ matrix.config.os }}
71104
name: ${{ matrix.config.name }}

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,19 @@ include/Ark/Constants.hpp
2828
/fct-ok/
2929
/fct-bad/
3030

31+
# WASM
32+
/public/node_modules
33+
/public/*.wasm.wasm
34+
/public/*.wasm.js
35+
/public/package.json
36+
/public/package-lock.json
37+
3138
# Folders
3239
.vs/
3340
.idea/
3441
.cache/
3542
build/
43+
build_emscripten/
3644
ninja/
3745
cmake-build-*/
3846
out/

CMakeLists.txt

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,18 @@ option(ARK_TESTS "Build ArkScript unit tests" Off)
2323
option(ARK_BENCHMARKS "Build ArkScript benchmarks" Off)
2424
option(ARK_COVERAGE "Enable coverage while building (clang, gcc) (requires ARK_TESTS to be On)" Off)
2525
option(ARK_UNITY_BUILD "Enable unity build" Off)
26+
option(ARK_JS_ONLY "Compiles to native JS (No WASM). Can be used only when compiling with emsdk" OFF)
2627

27-
include(cmake/link_time_optimization.cmake)
28-
include(cmake/sanitizers.cmake)
28+
if (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
29+
set(ARK_EMSCRIPTEN TRUE)
30+
endif ()
31+
32+
if (NOT ARK_EMSCRIPTEN)
33+
include(cmake/link_time_optimization.cmake)
34+
include(cmake/sanitizers.cmake)
35+
include(GNUInstallDirs) # Uses GNU Install directory variables
36+
endif ()
2937
include(cmake/CPM.cmake)
30-
include(GNUInstallDirs) # Uses GNU Install directory variables
3138

3239
# configure installer.iss
3340
configure_file(
@@ -48,7 +55,9 @@ file(GLOB_RECURSE SOURCE_FILES
4855
${ark_SOURCE_DIR}/lib/fmt/src/format.cc)
4956

5057
add_library(ArkReactor SHARED ${SOURCE_FILES})
51-
enable_lto(ArkReactor)
58+
if (NOT ARK_EMSCRIPTEN)
59+
enable_lto(ArkReactor)
60+
endif ()
5261
target_include_directories(ArkReactor
5362
PUBLIC
5463
${ark_SOURCE_DIR}/include)
@@ -59,6 +68,31 @@ if (ARK_UNITY_BUILD)
5968
set_source_files_properties(src/arkreactor/VM/VM.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION true)
6069
endif ()
6170

71+
if (ARK_EMSCRIPTEN)
72+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/public")
73+
74+
# ArkScript lib needs to know if we are building an exe to enable/disable specific code for embedding
75+
target_compile_definitions(ArkReactor PRIVATE ARK_BUILD_EXE=1)
76+
77+
add_executable(ArkEmscripten ${ark_SOURCE_DIR}/src/arkemscripten/main.cpp)
78+
target_link_libraries(ArkEmscripten PUBLIC ArkReactor)
79+
target_compile_features(ArkEmscripten PRIVATE cxx_std_20)
80+
81+
# enable_lto(ArkEmscripten)
82+
83+
if (ARK_JS_ONLY)
84+
message(STATUS "Setting compilation target to native JavaScript")
85+
set(CMAKE_EXECUTABLE_SUFFIX ".js")
86+
set_target_properties(ArkReactor PROPERTIES LINK_FLAGS "-s WASM=0 -s NO_DISABLE_EXCEPTION_CATCHING")
87+
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")
88+
else ()
89+
message(STATUS "Setting compilation target to WASM")
90+
set(CMAKE_EXECUTABLE_SUFFIX ".wasm.js")
91+
set_target_properties(ArkReactor PROPERTIES LINK_FLAGS "-s WASM=1 -s NO_DISABLE_EXCEPTION_CATCHING")
92+
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")
93+
endif ()
94+
endif ()
95+
6296
if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG OR APPLE)
6397
message(STATUS "Enabling computed gotos")
6498
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)
81115
-fvisibility=hidden
82116
-fno-semantic-interposition
83117
)
84-
if (NOT ARK_BENCHMARKS)
118+
if (NOT ARK_BENCHMARKS AND NOT ARK_EMSCRIPTEN)
85119
target_compile_options(ArkReactor PRIVATE -Werror)
86120
endif ()
87121

@@ -150,22 +184,23 @@ configure_file(
150184
${ark_SOURCE_DIR}/include/Ark/Constants.hpp)
151185

152186
# Installation rules
153-
154-
# Installs the dynamic library file.
155-
install(TARGETS ArkReactor
156-
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
157-
158-
# Install header files
159-
install(DIRECTORY ${ark_SOURCE_DIR}/include/
160-
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
161-
162-
# Install the standard library
163-
if (NOT ARK_NO_STDLIB)
164-
install(DIRECTORY ${ark_SOURCE_DIR}/lib/std/
165-
DESTINATION ${CMAKE_INSTALL_LIBDIR}/Ark/std
166-
FILES_MATCHING PATTERN "*.ark"
167-
PATTERN "std/tests" EXCLUDE
168-
PATTERN "std/.github" EXCLUDE)
187+
if (NOT ARK_EMSCRIPTEN)
188+
# Installs the dynamic library file.
189+
install(TARGETS ArkReactor
190+
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
191+
192+
# Install header files
193+
install(DIRECTORY ${ark_SOURCE_DIR}/include/
194+
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
195+
196+
# Install the standard library
197+
if (NOT ARK_NO_STDLIB)
198+
install(DIRECTORY ${ark_SOURCE_DIR}/lib/std/
199+
DESTINATION ${CMAKE_INSTALL_LIBDIR}/Ark/std
200+
FILES_MATCHING PATTERN "*.ark"
201+
PATTERN "std/tests" EXCLUDE
202+
PATTERN "std/.github" EXCLUDE)
203+
endif ()
169204
endif ()
170205

171206
# COMPILATION RELATED

public/index.html

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>ArkScript emscripten</title>
5+
</head>
6+
<body>
7+
<div>
8+
<textarea id="code" cols="80" rows="10"></textarea>
9+
<button id="run">Submit</button>
10+
</div>
11+
<div>
12+
<textarea id="output" cols="80" rows="10" readonly></textarea>
13+
</div>
14+
15+
<script>
16+
let output = "";
17+
log = console.log;
18+
window.console.log = (message) => {
19+
output = message;
20+
};
21+
22+
var Module = {
23+
onRuntimeInitialized: function () {
24+
document.getElementById("run").addEventListener("click", () => {
25+
const code = document.getElementById("code").value;
26+
console.log(code);
27+
28+
try {
29+
Module.run(code);
30+
document.getElementById("output").innerText = output;
31+
} catch (e) {
32+
document.getElementById("output").innerText = e;
33+
}
34+
});
35+
}
36+
};
37+
</script>
38+
39+
<script>
40+
(function (e, t) {
41+
console.log("Loading client...");
42+
43+
const scriptName = `ArkEmscripten${e.WebAssembly ? ".wasm" : ""}.js`;
44+
console.log(`Script set to ${scriptName}`);
45+
46+
const loader = t.createElement("script");
47+
loader.async = true;
48+
loader.type = "text/javascript";
49+
loader.src = scriptName;
50+
loader.onerror = (t) => {
51+
console.error("Script Error");
52+
console.error(t);
53+
setTimeout(() => {
54+
e.location.reload();
55+
}, 3000);
56+
};
57+
58+
const node = t.getElementsByTagName("script")[0];
59+
node.parentNode.insertBefore(loader, node);
60+
})(window, document);
61+
</script>
62+
</body>
63+
</html>

public/server.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// For development purpose only
2+
3+
const express = require('express');
4+
const app = express();
5+
6+
app.use(express.static('./'));
7+
app.listen(3000);

src/arkemscripten/main.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include <Ark/Ark.hpp>
2+
#include <emscripten.h>
3+
#include <emscripten/bind.h>
4+
5+
#include <string>
6+
7+
extern "C" {
8+
void __attribute__((noinline)) EMSCRIPTEN_KEEPALIVE run(const std::string& code)
9+
{
10+
Ark::State state;
11+
state.doString(code);
12+
13+
Ark::VM vm(state);
14+
vm.run();
15+
}
16+
}
17+
18+
EMSCRIPTEN_BINDINGS(arkscript)
19+
{
20+
using namespace emscripten;
21+
function("run", &run);
22+
}
23+
24+
int main()
25+
{
26+
return 0;
27+
}

0 commit comments

Comments
 (0)