Emscripten dylink and pthread demo
For the purpose of this demo, we will be running googletest as a dynamically loaded library and executing some example tests. To make things more interesting the tests will use threads so that we have to use an exciting new feature: dynamic linking + pthread.
We will use the following unit tests
#include <gtest/gtest.h> // googletest header file
#include <thread>
const char *actualValTrue = "hello gtest";
const char *actualValFalse = "hello world";
const char *expectVal = "hello gtest";
TEST(StrCompare, CStrEqual) {
std::thread([]{
EXPECT_STREQ(expectVal, actualValTrue);
}).join();
}
TEST(StrCompare, CStrNotEqual) {
std::thread([]{
EXPECT_STREQ(expectVal, actualValFalse);
}).join();
}
And here is the result, running right in your browser
About
This demo shows a demo application (a googletest unit test) built using dynamic linking and pthread support in Emscripten.
First, googletest is built as a dynamic library with pthread support.
Then an example test that dynamically links to googletest is also built as a dynamic library with pthread support. Although it is built as dynamic library this module contains the main
entry point.
Finally, an empty file is built as Emscripten’s main module, which hosts the system libraries. This main module dynamically links to the example test library.
The reason to run the main
entry point from a dynamically loaded module is so that global dependencies are constructed in the correct order.
The globals in the main module, which host the system libraries, will be constructed first. Then the globals in the side modules will be constructed following the correct dependency order.
In this case the test creates global objects that depend on the testing framework and needs to be constructed after it. If the test globals were included in the main module they would be constructed before the testing framework globals resulting in undefined behavior. Moving the tests (and the main
entry point) to a dynamic library solves this problem.
Build
Get the source
$ git clone https://github.com/jprendes/emscripten-dylink-demo.git
Then run the following commands to build the demo and launch it in a local web server.
$ docker build -t emscripten-dylink .
$ docker run --rm -it --net=host -v "$(pwd)":"/app/" emscripten-dylink
This will create a docker container with emsdk 2.0.13
and pull a patched version of emscripten from jprendes/emscripten dylink-merged-fixes
that implements some functionalities currently missing from emscripten-core/emscripten master
. The most important of those changes are:
- using the same dynamic library globals across all threads.
- support for thread local storage (required for exceptions, among others).
Then the container will pull a copy of googletest and compile it as a shared library qtest.so
. Then it will compile the example tests as another shared library string-compare.so
which depends on gtest.so
. Finally it will compile an empty file as a main module which will generate main.(html|js|wasm)
as well as main.worker.js
.
Once all modules are compiled, the container will launch a local web server so that you can see the result. You can access it by visiting http://localhost:5000/main.html on your browser. You should see the following output (up to timing differences):
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from StrCompare
[ RUN ] StrCompare.CStrEqual
[ OK ] StrCompare.CStrEqual (614 ms)
[ RUN ] StrCompare.CStrNotEqual
src/string-compare.cpp:19: Failure
Expected equality of these values:
expectVal
Which is: "hello gtest"
actualValFalse
Which is: "hello world"
[ FAILED ] StrCompare.CStrNotEqual (12 ms)
[----------] 2 tests from StrCompare (632 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (643 ms total)
[ PASSED ] 1 test.
[ FAILED ] 1 test, listed below:
[ FAILED ] StrCompare.CStrNotEqual
1 FAILED TEST