Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rachel's proof of concept for Web Assembly with SymForce #121

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ __pycache__/
# Distribution / packaging
.Python
build/
build_wasm/
develop-eggs/
dist/
downloads/
Expand Down
51 changes: 51 additions & 0 deletions symforce/wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Compiling SymForce to Web Assembly

The goal is to create Javascript (ideally Typescript) bindings
for select C++ implementations within [SymForce](https://symforce.org/)
using [Embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html)
so that symforce can be imported and used in JS like any other package. The strategy is similar to how we use pybind for Python.

Step 1: Source the emscripten SDK:
```
source ~/projects/wasymforce/.venv/bin/activate
source ~/projects/emsdk/emsdk_env.sh
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update this. Also look into CMake find package integration

```

Step 2: Run emcmake:
```
mkdir build_wasm
cd build_wasm

emcmake cmake .. \
-DSYMFORCE_BUILD_STATIC_LIBRARIES=ON \
-DSYMFORCE_BUILD_OPT=ON \
-DSYMFORCE_BUILD_CC_SYM=OFF \
-DSYMFORCE_BUILD_EXAMPLES=OFF \
-DSYMFORCE_BUILD_TESTS=OFF \
-DSYMFORCE_BUILD_SYMENGINE=OFF \
-DSYMFORCE_GENERATE_MANIFEST=OFF \
-DSYMFORCE_BUILD_BENCHMARKS=OFF
```

Step 3: Run emmake:
```
emmake make -j 7
```

Step 4: Run embind:
```
emcc -lembind -o symforce_bindings.js ../symforce/wasm/wasm_bindings.cc \
-s LLD_REPORT_UNDEFINED --no-entry \
-Wl,--whole-archive \
./_deps/spdlog-build/libspdlog.a \
./_deps/metis-build/libmetis/libmetis.a \
./_deps/fmtlib-build/libfmt.a \
./libsymforce_gen.a \
./symforce/opt/libsymforce_opt.a \
-Wl,--no-whole-archive \
-I ../gen/cpp \
-I ./_deps/eigen3-src \
-I ./lcmtypes/cpp \
-I ../third_party/skymarshal/include
```

58 changes: 58 additions & 0 deletions symforce/wasm/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<title>SymForce WASM Demo</title>
<style>
body {
padding: 1em;
}
#output {
background-color: rgb(60, 60, 60);
color: rgb(255, 255, 255);
padding: 1em;
}
</style>
</head>

<body>
<div>
<h1>SymForce Web Assembly Demo</h1>
<p>Output:</p>
<div id="output"></div>
</div>

<script>
/**
* Primary test function where sym is available.
*/
function main() {
const R = new sym.Rot3();
write("R:\n", R);

const R_inv = R.inverse();
write("R_inv:\n", R_inv.toString());

write(`R_ypr: ${R.toYawPitchRoll()}`)
const R_mat = R.toRotationMatrix();
write("R_mat:\n", R_mat.toString());
}

/**
* Function that writes to the console and to the div.
*/
function write(...stuff) {
console.log(...stuff);
document.getElementById("output").innerHTML += `<div> ${stuff.join(" ")} </div>`;
}

/**
* Initialization procedure for loading the WASM module under sym and then calling main.
*/
var Module = {
onRuntimeInitialized: main,
};
const sym = Module;
</script>
<script src="./symforce_bindings.js"></script>
</body>
</html>
1 change: 1 addition & 0 deletions symforce/wasm/symforce_bindings.js
1 change: 1 addition & 0 deletions symforce/wasm/symforce_bindings.wasm
57 changes: 57 additions & 0 deletions symforce/wasm/wasm_bindings.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <Eigen/Dense>
#include <emscripten/bind.h>

#include <sym/rot3.h>

using namespace emscripten;

// TODO(rachel): Use emscripten::val for template params. See:
// https://github.com/emscripten-core/emscripten/issues/4887#issuecomment-283285974
EMSCRIPTEN_BINDINGS(symforce)
{
class_<sym::Rot3d>("Rot3")
.constructor<>()
.function("data", &sym::Rot3d::Data)
.function("inverse", &sym::Rot3d::Inverse)
.function("toRotationMatrix", &sym::Rot3d::ToRotationMatrix)
.function("toYawPitchRoll", &sym::Rot3d::ToYawPitchRoll)
.class_function("identity", &sym::Rot3d::Identity)
// TODO(hayk): Figure this out.
// .class_function("fromYawPitchRoll", static_cast<sym::Rot3d (sym::Rot3d::*)(const double, const double, const double)>(&sym::Rot3d::FromYawPitchRoll))
// Added print for testing.
.function("toString", optional_override([](const sym::Rot3d &self) {
std::stringstream buf;
buf << self.Data().transpose() << std::endl;
return buf.str();
}));

// Need Eigen return values.

// TODO(rachel): How to handle overloads? Errors:
// JS: Cannot register public name 'Matrix' twice.
// emcc: template argument for non-type template parameter must be an expression
class_<Eigen::Matrix<double, 3, 3>>("Matrix33")
.constructor<>()
.function("toString", optional_override([](const Eigen::Matrix<double, 3, 3> &self) {
std::stringstream buf;
buf << self << std::endl;
return buf.str();
}));

class_<Eigen::Matrix<double, 3, 1>>("Matrix31")
.constructor<>()
.function("toString", optional_override([](const Eigen::Matrix<double, 3, 1> &self) {
std::stringstream buf;
buf << self << std::endl;
return buf.str();
}));

class_<Eigen::Matrix<double, 4, 1>>("Matrix41")
.constructor<>()
// Added print for testing.
.function("toString", optional_override([](const Eigen::Matrix<double, 4, 1> &self) {
std::stringstream buf;
buf << self << std::endl;
return buf.str();
}));
}