From 80ea0f7f09de61518da7ed1ea6ccafc8e224e617 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 14 Sep 2026 11:52:57 -0500 Subject: [PATCH 01/20] Fix Emscripten build errors --- src/libraries/JANA/Engine/JExecutionEngine.cc | 4 ++ src/libraries/JANA/Utils/JBacktrace.cc | 55 +++++++++++++++++++ src/libraries/JANA/Utils/JCpuInfo.cc | 5 +- src/programs/perf_tests/BasicTopology.cc | 2 +- src/programs/perf_tests/SourceTopology.cc | 2 +- src/programs/perf_tests/TapChainTopology.cc | 2 +- src/programs/perf_tests/UnfoldTopology.cc | 2 +- 7 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/libraries/JANA/Engine/JExecutionEngine.cc b/src/libraries/JANA/Engine/JExecutionEngine.cc index 60fd6a94e..382378136 100644 --- a/src/libraries/JANA/Engine/JExecutionEngine.cc +++ b/src/libraries/JANA/Engine/JExecutionEngine.cc @@ -324,7 +324,9 @@ void JExecutionEngine::HandleFailures() { if (worker->is_timed_out) { std::string arrow_name = (worker->last_arrow_id == static_cast(-1)) ? "(none)" : m_topology->GetArrows()[worker->last_arrow_id]->GetName(); LOG_FATAL(GetLogger()) << "Timeout in worker thread " << worker->worker_id << " while executing " << arrow_name << " on event #" << worker->last_event_nr << LOG_END; +#ifndef __EMSCRIPTEN__ pthread_kill(worker->thread->native_handle(), SIGUSR2); +#endif LOG_INFO(GetLogger()) << "Worker thread signalled; waiting for backtrace capture." << LOG_END; worker->backtrace.WaitForCapture(); } @@ -821,7 +823,9 @@ void JExecutionEngine::PrintWorkerReport(bool send_to_pipe) { LOG_INFO(GetLogger()) << "Generating worker report. It may take some time to retrieve each symbol's debug information." << LOG_END; for (auto& worker: m_worker_states) { worker->backtrace.Reset(); +#ifndef __EMSCRIPTEN__ pthread_kill(worker->thread->native_handle(), SIGUSR2); +#endif } for (auto& worker: m_worker_states) { worker->backtrace.WaitForCapture(); diff --git a/src/libraries/JANA/Utils/JBacktrace.cc b/src/libraries/JANA/Utils/JBacktrace.cc index 6ccbdb6e4..94550488e 100644 --- a/src/libraries/JANA/Utils/JBacktrace.cc +++ b/src/libraries/JANA/Utils/JBacktrace.cc @@ -5,6 +5,60 @@ #include "JBacktrace.h" +#ifdef __EMSCRIPTEN__ +#include + +JBacktrace::JBacktrace(const JBacktrace& other) { + m_ready = other.m_ready.load(); + m_frame_count = other.m_frame_count; + m_frames_to_omit = other.m_frames_to_omit; + m_buffer = other.m_buffer; +} + +JBacktrace& JBacktrace::operator=(const JBacktrace& other) { + m_ready = other.m_ready.load(); + m_frame_count = other.m_frame_count; + m_frames_to_omit = other.m_frames_to_omit; + m_buffer = other.m_buffer; + return *this; +} + +JBacktrace::JBacktrace(JBacktrace&& other) { + m_ready = other.m_ready.load(); + m_frame_count = other.m_frame_count; + m_frames_to_omit = other.m_frames_to_omit; + m_buffer = std::move(other.m_buffer); +} + +void JBacktrace::Reset() { + m_ready = false; +} + +void JBacktrace::WaitForCapture() const { +} + +void JBacktrace::Capture(int frames_to_omit) { + m_frame_count = 0; + m_frames_to_omit = frames_to_omit; + m_ready.store(true, std::memory_order_release); +} + +void JBacktrace::Format(std::ostream& os) const { + os << "Backtrace not supported on Emscripten." << std::endl; +} + +std::string JBacktrace::AddrToLineInfo(const char* filename, size_t offset) const { + return ""; +} + +std::string JBacktrace::ToString() const { + std::ostringstream oss; + Format(oss); + return oss.str(); +} + +#else + #include #include #include @@ -130,5 +184,6 @@ std::string JBacktrace::ToString() const { return oss.str(); } +#endif diff --git a/src/libraries/JANA/Utils/JCpuInfo.cc b/src/libraries/JANA/Utils/JCpuInfo.cc index 6bd72b432..0aa9815fb 100644 --- a/src/libraries/JANA/Utils/JCpuInfo.cc +++ b/src/libraries/JANA/Utils/JCpuInfo.cc @@ -75,7 +75,9 @@ uint32_t GetCpuID() { bool PinThreadToCpu(std::thread* thread, size_t cpu_id) { - +#ifdef __EMSCRIPTEN__ + return false; +#else if (typeid(std::thread::native_handle_type) != typeid(pthread_t)) { return false; } @@ -99,6 +101,7 @@ bool PinThreadToCpu(std::thread* thread, size_t cpu_id) { } #endif return true; +#endif // __EMSCRIPTEN__ } } // JCpuInfo namespace diff --git a/src/programs/perf_tests/BasicTopology.cc b/src/programs/perf_tests/BasicTopology.cc index c9e21a9aa..a7492a474 100644 --- a/src/programs/perf_tests/BasicTopology.cc +++ b/src/programs/perf_tests/BasicTopology.cc @@ -29,7 +29,7 @@ struct PESrc : public JEventSource { data_out.SetShortName("1"); } JEventSource::Result Emit(JEvent& event) override { - data_out().push_back(new Data {event.GetEventNumber()*3 }); + data_out().push_back(new Data {static_cast(event.GetEventNumber()*3) }); JBenchUtils::consume_cpu_us(*latency_us); return Result::Success; }; diff --git a/src/programs/perf_tests/SourceTopology.cc b/src/programs/perf_tests/SourceTopology.cc index 63d603d49..1446de864 100644 --- a/src/programs/perf_tests/SourceTopology.cc +++ b/src/programs/perf_tests/SourceTopology.cc @@ -25,7 +25,7 @@ struct Src : public JEventSource { data_out.SetShortName("1"); } JEventSource::Result Emit(JEvent& block) override { - data_out().push_back(new Data {block.GetEventNumber()*3 }); + data_out().push_back(new Data {static_cast(block.GetEventNumber()*3) }); JBenchUtils::consume_cpu_us(*latency_us); return Result::Success; }; diff --git a/src/programs/perf_tests/TapChainTopology.cc b/src/programs/perf_tests/TapChainTopology.cc index b38dc9363..ce879093f 100644 --- a/src/programs/perf_tests/TapChainTopology.cc +++ b/src/programs/perf_tests/TapChainTopology.cc @@ -25,7 +25,7 @@ struct Src : public JEventSource { data_out.SetShortName("1"); } JEventSource::Result Emit(JEvent& evt) override { - data_out().push_back(new Data {evt.GetEventNumber()*3 }); + data_out().push_back(new Data {static_cast(evt.GetEventNumber()*3) }); JBenchUtils::consume_cpu_us(*latency_us); return Result::Success; }; diff --git a/src/programs/perf_tests/UnfoldTopology.cc b/src/programs/perf_tests/UnfoldTopology.cc index 918fc41e9..0cb886f97 100644 --- a/src/programs/perf_tests/UnfoldTopology.cc +++ b/src/programs/perf_tests/UnfoldTopology.cc @@ -27,7 +27,7 @@ struct BSrc : public JEventSource { data_out.SetShortName("1"); } JEventSource::Result Emit(JEvent& block) override { - data_out().push_back(new Data {block.GetEventNumber()*3 }); + data_out().push_back(new Data {static_cast(block.GetEventNumber()*3) }); JBenchUtils::consume_cpu_us(*latency_us); return Result::Success; }; From d91868ba5997c0e4f849747003dfe1e789bf4331 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 14 Sep 2026 14:09:29 -0500 Subject: [PATCH 02/20] WASM: Disable sched_getcpu usage in JCpuInfo to fix linking error --- src/libraries/JANA/Utils/JCpuInfo.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/JANA/Utils/JCpuInfo.cc b/src/libraries/JANA/Utils/JCpuInfo.cc index 0aa9815fb..b6d5248b3 100644 --- a/src/libraries/JANA/Utils/JCpuInfo.cc +++ b/src/libraries/JANA/Utils/JCpuInfo.cc @@ -68,6 +68,8 @@ uint32_t GetCpuID() { #endif // __cpuid_count return cpuid; +#elif defined(__EMSCRIPTEN__) + return 0; #else //__APPLE__ return sched_getcpu(); #endif //__APPLE__ From 7455a8881ebdf788195d90704c3f197f9457dd56 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 14 Sep 2026 17:30:28 -0500 Subject: [PATCH 03/20] WASM: Conditionally build ROOT plugins --- src/plugins/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index f3db8d855..f4df3e0ce 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -1,10 +1,10 @@ - add_subdirectory(JTest) add_subdirectory(janacontrol) add_subdirectory(janadot) add_subdirectory(janarate) -add_subdirectory(janaroot) -add_subdirectory(janaview) +if(USE_ROOT) + add_subdirectory(janaroot) + add_subdirectory(janaview) +endif() add_subdirectory(regressiontest) add_subdirectory(perfetto) - From 00d8028842dde8c6fc64766dc2ef4b7133810c03 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 14 Sep 2026 17:33:54 -0500 Subject: [PATCH 04/20] WASM: Do not force USE_ROOT=ON when USE_PODIO=ON --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3aead24a6..e3c433105 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ endif() if (${USE_PODIO}) find_package(podio REQUIRED) set(JANA2_HAVE_PODIO 1) - set(USE_ROOT ON) + # set(USE_ROOT ON) # Disabled for WASM include_directories(SYSTEM ${podio_INCLUDE_DIR}) else() set(JANA2_HAVE_PODIO 0) From 60c7d77ff04a54c451f5718f8e3d9b8bcbfccdb0 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 14 Sep 2026 17:42:10 -0500 Subject: [PATCH 05/20] WASM: Conditional linking to podioRootIO based on USE_ROOT --- src/libraries/JANA/CMakeLists.txt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/libraries/JANA/CMakeLists.txt b/src/libraries/JANA/CMakeLists.txt index e2e5a9fec..3917fee24 100644 --- a/src/libraries/JANA/CMakeLists.txt +++ b/src/libraries/JANA/CMakeLists.txt @@ -83,7 +83,10 @@ if (USE_PERFETTO) endif() if (${USE_PODIO}) - target_link_libraries(jana2 PUBLIC podio::podio podio::podioRootIO) + target_link_libraries(jana2 PUBLIC podio::podio) + if(USE_ROOT) + target_link_libraries(jana2 PUBLIC podio::podioRootIO) + endif() elseif (${USE_ROOT}) target_link_libraries(jana2 PUBLIC ROOT::Core) endif() @@ -102,7 +105,10 @@ if (USE_PERFETTO) endif() if (${USE_PODIO}) - target_link_libraries(jana2_static_lib PUBLIC podio::podio podio::podioRootIO) + target_link_libraries(jana2_static_lib PUBLIC podio::podio) + if(USE_ROOT) + target_link_libraries(jana2_static_lib PUBLIC podio::podioRootIO) + endif() elseif (${USE_ROOT}) target_link_libraries(jana2_static_lib PUBLIC ROOT::Core) endif() @@ -122,7 +128,10 @@ if (USE_PERFETTO) endif() if (${USE_PODIO}) - target_link_libraries(jana2_shared_lib PUBLIC podio::podio podio::podioRootIO) + target_link_libraries(jana2_shared_lib PUBLIC podio::podio) + if(USE_ROOT) + target_link_libraries(jana2_shared_lib PUBLIC podio::podioRootIO) + endif() elseif (${USE_ROOT}) target_link_libraries(jana2_shared_lib PUBLIC ROOT::Core) endif() From f7d3604077f15a88d3286dfa269b2645b0d423d2 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Wed, 16 Sep 2026 08:46:46 -0500 Subject: [PATCH 06/20] wasm: build janaview only if ROOT::Gui is available --- src/plugins/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index f4df3e0ce..505e1f940 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -4,7 +4,9 @@ add_subdirectory(janadot) add_subdirectory(janarate) if(USE_ROOT) add_subdirectory(janaroot) - add_subdirectory(janaview) + if(TARGET ROOT::Gui) + add_subdirectory(janaview) + endif() endif() add_subdirectory(regressiontest) add_subdirectory(perfetto) From 7051055fc446f29bc4ac6f651774f4555d162e6a Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 19 Sep 2026 12:17:18 -0500 Subject: [PATCH 07/20] wasm: Enable dynamic linking for Emscripten MAIN/SIDE modules --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3c433105..f1c8c5075 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,12 @@ cmake_minimum_required(VERSION 3.16) cmake_policy(SET CMP0074 NEW) # find_package() uses _ROOT implicit hints project(jana2 VERSION 2026.03.01) +if(EMSCRIPTEN) + set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE) + string(APPEND CMAKE_EXE_LINKER_FLAGS " -sMAIN_MODULE=1") + string(APPEND CMAKE_SHARED_LINKER_FLAGS " -sSIDE_MODULE=1") + string(APPEND CMAKE_MODULE_LINKER_FLAGS " -sSIDE_MODULE=1") +endif() set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Enable -fPIC for all targets From f043aff74bac53c314b97071628669a4494e5337 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 19 Sep 2026 12:17:56 -0500 Subject: [PATCH 08/20] wasm: Setup GH Pages for proof-of-concept JANA web execution --- .github/wasm-pages/coi-serviceworker.js | 146 ++++++++++++++++++++++++ .github/wasm-pages/index.html | 70 ++++++++++++ .github/workflows/wasm-pages.yml | 69 +++++++++++ 3 files changed, 285 insertions(+) create mode 100644 .github/wasm-pages/coi-serviceworker.js create mode 100644 .github/wasm-pages/index.html create mode 100644 .github/workflows/wasm-pages.yml diff --git a/.github/wasm-pages/coi-serviceworker.js b/.github/wasm-pages/coi-serviceworker.js new file mode 100644 index 000000000..9901474cc --- /dev/null +++ b/.github/wasm-pages/coi-serviceworker.js @@ -0,0 +1,146 @@ +/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */ +let coepCredentialless = false; +if (typeof window === 'undefined') { + self.addEventListener("install", () => self.skipWaiting()); + self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim())); + + self.addEventListener("message", (ev) => { + if (!ev.data) { + return; + } else if (ev.data.type === "deregister") { + self.registration + .unregister() + .then(() => { + return self.clients.matchAll(); + }) + .then(clients => { + clients.forEach((client) => client.navigate(client.url)); + }); + } else if (ev.data.type === "coepCredentialless") { + coepCredentialless = ev.data.value; + } + }); + + self.addEventListener("fetch", function (event) { + const r = event.request; + if (r.cache === "only-if-cached" && r.mode !== "same-origin") { + return; + } + + const request = (coepCredentialless && r.mode === "no-cors") + ? new Request(r, { + credentials: "omit", + }) + : r; + event.respondWith( + fetch(request) + .then((response) => { + if (response.status === 0) { + return response; + } + + const newHeaders = new Headers(response.headers); + newHeaders.set("Cross-Origin-Embedder-Policy", + coepCredentialless ? "credentialless" : "require-corp" + ); + if (!coepCredentialless) { + newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin"); + } + newHeaders.set("Cross-Origin-Opener-Policy", "same-origin"); + + return new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: newHeaders, + }); + }) + .catch((e) => console.error(e)) + ); + }); + +} else { + (() => { + const reloadedBySelf = window.sessionStorage.getItem("coiReloadedBySelf"); + window.sessionStorage.removeItem("coiReloadedBySelf"); + const coepDegrading = (reloadedBySelf == "coepdegrade"); + + // You can customize the behavior of this script through a global `coi` variable. + const coi = { + shouldRegister: () => !reloadedBySelf, + shouldDeregister: () => false, + coepCredentialless: () => true, + coepDegrade: () => true, + doReload: () => window.location.reload(), + quiet: false, + ...window.coi + }; + + const n = navigator; + const controlling = n.serviceWorker && n.serviceWorker.controller; + + // Record the failure if the page is served by serviceWorker. + if (controlling && !window.crossOriginIsolated) { + window.sessionStorage.setItem("coiCoepHasFailed", "true"); + } + const coepHasFailed = window.sessionStorage.getItem("coiCoepHasFailed"); + + if (controlling) { + // Reload only on the first failure. + const reloadToDegrade = coi.coepDegrade() && !( + coepDegrading || window.crossOriginIsolated + ); + n.serviceWorker.controller.postMessage({ + type: "coepCredentialless", + value: (reloadToDegrade || coepHasFailed && coi.coepDegrade()) + ? false + : coi.coepCredentialless(), + }); + if (reloadToDegrade) { + !coi.quiet && console.log("Reloading page to degrade COEP."); + window.sessionStorage.setItem("coiReloadedBySelf", "coepdegrade"); + coi.doReload("coepdegrade"); + } + + if (coi.shouldDeregister()) { + n.serviceWorker.controller.postMessage({ type: "deregister" }); + } + } + + // If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are + // already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here. + if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return; + + if (!window.isSecureContext) { + !coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required."); + return; + } + + // In some environments (e.g. Firefox private mode) this won't be available + if (!n.serviceWorker) { + !coi.quiet && console.error("COOP/COEP Service Worker not registered, perhaps due to private mode."); + return; + } + + n.serviceWorker.register(window.document.currentScript.src).then( + (registration) => { + !coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope); + + registration.addEventListener("updatefound", () => { + !coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker."); + window.sessionStorage.setItem("coiReloadedBySelf", "updatefound"); + coi.doReload(); + }); + + // If the registration is active, but it's not controlling the page + if (registration.active && !n.serviceWorker.controller) { + !coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker."); + window.sessionStorage.setItem("coiReloadedBySelf", "notcontrolling"); + coi.doReload(); + } + }, + (err) => { + !coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err); + } + ); + })(); +} diff --git a/.github/wasm-pages/index.html b/.github/wasm-pages/index.html new file mode 100644 index 000000000..57252ba77 --- /dev/null +++ b/.github/wasm-pages/index.html @@ -0,0 +1,70 @@ + + + + + JANA2 WebAssembly + + + + + +

JANA2 WebAssembly + Dynamic Linking

+

This runs JANA2 natively in the browser with Web Workers (pthreads) and dynamically loads the JTest.so side module.

+ + +

+

+
+    
+    
+    
+
+
diff --git a/.github/workflows/wasm-pages.yml b/.github/workflows/wasm-pages.yml
new file mode 100644
index 000000000..d9dd51f8f
--- /dev/null
+++ b/.github/workflows/wasm-pages.yml
@@ -0,0 +1,69 @@
+name: Deploy WASM Pages
+
+on:
+  push:
+    branches:
+      - wasm
+  workflow_dispatch:
+
+permissions:
+  contents: read
+  pages: write
+  id-token: write
+
+concurrency:
+  group: "pages"
+  cancel-in-progress: false
+
+jobs:
+  deploy:
+    environment:
+      name: github-pages
+      url: ${{ steps.deployment.outputs.page_url }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Emscripten
+        uses: mymindstorm/setup-emsdk@v14
+
+      - name: Configure CMake
+        run: |
+          emcmake cmake -B build -S . \
+            -DCMAKE_CXX_FLAGS="-pthread -sUSE_PTHREADS=1" \
+            -DCMAKE_C_FLAGS="-pthread -sUSE_PTHREADS=1" \
+            -DCMAKE_EXE_LINKER_FLAGS="-pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency" \
+            -DCMAKE_SHARED_LINKER_FLAGS="-pthread -sUSE_PTHREADS=1" \
+            -DCMAKE_MODULE_LINKER_FLAGS="-pthread -sUSE_PTHREADS=1" \
+            -DBUILD_SHARED_LIBS=ON
+
+      - name: Build jana and JTest
+        run: cmake --build build --target jana JTest -j4
+
+      - name: Prepare Pages Directory
+        run: |
+          mkdir -p dist
+          # Copy our HTML and ServiceWorker
+          cp .github/wasm-pages/index.html dist/
+          cp .github/wasm-pages/coi-serviceworker.js dist/
+          
+          # Copy the WASM artifacts
+          cp build/src/programs/jana/jana.js dist/
+          cp build/src/programs/jana/jana.wasm dist/
+          cp build/src/programs/jana/jana.worker.js dist/
+          
+          # Copy the plugin side module
+          cp build/lib/JANA/plugins/JTest.so dist/ || cp build/src/plugins/JTest/JTest.so dist/
+
+      - name: Setup Pages
+        uses: actions/configure-pages@v4
+
+      - name: Upload artifact
+        uses: actions/upload-pages-artifact@v3
+        with:
+          path: './dist'
+
+      - name: Deploy to GitHub Pages
+        id: deployment
+        uses: actions/deploy-pages@v4

From b948d3865cede2e69592c2991d5636bf095f5aed Mon Sep 17 00:00:00 2001
From: Wouter Deconinck 
Date: Sat, 19 Sep 2026 12:20:42 -0500
Subject: [PATCH 09/20] wasm: Install jana.wasm and jana.worker.js

---
 src/programs/jana/CMakeLists.txt | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/programs/jana/CMakeLists.txt b/src/programs/jana/CMakeLists.txt
index 9ed2f11ff..7af75f3ed 100644
--- a/src/programs/jana/CMakeLists.txt
+++ b/src/programs/jana/CMakeLists.txt
@@ -7,3 +7,8 @@ target_link_libraries(jana jana2_shared_lib)
 target_link_options(jana PRIVATE -rdynamic)
 install(TARGETS jana DESTINATION bin)
 
+
+if(EMSCRIPTEN)
+    install(FILES $/jana.wasm DESTINATION bin)
+    install(FILES $/jana.worker.js DESTINATION bin OPTIONAL)
+endif()

From 626ace6f93fd1d7d0987be9b1f25bc78af7dde48 Mon Sep 17 00:00:00 2001
From: Wouter Deconinck 
Date: Sat, 19 Sep 2026 12:22:43 -0500
Subject: [PATCH 10/20] wasm: Fix i64 function signature mismatches by enabling
 WASM_BIGINT

---
 CMakeLists.txt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f1c8c5075..196a8572f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,9 +4,9 @@ cmake_policy(SET CMP0074 NEW) # find_package() uses _ROOT implicit
 project(jana2 VERSION 2026.03.01)
 if(EMSCRIPTEN)
     set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
-    string(APPEND CMAKE_EXE_LINKER_FLAGS " -sMAIN_MODULE=1")
-    string(APPEND CMAKE_SHARED_LINKER_FLAGS " -sSIDE_MODULE=1")
-    string(APPEND CMAKE_MODULE_LINKER_FLAGS " -sSIDE_MODULE=1")
+    string(APPEND CMAKE_EXE_LINKER_FLAGS " -sMAIN_MODULE=1 -sWASM_BIGINT=1")
+    string(APPEND CMAKE_SHARED_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1")
+    string(APPEND CMAKE_MODULE_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1")
 endif()
 
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)   # Enable -fPIC for all targets

From 161286260bc1d064ab116beea86aab74f0b2b3a2 Mon Sep 17 00:00:00 2001
From: Wouter Deconinck 
Date: Sat, 19 Sep 2026 12:33:08 -0500
Subject: [PATCH 11/20] wasm: Enable WASM exceptions and bypass VFS access
 checks for dynamic plugins

---
 CMakeLists.txt                               | 8 +++++---
 src/libraries/JANA/Services/JPluginLoader.cc | 4 ++++
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 196a8572f..35a7ef7a7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,10 +3,12 @@ cmake_policy(SET CMP0074 NEW) # find_package() uses _ROOT implicit
 
 project(jana2 VERSION 2026.03.01)
 if(EMSCRIPTEN)
+    string(APPEND CMAKE_CXX_FLAGS " -fwasm-exceptions")
+    string(APPEND CMAKE_C_FLAGS " -fwasm-exceptions")
     set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
-    string(APPEND CMAKE_EXE_LINKER_FLAGS " -sMAIN_MODULE=1 -sWASM_BIGINT=1")
-    string(APPEND CMAKE_SHARED_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1")
-    string(APPEND CMAKE_MODULE_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1")
+    string(APPEND CMAKE_EXE_LINKER_FLAGS " -sMAIN_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions")
+    string(APPEND CMAKE_SHARED_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions")
+    string(APPEND CMAKE_MODULE_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions")
 endif()
 
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)   # Enable -fPIC for all targets
diff --git a/src/libraries/JANA/Services/JPluginLoader.cc b/src/libraries/JANA/Services/JPluginLoader.cc
index 568ec9839..7bbad871a 100644
--- a/src/libraries/JANA/Services/JPluginLoader.cc
+++ b/src/libraries/JANA/Services/JPluginLoader.cc
@@ -138,6 +138,10 @@ void JPluginLoader::attach_plugins(JComponentManager* jcm) {
         // User didn't provide a path, so we have to search
         // If no valid paths found, `path` variable stays empty
 
+#ifdef __EMSCRIPTEN__
+        if (path.empty()) path = name + ".so";
+#endif
+
         if (path.empty()) {
             LOG_ERROR(m_logger) 
                 << "Couldn't find plugin '" << name << "'\n" 

From aae2697fc3c9ead2a49f1f05e9f1283fdf9cd931 Mon Sep 17 00:00:00 2001
From: Wouter Deconinck 
Date: Sat, 19 Sep 2026 13:20:19 -0500
Subject: [PATCH 12/20] wasm: Use flat installation layout when compiling with
 Emscripten

---
 CMakeLists.txt                    | 11 ++++++++---
 cmake/AddJanaLibrary.cmake        |  4 ++--
 cmake/AddJanaPlugin.cmake         |  9 +++++++--
 src/libraries/JANA/CMakeLists.txt |  4 ++--
 src/programs/jana/CMakeLists.txt  |  6 +++---
 5 files changed, 22 insertions(+), 12 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 35a7ef7a7..9d13af266 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,6 +2,11 @@ cmake_minimum_required(VERSION 3.16)
 cmake_policy(SET CMP0074 NEW) # find_package() uses _ROOT implicit hints
 
 project(jana2 VERSION 2026.03.01)
+include(GNUInstallDirs)
+if(EMSCRIPTEN)
+    set(CMAKE_INSTALL_BINDIR ".")
+    set(CMAKE_INSTALL_LIBDIR ".")
+endif()
 if(EMSCRIPTEN)
     string(APPEND CMAKE_CXX_FLAGS " -fwasm-exceptions")
     string(APPEND CMAKE_C_FLAGS " -fwasm-exceptions")
@@ -178,7 +183,7 @@ if (USE_PERFETTO)
         $<$,$,$>:
             -Wno-sign-compare -Wno-unused-parameter -Wno-missing-field-initializers>
     )
-    install(TARGETS perfetto_sdk EXPORT jana2_targets DESTINATION lib)
+    install(TARGETS perfetto_sdk EXPORT jana2_targets DESTINATION ${CMAKE_INSTALL_LIBDIR})
     install(FILES ${PERFETTO_SDK_DIR}/perfetto.h DESTINATION include/perfetto)
     set(JANA2_HAVE_PERFETTO 1)
 else()
@@ -286,10 +291,10 @@ endif()
 
 #---------------------------------------------------------------------------------------
 
-install(DIRECTORY scripts/ DESTINATION bin FILES_MATCHING PATTERN "jana-*.py"
+install(DIRECTORY scripts/ DESTINATION ${CMAKE_INSTALL_BINDIR} FILES_MATCHING PATTERN "jana-*.py"
         PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_READ WORLD_EXECUTE)
 
-    install(FILES "scripts/jana-status.sh" RENAME "jana-status" DESTINATION "bin"
+    install(FILES "scripts/jana-status.sh" RENAME "jana-status" DESTINATION "${CMAKE_INSTALL_BINDIR}"
         PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_READ WORLD_EXECUTE)
 
 include(${CMAKE_SOURCE_DIR}/cmake/MakeConfig.cmake)
diff --git a/cmake/AddJanaLibrary.cmake b/cmake/AddJanaLibrary.cmake
index e2667903c..1995289f0 100644
--- a/cmake/AddJanaLibrary.cmake
+++ b/cmake/AddJanaLibrary.cmake
@@ -86,7 +86,7 @@ macro(add_jana_library library_name)
     install(TARGETS ${library_name}
         EXPORT ${LIBRARY_EXPORT}
         PUBLIC_HEADER DESTINATION include/${INSTALL_NAMESPACE}/${CWD_NAME}
-        LIBRARY DESTINATION lib
+        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
     )
 
     # Handle tests
@@ -99,7 +99,7 @@ macro(add_jana_library library_name)
             INSTALL_RPATH_USE_LINK_PATH TRUE
             INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib"
         )
-        #install(TARGETS ${library_name}_tests RUNTIME DESTINATION bin)
+        #install(TARGETS ${library_name}_tests RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
         add_test(NAME ${library_name}_tests COMMAND ${library_name}_tests)
         set_tests_properties(${library_name}_tests PROPERTIES
             ENVIRONMENT "LD_LIBRARY_PATH=$:$:$ENV{LD_LIBRARY_PATH}"
diff --git a/cmake/AddJanaPlugin.cmake b/cmake/AddJanaPlugin.cmake
index f10e26448..da73038d3 100644
--- a/cmake/AddJanaPlugin.cmake
+++ b/cmake/AddJanaPlugin.cmake
@@ -60,6 +60,11 @@ macro(add_jana_plugin plugin_name)
     endif()
 
     # Set up target
+    if (EMSCRIPTEN)
+        set(JANA_INSTALL_PLUGINDIR ".")
+    else()
+        set(JANA_INSTALL_PLUGINDIR "${CMAKE_INSTALL_LIBDIR}/${INSTALL_NAMESPACE}/plugins")
+    endif()
     add_library(${plugin_name} SHARED ${PLUGIN_SOURCES})
 
     set_target_properties(${plugin_name} PROPERTIES
@@ -91,7 +96,7 @@ macro(add_jana_plugin plugin_name)
     install(TARGETS ${plugin_name}
         EXPORT ${PLUGIN_EXPORT}
         PUBLIC_HEADER DESTINATION include/${INSTALL_NAMESPACE}/plugins/${plugin_name}
-        LIBRARY DESTINATION lib/${INSTALL_NAMESPACE}/plugins
+        LIBRARY DESTINATION ${JANA_INSTALL_PLUGINDIR}
     )
 
     # Handle tests
@@ -104,7 +109,7 @@ macro(add_jana_plugin plugin_name)
             INSTALL_RPATH_USE_LINK_PATH TRUE
             INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${CMAKE_INSTALL_PREFIX}/lib/${INSTALL_NAMESPACE}/plugins"
         )
-        #install(TARGETS ${plugin_name}-tests RUNTIME DESTINATION bin)
+        #install(TARGETS ${plugin_name}-tests RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
         add_test(NAME ${plugin_name}-tests COMMAND ${plugin_name}-tests)
         set_tests_properties(${plugin_name}-tests PROPERTIES
             ENVIRONMENT "JANA_PLUGIN_PATH=${CMAKE_BINARY_DIR}/lib/JANA/plugins;LD_LIBRARY_PATH=$:$ENV{LD_LIBRARY_PATH}"
diff --git a/src/libraries/JANA/CMakeLists.txt b/src/libraries/JANA/CMakeLists.txt
index 3917fee24..6a2824c33 100644
--- a/src/libraries/JANA/CMakeLists.txt
+++ b/src/libraries/JANA/CMakeLists.txt
@@ -112,7 +112,7 @@ if (${USE_PODIO})
 elseif (${USE_ROOT})
     target_link_libraries(jana2_static_lib PUBLIC ROOT::Core)
 endif()
-install(TARGETS jana2_static_lib EXPORT jana2_targets DESTINATION lib)
+install(TARGETS jana2_static_lib EXPORT jana2_targets DESTINATION ${CMAKE_INSTALL_LIBDIR})
 
 
 # Build shared library
@@ -136,7 +136,7 @@ elseif (${USE_ROOT})
     target_link_libraries(jana2_shared_lib PUBLIC ROOT::Core)
 endif()
 
-install(TARGETS jana2_shared_lib EXPORT jana2_targets DESTINATION lib)
+install(TARGETS jana2_shared_lib EXPORT jana2_targets DESTINATION ${CMAKE_INSTALL_LIBDIR})
 set(INSTALL_RPATH_USE_LINK_PATH True)
 
 
diff --git a/src/programs/jana/CMakeLists.txt b/src/programs/jana/CMakeLists.txt
index 7af75f3ed..f6336b2cb 100644
--- a/src/programs/jana/CMakeLists.txt
+++ b/src/programs/jana/CMakeLists.txt
@@ -5,10 +5,10 @@ add_executable(jana jana.cc )
 find_package(Threads REQUIRED)
 target_link_libraries(jana jana2_shared_lib)
 target_link_options(jana PRIVATE -rdynamic)
-install(TARGETS jana DESTINATION bin)
+install(TARGETS jana DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 
 if(EMSCRIPTEN)
-    install(FILES $/jana.wasm DESTINATION bin)
-    install(FILES $/jana.worker.js DESTINATION bin OPTIONAL)
+    install(FILES $/jana.wasm DESTINATION ${CMAKE_INSTALL_BINDIR})
+    install(FILES $/jana.worker.js DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
 endif()

From 445969d5cd96f1fabadd3934d0d29be54e51fb56 Mon Sep 17 00:00:00 2001
From: Wouter Deconinck 
Date: Sat, 19 Sep 2026 13:41:22 -0500
Subject: [PATCH 13/20] wasm: Allow WASM memory arrays to dynamically grow to
 prevent OOM errors

---
 CMakeLists.txt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9d13af266..3cbc53fcd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,9 +11,9 @@ if(EMSCRIPTEN)
     string(APPEND CMAKE_CXX_FLAGS " -fwasm-exceptions")
     string(APPEND CMAKE_C_FLAGS " -fwasm-exceptions")
     set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
-    string(APPEND CMAKE_EXE_LINKER_FLAGS " -sMAIN_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions")
-    string(APPEND CMAKE_SHARED_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions")
-    string(APPEND CMAKE_MODULE_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions")
+    string(APPEND CMAKE_EXE_LINKER_FLAGS " -sMAIN_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions -sALLOW_MEMORY_GROWTH=1")
+    string(APPEND CMAKE_SHARED_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions -sALLOW_MEMORY_GROWTH=1")
+    string(APPEND CMAKE_MODULE_LINKER_FLAGS " -sSIDE_MODULE=1 -sWASM_BIGINT=1 -fwasm-exceptions -sALLOW_MEMORY_GROWTH=1")
 endif()
 
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)   # Enable -fPIC for all targets

From 087311cd8883bd5c4dd068929be9b7640c9dd5dd Mon Sep 17 00:00:00 2001
From: Wouter Deconinck 
Date: Sat, 19 Sep 2026 15:52:32 -0500
Subject: [PATCH 14/20] wasm: Fix missing jana.worker.js in CI and add native
 browser download prompt for benchmarker output

---
 .github/workflows/wasm-pages.yml       |  2 +-
 src/libraries/JANA/CLI/JBenchmarker.cc | 84 ++++++++++++++++++++++++++
 2 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/wasm-pages.yml b/.github/workflows/wasm-pages.yml
index d9dd51f8f..0bc4ccc7a 100644
--- a/.github/workflows/wasm-pages.yml
+++ b/.github/workflows/wasm-pages.yml
@@ -51,7 +51,7 @@ jobs:
           # Copy the WASM artifacts
           cp build/src/programs/jana/jana.js dist/
           cp build/src/programs/jana/jana.wasm dist/
-          cp build/src/programs/jana/jana.worker.js dist/
+          cp build/src/programs/jana/jana.worker.js dist/ || true
           
           # Copy the plugin side module
           cp build/lib/JANA/plugins/JTest.so dist/ || cp build/src/plugins/JTest/JTest.so dist/
diff --git a/src/libraries/JANA/CLI/JBenchmarker.cc b/src/libraries/JANA/CLI/JBenchmarker.cc
index 46e59a7ce..10c53260f 100644
--- a/src/libraries/JANA/CLI/JBenchmarker.cc
+++ b/src/libraries/JANA/CLI/JBenchmarker.cc
@@ -1,3 +1,6 @@
+#ifdef __EMSCRIPTEN__
+#include 
+#endif
 
 // Copyright 2020, Jefferson Science Associates, LLC.
 // Subject to the terms in the LICENSE file found in the top-level directory.
@@ -192,6 +195,87 @@ void JBenchmarker::RunUntilFinished() {
     // Hopefully, because we called flush(), the files will be partially filled even if we are SIGKILLed
     // before we reach this point.
 
+#ifdef __EMSCRIPTEN__
+
+    EM_ASM({
+
+        var rates_path = UTF8ToString($0);
+
+        var samples_path = UTF8ToString($1);
+
+        if (typeof window !== "undefined" && typeof Blob !== "undefined") {
+
+            function triggerDownload(path) {
+
+                try {
+
+                    var content = FS.readFile(path);
+
+                    var blob = new Blob([content], { type: "application/octet-stream" });
+
+                    var url = URL.createObjectURL(blob);
+
+                    var a = document.createElement("a");
+
+                    a.href = url;
+
+                    var parts = path.split("/");
+
+                    a.download = parts[parts.length - 1];
+
+                    document.body.appendChild(a);
+
+                    a.click();
+
+                    document.body.removeChild(a);
+
+                    URL.revokeObjectURL(url);
+
+                } catch(e) {
+
+                    console.error("Failed to download " + path, e);
+
+                }
+
+            }
+
+            triggerDownload(rates_path);
+
+            triggerDownload(samples_path);
+
+        } else if (typeof ENVIRONMENT_IS_NODE !== "undefined" && ENVIRONMENT_IS_NODE) {
+
+            var fs = require("fs");
+
+            function copyOut(path) {
+
+                try {
+
+                    var content = FS.readFile(path);
+
+                    var parts = path.split("/");
+
+                    fs.writeFileSync(parts[parts.length - 1], content);
+
+                } catch(e) {
+
+                    console.error("Failed to extract " + path, e);
+
+                }
+
+            }
+
+            copyOut(rates_path);
+
+            copyOut(samples_path);
+
+        }
+
+    }, (m_output_dir + "/" + m_rates_filename).c_str(), (m_output_dir + "/" + m_samples_filename).c_str());
+
+#endif
+
+
     samples_file.close();
     rates_file.close();
 

From 67aea791e26569d4fd0b6d43c80087650d6615de Mon Sep 17 00:00:00 2001
From: Wouter Deconinck 
Date: Sat, 19 Sep 2026 15:54:31 -0500
Subject: [PATCH 15/20] wasm: Flatten installation layout for remaining
 examples and plugins

---
 cmake/AddJanaTest.cmake                                   | 2 +-
 cmake/MakeJanaThis.cmake                                  | 6 +++---
 src/examples/misc/PodioDatamodel/CMakeLists.txt           | 8 ++++----
 src/examples/misc/RootDatamodelExample/CMakeLists.txt     | 2 +-
 src/examples/misc/SubeventCUDAExample/CMakeLists.txt      | 2 +-
 src/examples/misc/SubeventExample/CMakeLists.txt          | 2 +-
 .../19_wrapper_program/CMakeLists.txt                     | 2 +-
 .../01_datamodel/CMakeLists.txt                           | 8 ++++----
 src/plugins/janaview/CMakeLists.txt                       | 4 ++--
 src/programs/jana/CMakeLists.txt                          | 1 +
 10 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/cmake/AddJanaTest.cmake b/cmake/AddJanaTest.cmake
index c62b646a8..0f83a6df5 100644
--- a/cmake/AddJanaTest.cmake
+++ b/cmake/AddJanaTest.cmake
@@ -33,7 +33,7 @@ macro(add_jana_test test_target_name)
         INSTALL_RPATH_USE_LINK_PATH TRUE
         INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${CMAKE_INSTALL_PREFIX}/lib/JANA/plugins")
 
-    install(TARGETS ${test_target_name} RUNTIME DESTINATION bin)
+    install(TARGETS ${test_target_name} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
     add_test(NAME ${test_target_name} COMMAND ${test_target_name})
     set_tests_properties(${test_target_name} PROPERTIES
diff --git a/cmake/MakeJanaThis.cmake b/cmake/MakeJanaThis.cmake
index 9335c34a9..809602508 100644
--- a/cmake/MakeJanaThis.cmake
+++ b/cmake/MakeJanaThis.cmake
@@ -148,6 +148,6 @@ configure_file(scripts/jana-config.in jana-config @ONLY)
 configure_file(scripts/jana-this.sh.in  jana-this.sh  @ONLY)
 configure_file(scripts/jana-this.csh.in jana-this.csh  @ONLY)
 
-install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-config DESTINATION bin)
-install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-this.sh  DESTINATION bin)
-install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-this.csh  DESTINATION bin)
+install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-config DESTINATION ${CMAKE_INSTALL_BINDIR})
+install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-this.sh  DESTINATION ${CMAKE_INSTALL_BINDIR})
+install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/jana-this.csh  DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/examples/misc/PodioDatamodel/CMakeLists.txt b/src/examples/misc/PodioDatamodel/CMakeLists.txt
index eb6bab7cf..1674e29b5 100644
--- a/src/examples/misc/PodioDatamodel/CMakeLists.txt
+++ b/src/examples/misc/PodioDatamodel/CMakeLists.txt
@@ -14,17 +14,17 @@ if (USE_PODIO)
 
     install(TARGETS PodioDatamodel
         EXPORT jana2_targets
-        LIBRARY DESTINATION lib
+        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
         PUBLIC_HEADER DESTINATION include/JANA/examples/PodioDatamodel
     )
 
     install(TARGETS PodioDatamodelDict
         EXPORT jana2_targets
-        DESTINATION lib
+        DESTINATION ${CMAKE_INSTALL_LIBDIR}
     )
 
-    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/PodioDatamodelDictDict.rootmap DESTINATION lib)
-    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libPodioDatamodelDict_rdict.pcm DESTINATION lib)
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/PodioDatamodelDictDict.rootmap DESTINATION ${CMAKE_INSTALL_LIBDIR})
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libPodioDatamodelDict_rdict.pcm DESTINATION ${CMAKE_INSTALL_LIBDIR})
 
 else()
     message(STATUS "Skipping examples/PodioDatamodel because USE_PODIO=Off")
diff --git a/src/examples/misc/RootDatamodelExample/CMakeLists.txt b/src/examples/misc/RootDatamodelExample/CMakeLists.txt
index b69436391..862ee5466 100644
--- a/src/examples/misc/RootDatamodelExample/CMakeLists.txt
+++ b/src/examples/misc/RootDatamodelExample/CMakeLists.txt
@@ -41,7 +41,7 @@ if(${USE_ROOT})
     target_link_libraries(RootDatamodelExample PUBLIC ROOT::Core)
 
     message(STATUS "Installing ROOT PCM files: ${my_pcms}")
-    install(FILES ${my_pcms} DESTINATION lib/JANA/plugins)
+    install(FILES ${my_pcms} DESTINATION ${JANA_INSTALL_PLUGINDIR})
 
     add_test(NAME jana-example-rootdatamodel-tests
         COMMAND $ -Pplugins=RootDatamodelExample -Pjana:nevents=10)
diff --git a/src/examples/misc/SubeventCUDAExample/CMakeLists.txt b/src/examples/misc/SubeventCUDAExample/CMakeLists.txt
index c3ee3f7e4..09c93689e 100644
--- a/src/examples/misc/SubeventCUDAExample/CMakeLists.txt
+++ b/src/examples/misc/SubeventCUDAExample/CMakeLists.txt
@@ -16,7 +16,7 @@ if (USE_CUDA)
         OUTPUT_NAME "SubeventCUDAExample"
 		CUDA_ARCHITECTURES "75;80")
 
-	install(TARGETS SubeventCUDAExample DESTINATION bin)
+	install(TARGETS SubeventCUDAExample DESTINATION ${CMAKE_INSTALL_BINDIR})
     add_test(NAME jana-example-subevent-cuda-tests
         COMMAND SubeventCUDAExample)
 
diff --git a/src/examples/misc/SubeventExample/CMakeLists.txt b/src/examples/misc/SubeventExample/CMakeLists.txt
index 351a260f9..a1d45e54c 100644
--- a/src/examples/misc/SubeventExample/CMakeLists.txt
+++ b/src/examples/misc/SubeventExample/CMakeLists.txt
@@ -3,7 +3,7 @@
 add_executable(SubeventExample SubeventExample.cc)
 target_link_libraries(SubeventExample jana2)
 set_target_properties(SubeventExample PROPERTIES PREFIX "" OUTPUT_NAME "SubeventExample")
-install(TARGETS SubeventExample DESTINATION bin)
+install(TARGETS SubeventExample DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 add_test(NAME jana-example-subevent-tests
     COMMAND SubeventExample)
diff --git a/src/examples/tutorial_with_lightweight_datamodel/19_wrapper_program/CMakeLists.txt b/src/examples/tutorial_with_lightweight_datamodel/19_wrapper_program/CMakeLists.txt
index ac74de407..b89c48fdf 100644
--- a/src/examples/tutorial_with_lightweight_datamodel/19_wrapper_program/CMakeLists.txt
+++ b/src/examples/tutorial_with_lightweight_datamodel/19_wrapper_program/CMakeLists.txt
@@ -9,5 +9,5 @@ target_link_libraries(lw-tutorial-recon PRIVATE
 )
 # Note that if you are outside the JANA2 source tree, you need to target_link_libraries against JANA::jana2_shared_lib instead
 
-install(TARGETS lw-tutorial-recon DESTINATION bin)
+install(TARGETS lw-tutorial-recon DESTINATION ${CMAKE_INSTALL_BINDIR})
 
diff --git a/src/examples/tutorial_with_podio_datamodel/01_datamodel/CMakeLists.txt b/src/examples/tutorial_with_podio_datamodel/01_datamodel/CMakeLists.txt
index 75a652376..5142c6c46 100644
--- a/src/examples/tutorial_with_podio_datamodel/01_datamodel/CMakeLists.txt
+++ b/src/examples/tutorial_with_podio_datamodel/01_datamodel/CMakeLists.txt
@@ -13,15 +13,15 @@ PODIO_ADD_ROOT_IO_DICT(jana2_tutorial_podio_datamodel_dict jana2_tutorial_podio_
 
 install(TARGETS jana2_tutorial_podio_datamodel
     EXPORT jana2_targets
-    LIBRARY DESTINATION lib
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
     PUBLIC_HEADER DESTINATION include/jana2_tutorial_podio_datamodel
 )
 
 install(TARGETS jana2_tutorial_podio_datamodel_dict
     EXPORT jana2_targets
-    DESTINATION lib
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}
 )
 
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/jana2_tutorial_podio_datamodel_dictDict.rootmap DESTINATION lib)
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjana2_tutorial_podio_datamodel_dict_rdict.pcm DESTINATION lib)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/jana2_tutorial_podio_datamodel_dictDict.rootmap DESTINATION ${CMAKE_INSTALL_LIBDIR})
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjana2_tutorial_podio_datamodel_dict_rdict.pcm DESTINATION ${CMAKE_INSTALL_LIBDIR})
 
diff --git a/src/plugins/janaview/CMakeLists.txt b/src/plugins/janaview/CMakeLists.txt
index 9bc7ae051..21d56d2ab 100644
--- a/src/plugins/janaview/CMakeLists.txt
+++ b/src/plugins/janaview/CMakeLists.txt
@@ -12,8 +12,8 @@ if(USE_ROOT)
         PUBLIC_HEADER ${JANAVIEW_HEADERS}
     )
 
-    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjv_mainframe_rdict.pcm DESTINATION lib/JANA/plugins)
-    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjv_mainframe.rootmap DESTINATION lib/JANA/plugins)
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjv_mainframe_rdict.pcm DESTINATION ${JANA_INSTALL_PLUGINDIR})
+    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libjv_mainframe.rootmap DESTINATION ${JANA_INSTALL_PLUGINDIR})
 
 else()
 
diff --git a/src/programs/jana/CMakeLists.txt b/src/programs/jana/CMakeLists.txt
index f6336b2cb..8cf094cb9 100644
--- a/src/programs/jana/CMakeLists.txt
+++ b/src/programs/jana/CMakeLists.txt
@@ -8,6 +8,7 @@ target_link_options(jana PRIVATE -rdynamic)
 install(TARGETS jana DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 
+
 if(EMSCRIPTEN)
     install(FILES $/jana.wasm DESTINATION ${CMAKE_INSTALL_BINDIR})
     install(FILES $/jana.worker.js DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)

From b197f775b880d74ded73ba3549f484e555220b67 Mon Sep 17 00:00:00 2001
From: Wouter Deconinck 
Date: Sat, 19 Sep 2026 16:00:03 -0500
Subject: [PATCH 16/20] wasm: Include libJANA.so in GitHub Pages deployment
 artifacts

---
 .github/workflows/wasm-pages.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/wasm-pages.yml b/.github/workflows/wasm-pages.yml
index 0bc4ccc7a..b4b68b2d4 100644
--- a/.github/workflows/wasm-pages.yml
+++ b/.github/workflows/wasm-pages.yml
@@ -53,6 +53,8 @@ jobs:
           cp build/src/programs/jana/jana.wasm dist/
           cp build/src/programs/jana/jana.worker.js dist/ || true
           
+          cp build/lib/libJANA.so dist/ || cp build/src/libraries/JANA/libJANA.so dist/
+
           # Copy the plugin side module
           cp build/lib/JANA/plugins/JTest.so dist/ || cp build/src/plugins/JTest/JTest.so dist/
 

From eeb6366413b3a9d49e55acc013e536341f84d2a9 Mon Sep 17 00:00:00 2001
From: Wouter Deconinck 
Date: Sat, 19 Sep 2026 16:05:42 -0500
Subject: [PATCH 17/20] wasm: Export callMain and FS runtime methods

---
 .github/workflows/wasm-pages.yml | 2 +-
 src/programs/jana/CMakeLists.txt | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/wasm-pages.yml b/.github/workflows/wasm-pages.yml
index b4b68b2d4..96a164c91 100644
--- a/.github/workflows/wasm-pages.yml
+++ b/.github/workflows/wasm-pages.yml
@@ -33,7 +33,7 @@ jobs:
           emcmake cmake -B build -S . \
             -DCMAKE_CXX_FLAGS="-pthread -sUSE_PTHREADS=1" \
             -DCMAKE_C_FLAGS="-pthread -sUSE_PTHREADS=1" \
-            -DCMAKE_EXE_LINKER_FLAGS="-pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency" \
+            -DCMAKE_EXE_LINKER_FLAGS="-sEXPORTED_RUNTIME_METHODS=callMain,FS -pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency" \
             -DCMAKE_SHARED_LINKER_FLAGS="-pthread -sUSE_PTHREADS=1" \
             -DCMAKE_MODULE_LINKER_FLAGS="-pthread -sUSE_PTHREADS=1" \
             -DBUILD_SHARED_LIBS=ON
diff --git a/src/programs/jana/CMakeLists.txt b/src/programs/jana/CMakeLists.txt
index 8cf094cb9..f7a80b36c 100644
--- a/src/programs/jana/CMakeLists.txt
+++ b/src/programs/jana/CMakeLists.txt
@@ -10,6 +10,7 @@ install(TARGETS jana DESTINATION ${CMAKE_INSTALL_BINDIR})
 
 
 if(EMSCRIPTEN)
+    target_link_options(jana PRIVATE "-sEXPORTED_RUNTIME_METHODS=callMain,FS")
     install(FILES $/jana.wasm DESTINATION ${CMAKE_INSTALL_BINDIR})
     install(FILES $/jana.worker.js DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
 endif()

From b0edbb815a6b18152df5c7bcd730c2c282a48a33 Mon Sep 17 00:00:00 2001
From: Wouter Deconinck 
Date: Sat, 19 Sep 2026 16:09:38 -0500
Subject: [PATCH 18/20] wasm: Fix invalid nevents command line flag in demo
 page

---
 .github/wasm-pages/index.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/wasm-pages/index.html b/.github/wasm-pages/index.html
index 57252ba77..0cb63082d 100644
--- a/.github/wasm-pages/index.html
+++ b/.github/wasm-pages/index.html
@@ -55,10 +55,10 @@ 

JANA2 WebAssembly + Dynamic Linking

document.getElementById('runBtn').addEventListener('click', function() { this.disabled = true; - Module.print("\n--- Executing: jana -Pplugins=JTest -Pnthreads=4 -b --nevents=100 ---"); + Module.print("\n--- Executing: jana -Pplugins=JTest -Pnthreads=4 -b -Pjana:nevents=100 ---"); try { - Module.callMain(['-Pplugins=JTest', '-Pnthreads=4', '-b', '--nevents=100']); + Module.callMain(['-Pplugins=JTest', '-Pnthreads=4', '-b', '-Pjana:nevents=100']); } catch(e) { Module.printErr("Exception caught: " + e); } From e665e16c90cacb9896e2e6e1b67037362252e0cd Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 19 Sep 2026 16:13:56 -0500 Subject: [PATCH 19/20] wasm: Proxy main to pthread and use MAIN_THREAD_EM_ASM to avoid blocking browser UI --- .github/workflows/wasm-pages.yml | 2 +- src/libraries/JANA/CLI/JBenchmarker.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wasm-pages.yml b/.github/workflows/wasm-pages.yml index 96a164c91..c601ee28d 100644 --- a/.github/workflows/wasm-pages.yml +++ b/.github/workflows/wasm-pages.yml @@ -33,7 +33,7 @@ jobs: emcmake cmake -B build -S . \ -DCMAKE_CXX_FLAGS="-pthread -sUSE_PTHREADS=1" \ -DCMAKE_C_FLAGS="-pthread -sUSE_PTHREADS=1" \ - -DCMAKE_EXE_LINKER_FLAGS="-sEXPORTED_RUNTIME_METHODS=callMain,FS -pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency" \ + -DCMAKE_EXE_LINKER_FLAGS="-sEXPORTED_RUNTIME_METHODS=callMain,FS -pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency -sPROXY_TO_PTHREAD=1" \ -DCMAKE_SHARED_LINKER_FLAGS="-pthread -sUSE_PTHREADS=1" \ -DCMAKE_MODULE_LINKER_FLAGS="-pthread -sUSE_PTHREADS=1" \ -DBUILD_SHARED_LIBS=ON diff --git a/src/libraries/JANA/CLI/JBenchmarker.cc b/src/libraries/JANA/CLI/JBenchmarker.cc index 10c53260f..4089a0af0 100644 --- a/src/libraries/JANA/CLI/JBenchmarker.cc +++ b/src/libraries/JANA/CLI/JBenchmarker.cc @@ -197,7 +197,7 @@ void JBenchmarker::RunUntilFinished() { #ifdef __EMSCRIPTEN__ - EM_ASM({ + MAIN_THREAD_EM_ASM({ var rates_path = UTF8ToString($0); From a72c7c96e8a41b9c9d6423a71fa3b10be337ff4a Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 19 Sep 2026 16:15:29 -0500 Subject: [PATCH 20/20] wasm: Stagger benchmarker output downloads to bypass browser spam protections --- src/libraries/JANA/CLI/JBenchmarker.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/JANA/CLI/JBenchmarker.cc b/src/libraries/JANA/CLI/JBenchmarker.cc index 4089a0af0..0e8b92751 100644 --- a/src/libraries/JANA/CLI/JBenchmarker.cc +++ b/src/libraries/JANA/CLI/JBenchmarker.cc @@ -241,7 +241,7 @@ void JBenchmarker::RunUntilFinished() { triggerDownload(rates_path); - triggerDownload(samples_path); + setTimeout(function() { triggerDownload(samples_path); }, 500); } else if (typeof ENVIRONMENT_IS_NODE !== "undefined" && ENVIRONMENT_IS_NODE) {