Skip to content

Commit

Permalink
test emscripten-32wasm in the browser (#943)
Browse files Browse the repository at this point in the history
added emscripten-wasm32 tests
  • Loading branch information
DerThorsten authored Oct 6, 2023
1 parent 61d389c commit d5d58db
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 5 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/emscripten.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Emscripten build
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.job }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3

- uses: mamba-org/setup-micromamba@v1
with:
environment-name: xsimd
create-args: >-
microsoft::playwright
python
init-shell: bash



- name: Build script
shell: bash -el {0}
run: |
echo "Build script for wasm"
playwright install
./test/test_wasm/test_wasm.sh
4 changes: 4 additions & 0 deletions include/xsimd/arch/generic/xsimd_generic_math.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -974,8 +974,12 @@ namespace xsimd
template <class A, class T>
inline batch<std::complex<T>, A> polar(const batch<T, A>& r, const batch<T, A>& theta, requires_arch<generic>) noexcept
{
#ifndef EMSCRIPTEN
auto sincosTheta = sincos(theta);
return { r * sincosTheta.second, r * sincosTheta.first };
#else
return { r * cos(theta), r * sin(theta) };
#endif
}

// fdim
Expand Down
2 changes: 1 addition & 1 deletion include/xsimd/math/xsimd_rem_pio2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ namespace xsimd
#define XSIMD_LITTLE_ENDIAN
#endif
#elif defined(_WIN32)
// We can safely assume that Windows is always little endian
// We can safely assume that Windows is always little endian
#define XSIMD_LITTLE_ENDIAN
#elif defined(i386) || defined(i486) || defined(intel) || defined(x86) || defined(i86pc) || defined(__alpha) || defined(__osf__)
#define XSIMD_LITTLE_ENDIAN
Expand Down
12 changes: 10 additions & 2 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU"
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=${TARGET_ARCH} -mtune=${TARGET_ARCH}")
elseif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "riscv64")
# Nothing specific
elseif(NOT WIN32)
elseif(NOT WIN32 AND NOT EMSCRIPTEN)
if(NOT CMAKE_CXX_FLAGS MATCHES "-march" AND NOT CMAKE_CXX_FLAGS MATCHES "-arch" AND NOT CMAKE_OSX_ARCHITECTURES)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=${TARGET_ARCH}")
endif()
Expand Down Expand Up @@ -191,7 +191,7 @@ else()
endif()

if(ENABLE_XTL_COMPLEX)
target_include_directories(test_xsimd PRIVATE ${xtl_INCLUDE_DIRS})
target_include_directories(test_xsimd PRIVATE ${xtl_INCLUDE_DIRS})
endif()
add_test(NAME test_xsimd COMMAND test_xsimd)

Expand All @@ -207,3 +207,11 @@ endif()

add_subdirectory(doc)

if(EMSCRIPTEN)
set_target_properties(test_xsimd PROPERTIES LINK_FLAGS "-s MODULARIZE=1 -s EXPORT_NAME=test_xsimd_wasm -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -lembind")
target_compile_options(test_xsimd
PUBLIC --std=c++14
PUBLIC "SHELL: -msimd128"
PUBLIC "SHELL: -msse2"
)
endif()
20 changes: 19 additions & 1 deletion test/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@
* *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#ifndef EMSCRIPTEN
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest/doctest.h"
#else

#define DOCTEST_CONFIG_IMPLEMENT
#include "doctest/doctest.h"
#include <emscripten/bind.h>

int run_tests()
{
doctest::Context context;
return context.run();
}

EMSCRIPTEN_BINDINGS(my_module)
{
emscripten::function("run_tests", &run_tests);
}

#endif
3 changes: 2 additions & 1 deletion test/test_power.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ struct power_test
INFO("pow");
CHECK_EQ(diff, 0);

#ifdef __SSE__
// use of undeclared identifier '_MM_SET_EXCEPTION_MASK for emscripten
#if defined(__SSE__) && !defined(EMSCRIPTEN)
// Test with FE_INVALID...
unsigned mask = _MM_GET_EXCEPTION_MASK();
_MM_SET_EXCEPTION_MASK(mask & ~_MM_MASK_INVALID);
Expand Down
12 changes: 12 additions & 0 deletions test/test_wasm/browser_main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>TEST_TITLE</title>
<link rel="stylesheet" href="style.css">
<script src="test_xsimd.js"></script>
</head>
<body>
<!-- page content -->
</body>
</html>
43 changes: 43 additions & 0 deletions test/test_wasm/test_wasm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
set -e

# this dir
TEST_WASM_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
SRC_DIR=$TEST_WASM_DIR/../..


# the emsdk dir can be passed as optional argument
# if not passed, it will be downloaded in the current dir
if [ $# -eq 0 ]
then
git clone https://github.com/emscripten-core/emsdk
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

else
EMSCRIPTEN_DIR=$1
source $EMSCRIPTEN_DIR/emsdk_env.sh
fi


export LDFLAGS=""
export CFLAGS=""
export CXXFLAGS=""

# build wasm
mkdir -p build
cd build
emcmake cmake \
-DBUILD_TESTS=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STANDARD=14 \
-DDOWNLOAD_DOCTEST=ON \
$SRC_DIR

emmake make -j4
cd ..

# run tests in browser
python $TEST_WASM_DIR/test_wasm_playwright.py build/test
123 changes: 123 additions & 0 deletions test/test_wasm/test_wasm_playwright.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@

from tempfile import TemporaryDirectory
import shutil
import socket
import threading
from contextlib import closing, contextmanager
from http.server import HTTPServer, SimpleHTTPRequestHandler
import os
import asyncio
from pathlib import Path
from playwright.async_api import async_playwright

THIS_DIR = os.path.dirname(os.path.realpath(__file__))
WORK_DIR = os.path.join(THIS_DIR, "work_dir")


def find_free_port():
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.bind(("", 0))
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
return s.getsockname()[1]


def start_server(work_dir, port):
class Handler(SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=work_dir, **kwargs)

def log_message(self, fmt, *args):
return

httpd = HTTPServer(("127.0.0.1", port), Handler)

thread = threading.Thread(target=httpd.serve_forever)
thread.start()
return thread, httpd


@contextmanager
def server_context(work_dir, port):
thread, server = start_server(work_dir=work_dir, port=port)
try:
yield server, f"http://127.0.0.1:{port}"
finally:
server.shutdown()
thread.join()

async def playwright_run_page(page_url, headless=True, slow_mo=None):
async with async_playwright() as p:
if slow_mo is None:
browser = await p.chromium.launch(headless=headless)
else:
browser = await p.chromium.launch(
headless=headless, slow_mo=slow_mo
)
page = await browser.new_page()
await page.goto(page_url)
# n min = n_min * 60 * 1000 ms
n_min = 4
page.set_default_timeout(n_min * 60 * 1000)

async def handle_console(msg):
txt = str(msg)
print(txt)

page.on("console", handle_console)


status = await page.evaluate(
f"""async () => {{
let test_module = await test_xsimd_wasm();
console.log("\\n\\n************************************************************");
console.log("XSIMD WASM TESTS:");
console.log("************************************************************");
let r = test_module.run_tests();
if (r == 0) {{
console.log("\\n\\n************************************************************");
console.log("XSIMD WASM TESTS PASSED");
console.log("************************************************************");
return r;
}}
else {{
console.log("************************************************************");
console.log("XSIMD WASM TESTS FAILED");
console.log("************************************************************");
return r;
}}
}}"""
)
return_code = int(status)
return return_code
def main(build_dir):

work_dir = WORK_DIR# TemporaryDirectory()

with TemporaryDirectory() as temp_dir:
work_dir = Path(temp_dir)


shutil.copy(f"{build_dir}/test_xsimd.wasm", work_dir)
shutil.copy(f"{build_dir}/test_xsimd.js", work_dir)
shutil.copy(f"{THIS_DIR}/browser_main.html", work_dir)

port = find_free_port()
with server_context(work_dir=work_dir, port=port) as (server, url):
page_url = f"{url}/browser_main.html"
ret = asyncio.run(playwright_run_page(page_url=page_url))

return ret



if __name__ == "__main__":
import sys

# get arg from args
build_dir = sys.argv[1]

print(f"build_dir: {build_dir}")

ret_code = main(build_dir)
sys.exit(ret_code)
9 changes: 9 additions & 0 deletions test/test_xsimd_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,11 @@ struct xsimd_api_float_types_functions
void test_exp10()
{
value_type val(2);
#ifdef EMSCRIPTEN
CHECK_EQ(extract(xsimd::exp10(T(val))), doctest::Approx(std::pow(value_type(10), val)));
#else
CHECK_EQ(extract(xsimd::exp10(T(val))), std::pow(value_type(10), val));
#endif
}
void test_exp2()
{
Expand Down Expand Up @@ -661,7 +665,12 @@ struct xsimd_api_float_types_functions
{
value_type val0(3);
value_type val1(4);
#ifndef EMSCRIPTEN
CHECK_EQ(extract(xsimd::polar(T(val0), T(val1))), std::polar(val0, val1));
#else
CHECK_EQ(std::real(extract(xsimd::polar(T(val0), T(val1)))), doctest::Approx(std::real(std::polar(val0, val1))));
CHECK_EQ(std::imag(extract(xsimd::polar(T(val0), T(val1)))), doctest::Approx(std::imag(std::polar(val0, val1))));
#endif
}
void test_pow()
{
Expand Down

0 comments on commit d5d58db

Please sign in to comment.