Scientists and engineers utilize programming languages not only to build software systems but also to drive interactive exploratory workflows. They leverage developer tools to explore and reason through problems effectively. This process involves executing code, examining visualizations, loading data, and running additional code. Rapid iteration is particularly crucial during the exploratory phase of any technical project.
For this reason, the most popular tools for exploratory work tend to be interpreted languages, such as Python or R. The “Read-Eval-Print-Loop” (REPL) places the user at the center of these iterative workflows. While compiled languages such as C++ are essential for performance-sensitive applications, engineers accustomed to interactive tools often struggle in such environments.
At CERN, the C++ programming language has been pivotal to their data analysis platform for particle physics, notably in the ROOT framework. ROOT is underpinned by the Cling C++ interpreter, which is built on the Clang and LLVM stack. The Cling interpreter, co-created by Vassil Vassilev and Axel Naumann in the 2010s, effectively turned C++ into an agile scientific scripting language!
These interactive workflows are where the Jupyter stack shines compared to traditional IDEs. Indeed, the Jupyter project was designed with exploratory computing in mind. Crucially, it was built from the ground up to be language-agnostic, accommodating multiple programming languages seamlessly. In 2017, the Xeus-Cling Jupyter kernel was announced. Developed by Sylvain Corlay, Johan Mabille and Loic Gouarin, it integrated Cling in the Jupyter ecosystem, taking advantage of the rich features of Jupyter, such as rich mime type display and interactive widgets.
Packaging the Cling project proved challenging as it required patching the code base of Clang, and was not released as frequently as the LLVM stack, causing difficulties with rolling software distributions like conda-forge.
In an effort led by Cling co-creator Vassil Vassilev, the Clang-Repl project was born, which consists in building the foundations for interactive C++ in the core of Clang. Clang-Repl has been undergoing active development for a few years now and a Jupyter kernel for Clang-Repl, Xeus-Cpp, has also been developed, providing a similar set of features to Xeus-Cling.
Building the new C++ kernel upon Xeus was crucial as it enabled the porting of the C++ kernel to the Web browser with JupyterLite.
The standard approach for Jupyter-based computing relies on providing each user with computing resources in the backend. Serving a few thousand end users necessitates substantial resources and complex deployment strategies.
The JupyterLite project shifts this paradigm by running code directly in the user’s Web browser, by relying on a WebAssembly build of the kernel and required libraries. This eliminates the linear relationship between the number of users and the required resources.
For instance, the NumPy.org website features a JupyterLite code console on its main page, offering a computational environment to thousands of monthly visitors without incurring any cloud costs. Similarly, the Capytale deployment of Jupyter, used in French high schools, operates on the same model. It accounts for over half a million registered users and hosts more than 200,000 user sessions weekly.
In order to provide a broad range of packages and libraries for this new platform, the JupyterLite team at QuantStack developed the Emscripten-forge distribution, utilizing the conda package manager for creating WebAssembly environments. Emscripten-forge comprises the main packages of the Python scientific computing ecosystem, as well as the R programming language, and several command-line applications powering the JupyterLite terminal.
Today, we are proud to announce that Xeus-Cpp and LLVM are available in emscripten-forge, effectively enabling interpreted C++ in the browser.
Feel free to try it out in your browser by clicking the link below!
We now dive into the details on the internals of the WebAssembly build of Xeus-Cpp, and the key features of the project.
Emulated Just-in-Time Compilation and WebAssembly
A fundamental challenge inherent to the WebAssembly is that it operates in a sandboxed Harvard architecture: code and data reside in completely distinct memory spaces. This makes conventional JIT compilation unfeasible in the browser, as it prohibits dynamic modification of executable memory.
The WebAssembly backend for Clang-Repl was introduced during the LLVM 17 development cycle, with the introduction of a WASM-specific IncrementalExecutor, sidestepping the standard LLVM JIT approach used on native platforms. This new WasmIncrementalExecutor class handled the WebAssembly-specific execution model as follows:
- The LLVM IR from the REPL’s Partial Translation Unit (PTU) is compiled into an object file.
- This object file is passed to wasm-ld with flags to generate a standalone WebAssembly module.
- The resulting .wasm module acts as a dynamically linked side module, designed to be loaded on top of a persistent main module at runtime.
These side modules, while similar to shared libraries, are treated as auxiliary WASM modules that are dynamically attached to the main application using Emscripten’s dlopen mechanism. They share memory with the main module and can resolve symbols defined in previous executions. As a result, each REPL input produces a corresponding .wasm module that is compiled, linked, and loaded at runtime, incrementally extending the current execution environment.
Following the initial version in LLVM 17, the support for WebAssembly was incrementally consolidated by a series of pull requests addressing various issues and corner cases with respect to symbol duplication, memory corruption, and dynamic linking failures. We believe the version available today in LLVM 20 is a solid foundation to build upon.
- PR #86402 — Initial WebAssembly support for clang-repl
- PR #113446 — Fix undefined lld::wasm::link symbol
- PR #116735 — Improve flags responsible for generating shared wasm binaries
- PR #117978 — Fix generation of wasm binaries
- PR #118107 — Remove redundant shared flag while running clang-repl in browser
For a deeper understanding of the constraints of the WebAssembly platform for JIT compilation, refer to the GSOC report: Anubhab Ghosh — WASM Clang-Repl.
Porting Xeus-Cpp to Emscripten-forge
In addition to challenges with emulating JIT compilation in WebAssembly, packaging Xeus-Cpp for emscripten-forge presented another challenge: managing shared objects within the initial WebAssembly executable, which are typically built statically for other xeus kernels. This issue was addressed in the following PRS:
- PR #145 — Handle .data file for kernels that require preloading
- PR #146 - Kernel shared libs handling
As a result of this work, required side modules are now specified as metadata in the Jupyter kernelspec kernel.json file, enabling JupyterLite to automatically detect and utilize them.
Inline documentation
To fully integrate C++ as a first-class citizen within the Jupyter ecosystem, supporting inline documentation is crucial. Similar to the Python kernel, utilizing the ? magic command should allow users to inspect the following type, perform a lookup in the available documentation, and display the relevant information.
The C++ kernel comes equipped with a lookup file for the standard library, making it possible to execute queries like ?std::vector seamlessly.
This can be enabled for third-party libraries. Check out the documentation to enable inline help for your package.
This feature, inherited from the Xeus-Cling project, was initially created by Loic Gouarin.
Rich display
One of the core benefits of integrating C++ into the Jupyter ecosystem is the ability to leverage Jupyter’s rich MIME display system. Rather than limiting output to plain text, Xeus-Cpp allows you to render rich content like images, HTML tables, LaTeX, or even custom visualizations, all directly from C++.
This is made possible by the function xcpp::display, which sends a MIME bundle to the frontend. Each bundle can include multiple representations of the same object, such as text/plain, image/png, or even custom mime types.
This is illustrated in the following example:
A custom renderer for any given type can be defined by defining the mime_bundle_repr function in the corresponding namespace, which is then picked up by Xeus-Cpp’s display system through Argument-Dependent Lookup (ADL). A large collection of mime types are supported by the JupyterLab frontend, which can also be extended with plugins to support custom mime types.
Advanced Graphics
Another compelling example leveraging the rich rendering capabilities, is to combine it with frameworks such as SDL for rich graphic content. Emscripten has built-in support for SDL (enabled with the -s USE_SDL=2 compilation flag), which allows us to leverage its power in C++ notebooks.
To illustrate this, we ported Kevin Beason’s legendary smallpt global illumination renderer (a path tracer in 99 lines of C++) into a C++ notebook. The scene is rendered using SDL onto an in-memory canvas, and the resulting image is captured and displayed with xcpp::display.
This setup showcases the seamless integration of SDL and the Jupyter display system:
- Use SDL for real-time or offscreen rendering
- Then pipe the result through xcpp::display(…) as a PNG or pixel buffer.
PR #299 added an example notebook supporting smallpt with SDL-based rendering. Feel free to access the notebook through our lite link . Since the number of samples per pixel (spp) directly correlates with how long the process runs (approximately 2 minutes in our case), we’ve included debug logs to keep users informed during rendering, so they aren’t left wondering about the progress.
This approach paves the way for interactive C++ graphics demos, ray tracing notebooks, and even the prototyping of browser-based video games from within WebAssembly C++ notebooks.
Loading Third-Party Libraries
For a user’s perspective, a key difference between a Jupyter kernel for a compiled language like C++ and e.g. Python concerns how third-party libraries can be utilized. In Python, one merely needs to import the relevant installed packages, while in the case of C++, the relevant compiled libraries must be loaded in addition to including the corresponding headers.
Thankfully, Clang-Repl provides the LoadDynamicLibrary to dynamically load shared objects, which was adapted to the WebAssembly based in LLVM PR #133037. Instead of relying directly on raw dlopen calls, this function ensures that memory layout, symbol resolution, and constructor invocation are handled correctly — even in a WebAssembly environment.
Building on top of this, the CppInterOp project made these features available via a high-level C++ API.
Library authors interested in facilitating the use of their package with the Jupyter kernel can include the calls to CppInterop’s LoadLibrary can simply be included in user-facing headers.
This has been done for example, in the Symengine library.
Symbolic Computing with Symengine
A compelling example of the Jupyter rich display system’s advanced capabilities with Xeus-Cpp is the SymEngine package, a powerful C++ library for symbolic computing.
Originally designed as a backend for symbolic Python libraries like SymPy, SymEngine offers native support for expression trees, calculus and Latex/MathJax rendering of mathematical expressions. We have integrated SymEngine into the emscripten-forge distribution, making it readily available for use. Once installed, it works seamlessly with Xeus-Cpp, requiring no additional setup.
SymEngine expressions can be rendered via xcpp::display(), which produces LaTeX-formatted expressions and delegates their rendering to MathJax in the frontend.
Array-based Computing
Jupyter notebooks are particularly popular among data scientists, students, and practitioners of scientific computing. Array-based computing is at the center of the practice, be it in Python with NumPy, in R, GNU Octave, or Julia.
A framework for array-based computing is C++ is Xtensor. Xtensor’s syntax closely mirrors that of NumPy, supporting features like array broadcasting, lazy evaluation, and element-wise operations with an idiomatic C++ API. Using Xtensor in combination with the C++ Jupyter kernel provides a similar user experience to that of NumPy with the Python Jupyter kernel.
Built on top of Xtensor, Xtensor-BLAS brings the high-level syntax of Xtensor with the performance of OpenBLAS. It provides convenient, NumPy-style access to BLAS and LAPACK routines making operations like matrix inversion or system solving easy to express and execute.
To use these libraries inside a JupyterLite notebook, all you need is to include Xtensor-BLAS in your environment (available via emscripten-forge). Under the hood, Xtensor-BLAS dynamically loads libopenblas, which is bundled automatically as a dependency.
SIMD Acceleration
We can take a step further with SIMD (Single Instruction, Multiple Data) acceleration.
Using raw WebAssembly intrinsics : WebAssembly includes 128-bit SIMD instructions, which are provided in Clang’s <wasm_simd128.h> header. They can be used to perform vectorized operations directly.
Using Xsimd : Here’s an improved version of your text with better flow, clarity, and conciseness.
Rewriting specialized vectorized routines for WebAssembly can be cumbersome. This is where frameworks like Xsimd come in. It provides a high-level SIMD API in C++ that abstracts platform-specific SIMD instructions. With Xsimd, you can efficiently operate on batches of numbers using familiar arithmetic operators or mathematical functions, while benefiting from optimized performance under the hood.
Xsimd powers SIMD acceleration in Xtensor and is also used in many other projects, including Apache Arrow, Firefox, Velox, Pythran, and Krita. Here’s how the same example would look when when using Xsimd instead of raw intrinsics:
Magic Commands
In addition to interpreting standard C++ code, Xeus-Cpp also supports magic commands, special notebook commands prefixed with % or %% that allow for extended functionality not defined by the C++ language itself.
These magics let you interact with the runtime environment, manage files, or perform meta-operations, all from within a code cell. The following magics are available:
- %file : The %file magic enables file creation and editing directly from a code cell. You can append to an existing file using the -a flag
- %timeit : A %timeit magic has also been prototyped via PR #289 by @kr-2003, enabling performance measurement for expressions in the notebook.
However, due to the lack of last value printing support in Clang-Repl, this feature currently relies on a workaround and hasn’t yet been merged. Once upstream support is in place, %timeit will be integrated cleanly and available out of the box.
Here’s a preview of the %timeit magic in action:
Magic commands open the door to a more expressive and interactive notebook experience for C++. While support is currently limited to a few core magics, this is just the beginning — with more capabilities and enhancements expected in the near future.
Interactive Widgets
Beyond rich mime type rendering and inline documentation, the Jupyter widgets system allows users to create interactive components in the Jupyter notebook, leveraging by-directional communication with the kernel for more interactivity.
The xwidgets package is a C++ implementation of the Jupyter widget protocol, comprising an implementation of all the core Jupyter widgets, and which underlies other interactive visualization libraries like xleaflet or xcanvas.
Here are a couple examples demonstrating interactive widgets through xwidgets & xcanvas with Xeus-Cpp-lite.
If you inspect the codebase of xwidgets or xcanvas, you will notice that it contains very little code. All that is required on the C++ end is a declaration of the attributes that should be synchronized with the frontend, and serialization methods for complex data types.
Most of the programmatic logic for Jupyter interactive widgets lies in the frontend implementation. The consequence for the C++ Jupyter kernel is that with seemingly little implementation effort, we could enable an entire ecosystem of widget libraries, covering a large range of use cases. Interactive widgets for 2-D plotting (bqplot, plotly) and 3-D plotting (ipyvolume), GIS (ipyleaflet), efficient rendering of meshes (ipygany), graph visualization (ipycytoscape), and many more could be almost automatically connected to the C++ world.
The GitHub repository https://github.com/jupyterlite/xeus-lite-demo is a template for creating a JupyterLite deployment on GitHub pages that includes the packages specified in a conda environment.
The process is as follows:
- Create a new repository from the GitHub template.
- Enable the deployment on GitHub pages from a GitHub action, as shown in the README.
- Edit the environment file to include the desired packages.
For example, to deploy an C++ kernel with Symengine & Xtensor-blas installed, the environment.yml file would contain the following:
name: xeus-cppchannels:
- https://repo.prefix.dev/emscripten-forge-dev
- conda-forge
dependencies:
- xeus-cpp
- symengine
- xtensor-blas
The deployment linked at the beginning of this article was created with this template.
The journey of interactive C++ in Jupyter, both natively and in the browser, is still evolving. While Xeus-Cpp already offer powerful foundations, several exciting developments are underway that will further expand their capabilities across platforms
Debugger Support:
Currently, only a few Jupyter kernels like Xeus-Python offer full integration with the Jupyter Debug Protocol to enable the Jupyterlab debugger. We’re working to bring that experience to C++ as well.
This summer, thanks to Google Summer of Code 2025, Abhinav Kumar will be implementing native debugging support in Xeus-Cpp.The approach is based on using LLDB and its Debug Adapter Protocol (lldb-dap). Modeled after Xeus-Python, it leverages LLDB’s Clang and JIT debugging capabilities to enable breakpoints, variable inspection, and step-through execution directly within the JupyterLab debugger interface.
We already have a proof-of-concept demonstrating that debugging C++ in Jupyter is possible.You can track this effort here: xeus-cpp issue #282.
In the longer term, we aim to explore extending debugging to the browser through Xeus-Cpp.
Cuda Support in Xeus-Cpp:
CUDA support in Clang-Repl was broken by the time LLVM 20 was released, but that changed with this upstream patch:
We’re now preparing to bring CUDA support directly to Xeus-Cpp, enabling GPU programming from a Jupyter notebook. Early planning is underway:
- GitHub issue: #300 — CUDA REPL support in xeus-cpp
Last Value Printing:
In cling, a widely appreciated feature is last value printing, simply writing a variable at the end of a cell automatically displays its value, without needing any manual printing. This behavior naturally carried over to xeus-cling, making interactive exploration even smoother.
This feature stands almost done in Clang-Repl:
Once merged, this will be integrated into Xeus-Cpp, enabling seamless REPL experience for C++ users.
Whether you’re a developer, package maintainer, educator, or simply curious about C++ in the browser, contributions are always welcome!
Packaging for the Browser
Many of the capabilities in xeus-cpp-lite rely on packages built for the WebAssembly platform via emscripten-forge. If you’re interested in porting a library or creating a new recipe, please visit emscripten-forge.org for detailed guidelines and examples.
PRs and discussions are welcome at github.com/emscripten-forge, especially if you’re looking to bring more numerical, graphical, or scientific libraries into the browser.
Improving Xeus-Cpp, CppInterOp and Clang-Repl
We welcome contributions to the broader xeus project, which is part of the Project Jupyter ecosystem. Xeus-Cpp in particular is actively seeking contributors, whether to improve kernel behavior, expand feature support, or explore new magic commands and UI integrations.
On the LLVM side, the Clang-Repl component is still under active development and can greatly benefit from community contributions, particularly around WebAssembly backend improvements and incremental execution features.
Areas of Need & Opportunities for Collaboration
In addition to code contributions, we are seeking funding opportunities to advance the project. We’ve identified several areas where focused effort could significantly enhance the ecosystem, and we warmly welcome collaborators or potential funding partners interested in helping us move these efforts forward, including:
- Integration testing: LLVM currently lacks tests for Clang-Repl + WebAssembly. Establishing a reliable test harness in this area is both challenging and critical.
- Interactive plotting: A robust plotting library or plugin for Xeus-Cpp would make the kernel far more expressive for teaching, data exploration, and scientific computing.
- Plugin system for magics: Further decoupling kernel extensions would simplify development and allow community-driven growth. A concrete description of this project is framed on CERN’s website.
If you or your organization shares the vision of making modern C++ more accessible, especially in-browser, we’d love to hear from you.
Anutosh Bhat is a scientific software engineer at QuantStack. His contributions range from the core of LLVM and Xeus, to their packaging for the Emscripten-forge software distribution. In the course of this endeavor, Anutosh was granted commit rights to the LLVM project.
Vassil Vassilev is a Research Software Consultant with Princeton at CERN. As the co-creator of Cling and the lead developer of the Clang-Repl and CppInterOp projects, he has spearheaded the development of interpreted C++ for over a decade.
The work by Vassil Vassilev on this project is done in the context of the Compiler Research group, funded by the National Science Foundation grant OAC-2311471.
The work by Anutosh Bhat on this project is funded by QuantStack, as part of a broader initiative to enable the data science ecosystem within the Web browser.
Work on prototyping the WebAssembly infrastructure for Clang-Repl was done by Anubhab Ghosh, supported through Google Summer of Code 2023 via the LLVM mentoring organization.
A significant portion of the Xeus-Cpp codebase was inherited from the Xeus-Cling project and adapted for the Clang-Repl interpreter. This includes work by Sylvain Corlay, Johan Mabille, Loic Gouarin, and Martin Renou.
We are grateful to Thorsten Beier, the creator and lead developer of the emscripten-forge software distribution, for his support in packaging LLVM and Xeus-Cpp.
We extend our gratitude to Martin Renou and Anastasiia Sliusar for devising the processing of conda packages in the frontend, which underlies the support of third-party libraries in WebAssembly environments used by Xeus-Cpp and xeus-r. We are grateful to Johan Mabille for creating xeus and for his code reviews within the xeus stack. We also thank Jeremy Tuloup for creating JupyterLite.
We are grateful to Matthew Barton, Tharun Anandh & Abhinav Kumar for their numerous and significant contributions to the Xeus-Cpp project.
Finally, we are immensely grateful to the authors and maintainers of Emscripten and LLVM for building such remarkable technologies and for their engagement and support during our numerous interactions on GitHub.
.png)
