Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ venv/
/resources/panda/*.pkl
src/vamp/_core/*.pyi
/.mypy_cache/
build-wasm/
41 changes: 41 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ if(VAMP_FORCE_COLORED_OUTPUT)
endif()
endif()

add_definitions(-w)

project(
vamp
VERSION 0.2
Expand All @@ -52,6 +54,45 @@ include(CompilerSettings)
include(FetchInitCPM)
include(Dependencies)

# Emscripten + Wasm SIMD configuration
if(EMSCRIPTEN)
add_compile_options(-msimd128 -mrelaxed-simd)
add_link_options(-msimd128 -mrelaxed-simd)
# Disable Python bindings for wasm builds
set(VAMP_BUILD_PYTHON_BINDINGS OFF CACHE BOOL "" FORCE)
# Build a minimal wasm smoke test module as an ES module for Node/browser
add_executable(vamp_wasm_smoke src/impl/vamp/bindings/wasm_smoke.cc)
# Emscripten flags: ES module, no filesystem, export smoke and cwrap for script
target_link_options(vamp_wasm_smoke PRIVATE
"SHELL:-s EXPORTED_FUNCTIONS=['_vamp_wasm_smoke']"
"SHELL:-s EXPORTED_RUNTIME_METHODS=['cwrap']"
"SHELL:-s MODULARIZE=1"
"SHELL:-s EXPORT_ES6=1"
"SHELL:-s ENVIRONMENT=web,worker,node"
"SHELL:-s INITIAL_MEMORY=67108864"
"SHELL:-s ALLOW_MEMORY_GROWTH=1"
"SHELL:-s FILESYSTEM=0"
-O3
)
target_link_libraries(vamp_wasm_smoke PRIVATE vamp_cpp)
set_target_properties(vamp_wasm_smoke PROPERTIES OUTPUT_NAME vamp_smoke SUFFIX ".mjs")
add_executable(vamp_wasm_planning src/impl/vamp/bindings/wasm_planning.cc)
# Emscripten flags: ES module, no filesystem, export smoke and cwrap for script
target_link_options(vamp_wasm_planning PRIVATE
"SHELL:-s EXPORTED_FUNCTIONS=['_vamp_wasm_planning']"
"SHELL:-s EXPORTED_RUNTIME_METHODS=['cwrap']"
"SHELL:-s MODULARIZE=1"
"SHELL:-s EXPORT_ES6=1"
"SHELL:-s ENVIRONMENT=web,worker,node"
"SHELL:-s INITIAL_MEMORY=67108864"
"SHELL:-s ALLOW_MEMORY_GROWTH=1"
"SHELL:-s FILESYSTEM=0"
-O3
)
target_link_libraries(vamp_wasm_planning PRIVATE vamp_cpp)
set_target_properties(vamp_wasm_planning PROPERTIES OUTPUT_NAME vamp_planning SUFFIX ".mjs")
endif()

# Create VAMP C++ library
add_library(vamp_cpp INTERFACE)

Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,39 @@ See the `environment.yaml` file for a basic environment, and see `docker/ubuntu2
We currently support x86 CPUs (e.g., Intel, AMD) with the [AVX2 vector instruction set](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions) and ARM CPUs (e.g., Raspberry Pi, Mac M1) with [NEON](https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(Neon)).
Please see the `docker/` folder for reference installation procedures.

### WebAssembly (Wasm) Build
VAMP can be built as a WebAssembly module using Emscripten with Wasm SIMD enabled. Python bindings are disabled for this target.

Requirements:
- Emscripten SDK installed and activated
- Node.js for running the smoke test

Build and run a minimal smoke test:
```bash
# Configure with Emscripten toolchain
emcmake cmake -S . -B build-wasm -DCMAKE_BUILD_TYPE=Release -DVAMP_BUILD_PYTHON_BINDINGS=OFF

# Build headers-only deps and generate compile_commands.json, etc.
cmake --build build-wasm -j

# Link the Wasm smoke test (Node environment)
em++ -O3 -msimd128 -mrelaxed-simd \
-s WASM=1 -s MODULARIZE=1 -s ENVIRONMENT=node \
-s EXPORTED_FUNCTIONS='["_vamp_wasm_smoke"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]' \
-I src/impl \
-o build-wasm/vamp_smoke.mjs \
src/impl/vamp/bindings/wasm_smoke.cc

# Run the Node smoke test
node scripts/wasm_smoke.js
# Expected output: OK 17.000000
```

Notes:
- Wasm SIMD is required (`-msimd128 -mrelaxed-simd`).
- The Wasm build uses the native `<wasm_simd128.h>` backend and selects it automatically under Emscripten.

### Using Clang instead of GCC
You can force the use of Clang instead of GCC for compiling VAMP by uncommenting the line at the bottom of the `pyproject.toml` (or setting the corresponding CMake variable for C++ builds):
```toml
Expand Down
11 changes: 7 additions & 4 deletions cmake/CompilerSettings.cmake
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Allow users to override VAMP_ARCH (e.g., for Docker builds targeting different hardware)
if(NOT DEFINED VAMP_ARCH)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
if(EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "wasm32")
# Emscripten/wasm32: don't use native arch flags; top-level adds -msimd128/-mrelaxed-simd
set(VAMP_ARCH "")
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
# Need explicit AVX2 for some MacOS clang versions
set(VAMP_ARCH "-march=native -mavx2")
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
Expand All @@ -18,7 +21,7 @@ endif()
# default fast args that work on all platforms
set(VAMP_FAST_ARGS "-fno-math-errno -fno-signed-zeros -fno-trapping-math -fno-rounding-math -ffp-contract=fast")

if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") # x86 supports additional flags
if(NOT (EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "wasm32") AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") # x86 supports additional flags
string(APPEND VAMP_FAST_ARGS " -fassociative-math")
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # Clang supports additional fine-grained flags over GCC
string(APPEND VAMP_FAST_ARGS " -fno-honor-infinities -fno-honor-nans")
Expand All @@ -40,13 +43,13 @@ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -g")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 ${VAMP_FAST_ARGS}")

if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
if(NOT (EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "wasm32") AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
# Valgrind can't handle avx512 instructions
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -mno-avx512f")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -mno-avx512f")
endif()

if(VAMP_LTO)
if(VAMP_LTO AND NOT (EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "wasm32"))
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")
Expand Down
9 changes: 9 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ Visualization is enabled by `--visualize`, which shows the plan with the PyBulle

# Other Scripts

## Wasm32 smoke test
If you have [Emscripten](https://emscripten.org) installed and activated (emcc/em++ in PATH), you can build a minimal wasm32 SIMD smoke module and run a quick Node test:

1) Configure a separate build directory using Emscripten's toolchain (e.g., `emcmake cmake -S . -B build-wasm -DCMAKE_BUILD_TYPE=Release`).
2) Build the `vamp_wasm_smoke` target: `cmake --build build-wasm -j`.
3) Run the Node test: `node scripts/wasm_smoke.js`.

This produces `build-wasm/vamp_smoke.mjs` and exercises the Wasm SIMD paths in the collision and vector code.

## `visualize_ompl.py`
If you have the [OMPL](https://ompl.kavrakilab.org/) Python Bindings installed (we recommend using the [pre-built wheels](https://github.com/ompl/ompl/releases/tag/prerelease)) you can also test problems against OMPL using PyBullet's collision checking with this script.

Expand Down
17 changes: 17 additions & 0 deletions scripts/wasm_planning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Minimal Node smoke test for the Wasm SIMD backend
// Usage after building the module (see README):
// node scripts/wasm_planning.js

(async () => {
try {
const mod = await import('../build-wasm/vamp_planning.mjs');
const instance = await mod.default();
const smoke = instance.cwrap('vamp_wasm_planning', 'number', []);
const res = smoke();
console.log('OK', Number(res).toFixed(6));
} catch (e) {
console.error('Planning test failed:', e);
process.exit(1);
}
})();

17 changes: 17 additions & 0 deletions scripts/wasm_smoke.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Minimal Node smoke test for the Wasm SIMD backend
// Usage after building the module (see README):
// node scripts/wasm_smoke.js

(async () => {
try {
const mod = await import('../build-wasm/vamp_smoke.mjs');
const instance = await mod.default();
const smoke = instance.cwrap('vamp_wasm_smoke', 'number', []);
const res = smoke();
console.log('OK', Number(res).toFixed(6));
} catch (e) {
console.error('Smoke test failed:', e);
process.exit(1);
}
})();

115 changes: 115 additions & 0 deletions src/impl/vamp/bindings/wasm_planning.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include <cstdlib>
#include <cstdint>

#include <emscripten/emscripten.h>

#include <vector>
#include <array>
#include <utility>
#include <iostream>
#include <iomanip>

#include <vamp/collision/factory.hh>
#include <vamp/planning/validate.hh>
#include <vamp/planning/rrtc.hh>
#include <vamp/planning/simplify.hh>
#include <vamp/robots/panda.hh>
#include <vamp/random/halton.hh>

extern "C" {

using Robot = vamp::robots::Panda;
static constexpr const std::size_t rake = vamp::FloatVectorWidth;
using EnvironmentInput = vamp::collision::Environment<float>;
using EnvironmentVector = vamp::collision::Environment<vamp::FloatVector<rake>>;
using RRTC = vamp::planning::RRTC<Robot, rake, Robot::resolution>;

// Start and goal configurations
static constexpr Robot::ConfigurationArray start = {0., -0.785, 0., -2.356, 0., 1.571, 0.785};
static constexpr Robot::ConfigurationArray goal = {2.35, 1., 0., -0.8, 0, 2.5, 0.785};

// Spheres for the cage problem - (x, y, z) center coordinates with fixed, common radius defined below
static const std::vector<std::array<float, 3>> problem = {
{0.55, 0, 0.25},
{0.35, 0.35, 0.25},
{0, 0.55, 0.25},
{-0.55, 0, 0.25},
{-0.35, -0.35, 0.25},
{0, -0.55, 0.25},
{0.35, -0.35, 0.25},
{0.35, 0.35, 0.8},
{0, 0.55, 0.8},
{-0.35, 0.35, 0.8},
{-0.55, 0, 0.8},
{-0.35, -0.35, 0.8},
{0, -0.55, 0.8},
{0.35, -0.35, 0.8},
};

// Radius for obstacle spheres
static constexpr float radius = 0.2;

EMSCRIPTEN_KEEPALIVE
float vamp_wasm_planning()
{

std::cout << "Running WASM planning test" << std::endl;

// Build sphere cage environment
EnvironmentInput environment;
for (const auto &sphere : problem)
{
environment.spheres.emplace_back(vamp::collision::factory::sphere::array(sphere, radius));
}

std::cout << "Environment built" << std::endl;

environment.sort();
auto env_v = EnvironmentVector(environment);

std::cout << "Environment vector built" << std::endl;

// Create RNG for planning
auto rng = std::make_shared<vamp::rng::Halton<Robot>>();


std::cout << "RNG built" << std::endl;
// Setup RRTC and plan
vamp::planning::RRTCSettings rrtc_settings;
rrtc_settings.range = 1.0;

auto result =
RRTC::solve(Robot::Configuration(start), Robot::Configuration(goal), env_v, rrtc_settings, rng);

std::cout << "RRTC solved" << std::endl;
std::cout << "result.nanoseconds: " << result.nanoseconds << std::endl;
std::cout << "result.iterations: " << result.iterations << std::endl;
std::cout << "result.size: " << result.size[0] << ", " << result.size[1] << std::endl;
std::cout << "result.path.size: " << result.path.size() << std::endl;

// If successful
if (result.path.size() > 0)
{
// Simplify path with default settings
vamp::planning::SimplifySettings simplify_settings;
auto simplify_result = vamp::planning::simplify<Robot, rake, Robot::resolution>(
result.path, env_v, simplify_settings, rng);

// Output configurations of simplified path
std::cout << std::fixed << std::setprecision(3);
for (const auto &config : simplify_result.path)
{
const auto &array = config.to_array();
for (auto i = 0U; i < Robot::dimension; ++i)
{
std::cout << array[i] << ", ";
}

std::cout << std::endl;
}
}

return 0;
}

} // extern "C"
27 changes: 27 additions & 0 deletions src/impl/vamp/bindings/wasm_smoke.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <cstdlib>
#include <cstdint>

#include <emscripten/emscripten.h>

#include <vamp/vector.hh>
#include <vamp/collision/environment.hh>
#include <vamp/collision/validity.hh>

extern "C" {

EMSCRIPTEN_KEEPALIVE
float vamp_wasm_smoke()
{
using V = vamp::FloatVector<4>;

// Extra vector math to exercise SIMD paths
V a(1.0f);
V b(2.0f);
float vhsum = (a * b + b).hsum(); // 4 lanes of (1*2+2) = 4 -> sum = 16

float checksum = vhsum;
return checksum; // Expected 16.0f if logic holds
}

} // extern "C"

7 changes: 5 additions & 2 deletions src/impl/vamp/random/xorshift.hh
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#pragma once

extern "C"
{
#if !defined(__x86_64__)
#error "XORShift is only supported on x86_64 (AVX)."
#endif

extern "C" {
#include <simdxorshift128plus.h>
}

Expand Down
10 changes: 8 additions & 2 deletions src/impl/vamp/vector.hh
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@
#include <cstdint>
#include <vamp/vector/interface.hh>

#if defined(__x86_64__)
#if defined(__EMSCRIPTEN__) && defined(__wasm_simd128__)
#include <vamp/vector/wasm.hh>
#elif defined(__x86_64__)
#include <vamp/vector/avx.hh>
#elif defined(__ARM_NEON) || defined(__ARM_NEON__)
#include <vamp/vector/neon.hh>
#endif

namespace vamp
{
#if defined(__x86_64__)
#if defined(__EMSCRIPTEN__) && defined(__wasm_simd128__)
using FloatT = float;
using SimdFloatT = wasm_f32x4;
using SimdIntT = wasm_i32x4;
#elif defined(__x86_64__)
using FloatT = float;
using SimdFloatT = __m256;
using SimdIntT = __m256i;
Expand Down
Loading
Loading