Hi All,
I’m Anutosh, a core developer on clang/clang-repl and part of Project Jupyter. Previously I was a member at another LLVM-based Fortran compiler, LFortran .
I had presented the above idea in the previous flang biweekly meeting and was advised to frame a RFC for the same.
-
The Fortran ecosystem still lacks a modern, interactive workflow—something equivalent to a REPL or an Interpreter that many other LLVM-based languages now support. This limits rapid prototyping, teaching, exploratory numerical work, and smooth integration with tools like Jupyter.
-
What was achievable through LFortran : During my time working on LFortran , we could leverage the Interpreter it provided to come up with a Jupyter Kernel which demonstrated an incremental, REPL-style Fortran workflow. Here's a demo notebook that we can run through LFortran.
-
Why Flang ? Since Flang is the long-term Fortran frontend for LLVM—with FIR, MLIR, and tight integration across the toolchain—it feels like the natural place to bring interactive Fortran into the mainstream.
The motivation for this work comes from the systems we (The Project Jupyter & Clang-repl Community) have been working on since the past 1.5-2 years.
Even before we get started, I would like everyone to have a go at the JupyterLite C++ Kernel
- Jupyterlite is a Jupyterlab distribution that runs entirely in the browser and is powered by WebAssembly.
- Open up a C++/C kernel or one of the demo notebooks provided on the left. You should be able to "Interpret" C++ completely in the browser, no local setup whatsoever in a REPL format.
- Feel free to explore anything from BLAS & LAPACK routines through Openblas & Xtensor, to interactive Jupyter widgets to Graphics using SDL.
Now that you might have experienced using the Kernel, let me highlight the work done for supporting the kernel.
- Clang-Repl is an interactive C++ interpreter that allows for incremental compilation. Please go through the link attached for the design and workflow.
The design is tightly coupled with the Clang compiler itself: clang-repl reuses Clang’s parsing, semantic analysis, and code-generation pipeline, and builds an incremental execution layer on top of LLVM’s JIT infrastructure (ORC). So clang-repl is provided both as a standalone tool and as a library (libclangInterpreter) so its incremental facilities can be embedded directly into other applications.
- Xeus-Cpp which is the native Jupyter C/C++ Kernel working on linux, macOS and windows platforms built on top of clang-repl.This enables several capabilities that go beyond what the standalone clang-repl tool provides:
- General C++ workflows: Here's a demo notebook showcasing basic C++ and Jupyter's rich rendering capabilities (images/widgets etc)
- CUDA support: We recently fixed running cuda through clang-repl. Here are some demo notebooks (notebook1 notebook2)
- OpenMP support: We also added OpenMP support to clang-repl, which may be particularly relevant for Flang given the amount of OpenMP work happening in the Fortran ecosystem. Here's a demo notebook
- Xeus-Cpp-Lite which is a JupyterLite kernel shown above using Xeus-cpp & JupyterLite.
As clang-repl uses the ORC JIT for native platforms but JIT doesn't work with WASM, we framed a separate WASM backend for clang-repl. This involved compiling llvm, clang and the kernel, Xeus-Cpp itself to WASM. Here's my blogpost on Project Jupyter's medium from a technical standpoint explaining all the effort behind this.
- Debugger Support in Xeus-Cpp : We have been participating in Google Summer of Code for the past few years, and this year we introduced Debugger Support for xeus-cpp.
This required running clang-repl out-of-process, integrating with LLDB and lldb-dap , and building on top of Jupyter’s Debug Adapter Protocol implementation. The result is the ability to set breakpoints, step through code, inspect variables, and debug JIT-generated code entirely from a Jupyter notebook. Here's a demo for the same and the final gsoc report.
I recently also gave a talk about the whole ecosystem revolving around interactive C++ through Jupyter at PyData Paris 2025. Here are the slides for the same. The video for the talk would be available soon.
Given this background, I began exploring whether something similar could be possible for Flang.
-
Going through Andrzej Warzynski's talk “How to write a new compiler driver? The LLVM Flang perspective” from EuroLLVM 2022 I realize that Flang’s frontend and driver architecture take intentional inspiration from Clang. This makes me think that the incremental model we use in clang-repl could also be adapted for Fortran.
-
I see an Execution Engine based on MLIR exists. Just as clang-repl lowers interactive input to LLVM IR and executes it through ORC, Flang lowers to FIR/MLIR, and the presence of MLJIT suggests a path toward an incremental, REPL-style execution model based on MLIR rather than LLVM IR.
-
This could form the foundation for a “flang-repl,” with the possibility of extending it into a full interactive environment similar to xeus-cpp. In particular, if an incremental pipeline for Flang were possible, we could imagine:
- A flang-repl tool, analogous to clang-repl. I think we can come up with a flang interpreter tightly coupled with the compiler.
- A xeus-fortran kernel for interactive Fortran in Jupyter, with the ability to load and use existing Fortran libraries from ecosystems such as fpm, conda-forge, or system installations.
- A xeus-fortran-lite variant, running Fortran entirely in the browser through WebAssembly.
- Interactive OpenMP workflows, similar to what we support today in xeus-cpp.
- Debugger integration, using the same Jupyter DAP stack we built for C++.
These are questions that naturally arise when evaluating this proposal. I’ve attempted to provide preliminary answers, and community insight would be valuable in validating or extending them.
Q1) Flang was built with the idea of being an ahead of time compiler. Out of the box it will not be able to parse incomplete source code as that will result in semantic errors. How do incremental workflows fit here ? Will this require deep changes to parsing and semantics?
You are absolutely right that Flang is currently an ahead-of-time compiler with a different frontend than Clang.
What I wanted to highlight is that Clang itself started in the same “pure AOT” world (technically it implements the c++ standard where neither compilation nor interpretation is mentioned so in theory should support both equally well but most of the use cases are AOT). Out of the box it assumed a complete translation unit, performed all semantic checks at end-of-TU, and then tore down state.
To get clang-repl working, we had to introduce an explicit incremental mode on top of that design: a new TU_Incremental kind, an IncrementalAction that avoids the usual end-of-TU cleanup, and an IncrementalParser /IncrementalExecutor layer that keeps the AST, Sema state and generated IR alive across many snippets instead of discarding them after one compile. In other words, we didn’t change Clang’s core semantics, but we wrapped them in a growing “ever-extending” translation unit and routed each snippet through the existing frontend, then into the JIT. All of this is encapsulated within an Interpreter.
This "ever-growing" AST can be seen through an AST dump
$ ./clang-repl --Xcc=-Xclang --Xcc=-ast-dump "int x = 10;"
TranslationUnitDecl 0x555561012608 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x5555610132c8 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
| `-BuiltinType 0x555561012bd0 '__int128'
.......
.......
TranslationUnitDecl 0x555561060e08 prev 0x555561012608 <<invalid sloc>> <invalid sloc>
|-CXXRecordDecl 0x555561060e70 <input_line_0:5:5, col:39> col:12 referenced struct __clang_Interpreter_NewTag definition
| |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
.......
.......
TranslationUnitDecl 0x55556108aeb0 prev 0x555561060e08 <<invalid sloc>> <invalid sloc>
`-VarDecl 0x55556108af30 <input_line_1:1:1, col:9> col:5 x 'int' cinit
`-IntegerLiteral 0x55556108af98 <col:9> 'int' 10
What can be seen here ?
- 3 TUDECL nodes - 1 for Builtin, 1 for Runtimes, 1 for Input1. Each input would have 1.
- Each TUDECL node has links with the previous node. So every node is tracked.
For Flang, I’m imagining something analogous in spirit : keeping the existing parser & semantics as-is, but adding a minimal “incremental mode” where the frontend state is preserved between snippets, and where some of the end-of-compilation checks are relaxed so that later inputs can extend earlier ones.
So there are really two orthogonal pieces:
- making the frontend capable of being driven incrementally, and
- feeding the resulting MLIR into MLIR JIT.
Some References
- Here's the RFC for clang-repl. It also mentions that Clang’s existing infrastructure happened to align really well with incremental use cases.
- Clang-repl's 1st commit & 2nd commit that have almost all of the design changes needed to support incremental mode.
Q2) Flang is technically not a cross compiler. How will the wasm use case work ? Even if we have flang-repl, would we be able to achieve flang-repl completely in the browser ?
Absolutely true, Flang cannot generate WebAssembly output out of the box. Despite this, we’ll soon see that with LLVM’s modular design it’s possible to use the Flang frontend with LLVM’s WebAssembly backend. I would like to highlight work done by few people in this regard
- George Stagg was able to hack around flang and compile BLAS & LAPACK to WASM. He describes his work & patches through a very well framed blog post here
- Serge Guelton, a LLVM core dev was able to build on top of this and introduced some of patches in a more generic way upstream to flang. I would encourage all to go through his 5 minute long FOSDEM 2025 talk with the topic Flang + WASM. He speaks on how people want to run Scipy in the browser through Jupyter for education and as Scipy depends on LAPACK, we would need to compile it to WASM.
- Though some patches were merged some remained open. But Axel Obermeier, another LLVM and conda-forge core dev hosted this patched version of flang on conda-forge
- Since R relies on BLAS and LAPACK and many essential R packages wrap native libraries that use Fortran, Isabel Parades was able to use the above build to compile the whole R stack for WASM and get a working Jupyterlite R kernel working entirely in the browser.
- Thorsten Beier, Isabel Parades and Ian Thomas also used this build to compile scipy for wasm. Try it out here
Building on top of this work & the design used for clang-repl would be the way to proceed here. How clang-repl's wasm backend works is very well explained on slides 9/10/11 from my PyData Paris talk, but giving an overview :
i) Every input cell is compiled into a wasm object file, which is fed to wasm-ld to generate a standalone side module. ii) This is loaded on top of a main module at runtime using emscripten's dlopen mechanism. These modules share the same memory as the main module and there's symbol resolution going on at load time to access definitions from previous cells.
As an experiment we could try to replicate flang-repl in the browser through Jupyter using the C++ Kernel, Xeus-Cpp-Lite. This works because we expect the same design to work in theory.
Cell 1 : Has a global definition for a variable Cell 2 : Has an incomplete module that depends on the definiton above Cell 3 : Call for module defined in cell 2
// Cell 2
MODULE mymod
IMPLICIT NONE
INTEGER :: a ! global symbol, not defined here
CONTAINS
SUBROUTINE foo(y, z)
IMPLICIT NONE
INTEGER, INTENT(IN) :: y
INTEGER, INTENT(OUT) :: z
z = a + y
END SUBROUTINE foo
END MODULE mymod
Now how would cell 2 execute at runtime in the browser
# Using the patched flang as discussed
# Step 1: Compile Fortran code to relocatable object file
flang --target=wasm32-unknown-emscripten -fPIC -c cell2.f08 -o cell2.o
llvm-nm cell2.o
00000000 D _QMmymodEa
00000001 T _QMmymodPfoo
# Step 2: Link to a shared WebAssembly module with undefined symbol allowed
# Because load time is responsible for symbol resolution
wasm-ld -shared \
--experimental-pic \
--import-memory \
--stack-first \
--allow-undefined \
cell2.o -o cell2.wasm
#Step 3: Loaded using emscripten's dlopen in the backend
The above hardcoded dlopen in the frontend would be done in the backend at runtime when we execute cell 2 as fortran code through Jupyterlite.
- MLIR + WASM work in the llvm/eudsl incubation project : As a final confidence boost that the above approach is feasible, I came across some independent work on the EUDSL project. The EUDSL project exposes MLIR through Python bindings and allows running it Jupyter/JupyterLite.
Recently, they even added a WebAssembly MLIR execution engine (PR) whose implementation is simply using clang-repl's design. I would have introduced something similar for the "flang-repl in the browser" concept and now we also have something to take inspiration from.
Checkout the Jupyterlite notebook they provide. In practice, this means they can parse and construct MLIR from Python, execute it via the MLIR ExecutionEngine, and do all of this inside a JupyterLite instance running entirely in the browser.
In other words: we would already know how to
- lower incremental Fortran input to FIR/MLIR via a flang-repl frontend, and
- execute MLIR both natively (with the upstream MLIR JIT) and in the browser (using the MLIR execution engine from EUDSL’s JupyterLite playground).
Taken together with the flang→WASM proof-of-concept above, this is effectively the “cherry on top”.
- Wasm Dialect in MLIR by the WAMI Project (Carnegie Mellon University) : This is something I still need to explore and haven't had a chance to dive deeper.
The WAMI project from Carnegie Mellon demonstrates a complete MLIR-based pipeline for compiling to WebAssembly. It introduces an SSA-based SsaWasm dialect that enables high-level MLIR dialects like func, scf, and arith to be directly lowered to structured WebAssembly, preserving abstractions without relying on LLVM IR. The pipeline produces valid .wasm binaries and supports modern WebAssembly features, offering a modular path for languages targeting the Web