Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
80ea0f7
Fix Emscripten build errors
wdconinc Sep 14, 2026
d91868b
WASM: Disable sched_getcpu usage in JCpuInfo to fix linking error
wdconinc Sep 14, 2026
7455a88
WASM: Conditionally build ROOT plugins
wdconinc Sep 14, 2026
00d8028
WASM: Do not force USE_ROOT=ON when USE_PODIO=ON
wdconinc Sep 14, 2026
60c7d77
WASM: Conditional linking to podioRootIO based on USE_ROOT
wdconinc Sep 14, 2026
f7d3604
wasm: build janaview only if ROOT::Gui is available
wdconinc Sep 16, 2026
7051055
wasm: Enable dynamic linking for Emscripten MAIN/SIDE modules
wdconinc Sep 19, 2026
f043aff
wasm: Setup GH Pages for proof-of-concept JANA web execution
wdconinc Sep 19, 2026
b948d38
wasm: Install jana.wasm and jana.worker.js
wdconinc Sep 19, 2026
626ace6
wasm: Fix i64 function signature mismatches by enabling WASM_BIGINT
wdconinc Sep 19, 2026
1612862
wasm: Enable WASM exceptions and bypass VFS access checks for dynamic…
wdconinc Sep 19, 2026
aae2697
wasm: Use flat installation layout when compiling with Emscripten
wdconinc Sep 19, 2026
445969d
wasm: Allow WASM memory arrays to dynamically grow to prevent OOM errors
wdconinc Sep 19, 2026
087311c
wasm: Fix missing jana.worker.js in CI and add native browser downloa…
wdconinc Sep 19, 2026
67aea79
wasm: Flatten installation layout for remaining examples and plugins
wdconinc Sep 19, 2026
b197f77
wasm: Include libJANA.so in GitHub Pages deployment artifacts
wdconinc Sep 19, 2026
eeb6366
wasm: Export callMain and FS runtime methods
wdconinc Sep 19, 2026
b0edbb8
wasm: Fix invalid nevents command line flag in demo page
wdconinc Sep 19, 2026
e665e16
wasm: Proxy main to pthread and use MAIN_THREAD_EM_ASM to avoid block…
wdconinc Sep 19, 2026
a72c7c9
wasm: Stagger benchmarker output downloads to bypass browser spam pro…
wdconinc Sep 19, 2026
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
146 changes: 146 additions & 0 deletions .github/wasm-pages/coi-serviceworker.js
Original file line number Diff line number Diff line change
@@ -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);
}
);
})();
}
70 changes: 70 additions & 0 deletions .github/wasm-pages/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JANA2 WebAssembly</title>
<!-- Required for SharedArrayBuffer (pthreads) on GitHub Pages -->
<script src="coi-serviceworker.js"></script>
<style>
body { font-family: sans-serif; margin: 20px; background: #1e1e1e; color: #ddd; }
#output { width: 100%; height: 600px; background: #000; color: #0f0; font-family: monospace; padding: 10px; overflow-y: auto; border-radius: 5px; }
button { padding: 10px 20px; font-size: 16px; cursor: pointer; background: #007bff; color: white; border: none; border-radius: 3px; }
button:disabled { background: #555; cursor: not-allowed; }
</style>
</head>
<body>
<h1>JANA2 WebAssembly + Dynamic Linking</h1>
<p>This runs JANA2 natively in the browser with Web Workers (pthreads) and dynamically loads the <code>JTest.so</code> side module.</p>

<button id="runBtn" disabled>Loading WASM...</button>
<br><br>
<pre id="output"></pre>

<script>
// Track whether COI is working
if (typeof SharedArrayBuffer === 'undefined') {
document.getElementById('output').innerHTML += "<span style='color:red'>Error: SharedArrayBuffer is not defined. COI ServiceWorker failed or your browser does not support it.</span>\n";
}

var Module = {
print: function(text) {
const output = document.getElementById('output');
output.innerHTML += text + '\n';
output.scrollTop = output.scrollHeight;
console.log(text);
},
printErr: function(text) {
const output = document.getElementById('output');
output.innerHTML += '<span style="color:#ff6b6b">' + text + '</span>\n';
output.scrollTop = output.scrollHeight;
console.error(text);
},
// Preload the side module. Emscripten will fetch this automatically.
// When JANA calls dlopen, it'll link into this.
dynamicLibraries: ['JTest.so'],

onRuntimeInitialized: function() {
document.getElementById('runBtn').innerText = 'Run JANA2 (-Pplugins=JTest -Pnthreads=4)';
document.getElementById('runBtn').disabled = false;
Module.print("Runtime initialized. Ready.");
},

// Do not call main automatically
noInitialRun: true
};

document.getElementById('runBtn').addEventListener('click', function() {
this.disabled = true;
Module.print("\n--- Executing: jana -Pplugins=JTest -Pnthreads=4 -b -Pjana:nevents=100 ---");

try {
Module.callMain(['-Pplugins=JTest', '-Pnthreads=4', '-b', '-Pjana:nevents=100']);
} catch(e) {
Module.printErr("Exception caught: " + e);
}
});
</script>

<script src="jana.js"></script>
</body>
</html>
71 changes: 71 additions & 0 deletions .github/workflows/wasm-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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="-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

- 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/ || 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/

- 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
21 changes: 17 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ cmake_minimum_required(VERSION 3.16)
cmake_policy(SET CMP0074 NEW) # find_package() uses <PackageName>_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")
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
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

Expand Down Expand Up @@ -80,7 +93,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)
Expand Down Expand Up @@ -170,7 +183,7 @@ if (USE_PERFETTO)
$<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:
-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()
Expand Down Expand Up @@ -278,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)
Expand Down
4 changes: 2 additions & 2 deletions cmake/AddJanaLibrary.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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=$<TARGET_FILE_DIR:${library_name}>:$<TARGET_FILE_DIR:jana2_shared_lib>:$ENV{LD_LIBRARY_PATH}"
Expand Down
9 changes: 7 additions & 2 deletions cmake/AddJanaPlugin.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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=$<TARGET_FILE_DIR:jana2_shared_lib>:$ENV{LD_LIBRARY_PATH}"
Expand Down
2 changes: 1 addition & 1 deletion cmake/AddJanaTest.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading