Skip to content

Brian2 downstream support: wheels, Windows, and migration from cppyy #39

Open

Description

@Legend101Zz

Hi team , So I'm a contributor to Brian2 and maintain an experimental cppyy code-generation backend for it, a simulator for spiking neural networks (brian-team/brian2#1769, still a draft , we have been working on it for over an year now ).

I had also discussed the platform-support side of this work earlier with Wim Lavrijsen (wlav) in the cppyy repository, wlav/cppyy#311

A bit of context on why I'm asking. Brian2 generates C++ for simulations and currently runs it through Cython. That means compiling and importing each generated code object before it can run. On a cold cache, startup can take tens of seconds, which is especially noticeable for the short, exploratory simulations people tend to run repeatedly.

Replacing that path was the focus of my Google Summer of Code 2025 project (https://brian.discourse.group/t/gsoc-2025-replacing-brians-just-in-time-compilation-mechanism/1450/9 ), Replacing Brian's just-in-time compilation mechanism,
with the Brian2 maintainers (@mstimberg ).

We ended up using cppyy because it gives us an actual in-process JIT instead of doing AOT compilation at runtime. It also lets us keep hot data structures on the C++ side rather than repeatedly crossing the Python/C++ boundary in inner loops.

On Linux and macOS, the backend now does what we wanted it to do and we see a huge performance boost there too. The remaining problem is less about the backend itself and more about whether we can actually ship it to Brian2 users.

Our cppyy surface area is fairly small: cppdef, include, add_include_path, gbl, and the buffer protocol for exposing NumPy arrays to C++ as raw pointers.

I've only recently caught up with the fact that wlav/cppyy is frozen, the three compiler-research forks were archived on August 4, and CppJIT is now the successor. Before we decide how to land our PR, I'd like to understand where CppJIT is heading, particularly around packaging and platform support.

1. Wheels and platform coverage

So I checked and It's good to see #32 and #33 moving toward wheels that bundle LLVM. For Brian2, that would solve one of the biggest problems with the current cppyy stack.

The part I'm less clear on is the intended platform coverage.

Brian2 currently tests on Linux x86-64, Windows x86-64, macOS Intel, and macOS arm64. We publish wheels for manylinux x86-64/aarch64, musllinux x86-64, Windows, and both macOS architectures. Python support follows SPEC-0, so we're on 3.12–3.14 today, which happens to line up exactly with CppJIT's cp312/cp313/cp314 build list.

The important constraint for us is that most Brian2 users are neuroscientists installing through pip or conda. We can't really make a backend the default if installing it means building LLVM locally.

As #22 currently stands, [tool.cibuildwheel] has no Windows section, Linux only builds x86_64, macOS only builds arm64, and musllinux is skipped. That would leave Windows, Linux aarch64, macOS Intel, and musllinux uncovered relative to Brian2.

Are those platforms intended for the first release, or something you'd expect to add later?

A few related questions:

  • What macOS deployment target are you aiming for? MACOSX_DEPLOYMENT_TARGET = "26.0" in the PR would be a fairly high floor for Brian2 users.
  • Is conda-forge part of the plan?
  • I assume a LICENSE will land before the first release. Do you already know which license CppJIT will use?

2. Windows

This is probably the question that matters most for us, and it is also something I had discussed with wlav while working on the original cppyy-based backend.

From the outside, most of the underlying pieces seem to exist already. CppInterOp's win2025-msvc-llvm22 and win2025-msvc-cling jobs are green, ci-workflows' setup-llvm knows about windows-2025, CppJIT still has the IS_WINDOWS handling inherited from cppyy, and conda-forge has llvmdev 20–22 for win-64.

What I can't find is anything connecting those pieces yet. The reusable cppjit.yml installs dependencies only for Linux and macOS, neither ci.yml nor nightly.yml has a Windows job, and none of CppInterOp's Windows jobs enable cppyy: On.

Is Windows simply work that hasn't been reached yet, or is there a known technical blocker?

Also, do you expect to support a particular range of MSVC toolsets?

I'd be happy to help here. I already have a reproducible GitHub Actions workflow that drives a JIT backend end-to-end on Windows runners, so I can put together a Windows CI job if that would be useful.

3. What happens to the frozen cppyy stack?

Should downstream projects treat cppyy 3.5.0 / cppyy-cling 6.32.8 as effectively final?

So actually all four packages were last released on 2024-12-17, and three of the four are still sdist-only across platforms, so even Linux users need a compiler for a normal install today.

There's also the unreleased Windows linker fix from wlav/cppyy#282:

LNK2001: Cppyy::GetNumBasesLongestBranch

As I understand it, CPyCppyy 1.13.0 build-requires cppyy-backend==1.15.2, while its setup.py run-requires 1.15.3. That's fixed on master but was never released.

Is there any chance of one final cppyy release containing fixes like that, or should downstreams assume there won't be another release and work around them?

4. Migration from cppyy to CppJIT

wheel.packages currently lists only cppjit and cppjit_backend, so I'm assuming there won't be a cppyy compatibility alias and downstream projects should migrate imports themselves. Is that the intended path?

Beyond the rename, how much API drift do you expect from cppyy 3.5?

Our usage is deliberately narrow, so most of it looks straightforward. The bit I'm most interested in is the buffer/pointer path, since we rely on passing NumPy-backed memory into generated C++ without copying it.

One smaller question: the README currently asks for LLVM 21, while CMakeLists.txt accepts LLVM 20–22. Which version would you recommend downstream projects target?

5. What would you recommend we do?

This is ultimately the decision we're trying to make.

We have about a year of work behind this backend. It works and gives us the behavior we were aiming for on Linux and macOS, but packaging and Windows support are what stand between an experimental backend and something Brian2 can actually put in front of users.

Would you recommend that we:

  1. land against the existing cppyy stack now, document the Windows limitation, and migrate to CppJIT later; or
  2. wait for the first CppJIT release and target it directly?

If the second path makes more sense, even a rough idea of the release/platform timeline would help us plan the Brian2 side.

Thanks for taking this project on. I'm also happy to help with testing or CI rather than just asking about the roadmap :) .

Brian2 should be a pretty good real-world test case for this too. We generate C++ heavily, pass NumPy-backed memory across the boundary, JIT repeatedly, and already have CI across Linux, Windows, and both macOS architectures.

Activity

  1. Legend101Zz commented on Aug 24, 2026

    @Legend101Zz
    Author

    Some performance numbers from our Brian2 PR, just to give a sense of why I'm keen to get this shipped: brian-team/brian2#1769 (comment)

    cppyy made a pretty noticeable difference for the workloads we care about, so I'm really excited to get this in front of Brian2 users. The main thing holding us back right now is getting the Windows side into a state we can actually ship :(

  2. mcbarton commented on Aug 24, 2026

    @mcbarton

    Hi @Legend101Zz

    Here are 2 major issues I know there currently blockers on having a Windows build of cppyy.

    1. cppyy relies on having a shared library on CppInterOp. Over a year ago I did a Windows shared library build of CppInterOp on Windows, but ran into a list of unresolved external symbol errors . Its possible that a fix was needed upstream in llvm to fix these errors, but I never followed up on this. I think the unresolved external symbols are listed here https://github.com/mcbarton/CppInterOp/blob/83837d6a33fcc7bb472f7b6ba3c109711161afb1/lib/CppInterOp/clangCppInterOp.def , but this is only from some vague recollection on what I did looking into the problem.

    2. If you try and print anything to standard output and error on Windows then you get errors due to lack of emulated tls symbols (See clang-repl JIT session error: Symbols not found ... _emutls_get_address, __emutls_v._Init_thread_epoch ...error: Failed to materialize symbols llvm/llvm-project#187040 for one such issue raised about this). A few attempts have been made to fix this issue, with the latest being Reland "[ORC] Track __emutls_t definitions in IRMaterializationUnit" (#207161) llvm/llvm-project#209717 which currently has a problem which [clang-repl] Add CLANG_ENABLE_EMUTLS_GET_ADDRESS cmake option llvm/llvm-project#217700 is trying to fix. Another problem with emulated tls symbols is detailed in this comment [ORC] Track __emutls_t definitions in IRMaterializationUnit llvm/llvm-project#207161 (comment) . @conrade-ctc has looked quite a bit into this issue so should be able to give you more details.

    There will almost certainly be more issues to solve after these have been fixed, but I hope if these were fixed then we would be in a state where we could have a Windows build of cppjit that could be iterated on. Just going to caveat these comments are by someone who isn't a core member of the compiler research team, and only work on this project in my spare time.

    cc @vgvassilev @aaronj0 @Vipul-Cariappa as people who probably know more issues blocking a Windows build.

  3. aaronj0 commented on Aug 25, 2026

    @aaronj0
    Collaborator

    Hi @Legend101Zz,

    Thanks for reaching out and the nice overview of your use case for cppyy. I'd be curious to know more about how your benchmarks in brian-team/brian2#1769 (comment) were done. This is some pretty cool work.

    A quick overview on the status with cppyy/cppjit:

    We've recently begun full development on this monorepo, after previously working on the compiler-research forks. This new design allows for a leaner and simpler build setup. There's a few details in this presentation on how the underlying interpreter-layer and type-system evolved following CppInterOp and upstream LLVM's clang-repl.

    It may help to know that you are not the first: the ROOT data analysis software used at CERN has based their Python interfaces entirely on cppyy for many years, and are in the process of migrating to CppJIT, among a couple of others. One of the goals with CppJIT is to ensure easy adoption for downstream domain-specific libraries that require automatic JIT-based interop. With the new core, we've re-engineered the codebase and compiler infrastructure to be more robust and efficient (enabling more language features and CUDA support)

    Platform support:

    • As you've pointed out, the wheels bundle LLVM - standard releases either built on manylinux images. The wheels are slimmer and don't trigger any on-system builds.
    • Apart from LLVM, CppInterOp is a dependency. For source builds, it is pulled in via ExternalProject_Add and handled by scikit-build-core.
    • For package-manager installs, everything is self contained so unlike what the README currently says, LLVM is not an on-system requirement.
    • We are indeed closely following SPEC 0 for supported dependencies and are implementing a few more SPEC aligning patches.
    • The chosen macOS deployment target is 14: https://github.com/compiler-research/ci-workflows/blob/main/recipes/llvm-wheel/build.py#L31, the code in Support building wheels for manylinux and OSX #22 is a WIP and will use this infrastructure.
    • Conda infrastructure is planned and preliminary work has been done but not upstreamed. It is in the pipeline along with relicensing, planned for 0.2.
    • Linux aarch64, macOS Intel are immediately planned follow ups for wheels building jobs (Support building wheels for manylinux and OSX #22), musllinux and Windows would only happen post 0.2. Note that intel mac support is best-effort and will drop as Apple drops support.

    I'm assuming there won't be a cppyy compatibility alias

    • There is a planned legacy mode that would keep the cppyy.gbl style alias working but note that won't hide the underlying changes still present in behavior, proxy metadata and new type-system.

    how much API drift do you expect from cppyy 3.5

    • On the Python frontend, it is currently almost 1-to-1. Your elaborated subset (cppdef, include, add_include_path, gbl) and zero-copy NumPy buffer path is all supported and tested. On the public C++ API (formerly under CPyCppyy), we have not converged on what that would look like, but you can still use those headers (API.h) if you have some extensions on top.

    do you expect to support a particular range of MSVC toolsets?

    • Following CPython 3.12–3.14 official Windows builds, we should target VS 2022+ for 64-bit Windows platforms

    Which version would you recommend downstream projects target?

    • We aim to support the latest stable release of LLVM based on CppInterOp's support window. Currently we are building wheels with LLVM 21 but more stable releases towards the end of the year will be based on 22.

    Windows:

    • Yes, CppInterOp currently has windows CI, and ROOT also tests CppInterOp on Windows. Build support for cppjit on Windows is planned for after release 0.2b as that is quite an involved task. Feel free to pick that up if you are interested, but right now the focus is to have stable Linux/macOS releases for 0.2b. Our CI infrastructure is based on https://github.com/compiler-research/ci-workflows, similar to how CppInterOp does it we would need the same abstraction when building and testing cppjit on windows.

    • On the LLVM clang-repl issues brought up in mcbarton's comment above, we should be able to see how many tests are affected by adding a windows cell and trying to get a barebones cppjit windows build. The ROOT migration to CppJIT does pass windows as well (limited support) but that build uses Cling, while CppJIT defaults to clang-repl.

    cppyy:

    • Afaik there could be one more final release, cc'ing @wlav on this who can give us more clarity.
    • The requirements not pulling in the Windows fix seems like it may benefit from a final patch release

    Since your first option also limits Windows support, I would recommend migrating to CppJIT now and contributing the pieces necessary for Windows support. You will benefit from the much simpler package structure, build system and dependencies. We have a weekly meeting (if you email me aaron [dot] jomy [at] cern [dot] ch, I can add you to the mailing list) where we can discuss further your use case, our teams current plans and timelines.

    -- Aaron

  4. Legend101Zz commented on Aug 26, 2026

    @Legend101Zz
    Author

    Thanks both of you, @mcbarton @aaronj0. This is far more signal than I expected to get, and it makes the decision pretty straightforward for me. Really appreciate the fast replies :)

    On the benchmarks

    The setup is deliberately boring because I wanted the result to live entirely in the compile path and not be arguable.

    I’ve only run these on my local machine so far, so we still need to reproduce them across systems, and I’d expect some variation there. But the numbers we have right now look very promising for us.

    There are three Brian2 workloads:

    • small_lif: 2k neurons, 200 ms
    • coba: 4k neurons, 500 ms
    • kremer3: a Kremer barrel-cortex network, 500 ms

    Each number is the median of at least 8 samples. Every sample runs in a fresh subprocess, so build/compile time and simulation-loop time are separated by construction rather than something we try to untangle afterwards.

    Two details matter for making this a fair comparison:

    1. The Cython on-disk cache is wiped before every first-run measurement. We’re never comparing a fresh cppyy build against an already-cached Cython artifact. Once cached, the Cython path is extremely fast, thanks to years of optimization we’ve done around that caching system.

    2. cppyy runs with EXTRA_CLING_ARGS="-O2", matching the optimization level Cython extension modules get by default. Without that, comparing the simulation-loop numbers wouldn’t tell us much.

    The scripts are in cppyy_optimization_work/scripts/ on the PR branch. I’d be happy to pull them out into something runnable outside Brian2 if a JIT-startup benchmark would be useful to you. It’s actually a decent stress test on its own: lots of small, independent translation units, generated fresh in a loop. Pretty close to the worst case for anything that has to shell out to a compiler.

    One number worth calling out: the simulation-loop result is basically parity, 0.88x–1.02x, not a win. That’s what we expected, and we’ve reported it that way. I tried pushing it further, but eventually hit a floor of roughly 3 µs of residual per-call overhead in our hot path. Beyond that, the added complexity stopped being worth it.

    The startup numbers are the real result for us.

    On migrating

    Yes, we’re going to target CppJIT.

    Luckily, this is a very cheap decision for us right now. The backend is experimental, the PR is still a draft, it has never shipped in a release, it isn’t the default, and nobody has simulations depending on its current behaviour.

    Your confirmation that cppdef, include, add_include_path, gbl, and the zero-copy NumPy buffer path are supported and tested pretty much settles it. That is almost our entire surface area.

    We don’t touch the CPyCppyy C++ API at all, so the unconverged public C++ API isn’t really a constraint for us either. Good to know legacy mode is planned, though on our side I think I’d rather just do the rename and track the new type system directly.

    On Windows

    I’d really like to pick this up.

    Windows isn’t a nice-to-have for us. It’s also the platform where the problem we’re trying to solve hurts the most.

    Requiring users to have a working compiler is a much bigger ask on Windows than on Linux, and MSVC is the slowest of the three compilers in our compile step. So Windows users are simultaneously the ones who suffer most from the Cython compile cycle and the ones most likely to struggle with the setup in the first place.

    A JIT backend that skips Windows can’t realistically become our default, no matter how good the numbers look elsewhere :(

    There is one practical constraint I should put on the table though: I don’t own a Windows machine.

    Everything I’ve done on Windows so far has gone through GitHub Actions. I have a workflow there that exercises the full install → JIT path, which is how I ran into the LNK2001 issue in the first place.

    So the CI-shaped half of this is fine. The painful part is exactly what @mcbarton described: chasing unresolved symbols and emutls failures through a ten-minute CI round trip is a miserable development loop, and I don’t want to pretend otherwise.

    I’m going to see what I can do about getting access to an actual Windows box. If that turns out to be the blocker, I’ll say so early rather than quietly stalling on it.

    I’ll also spend this weekend getting the setup running locally and reading through the CppJIT codebase properly.

    Selfishly, I’ve wanted an excuse to get into this codebase for a while. cppyy was easily one of the most impressive pieces of software I came across while working on the GSoC project, and contributing back to it has been sitting on my list ever since.

    This looks like a pretty good chance to finally do that, so I’m looking forward to the weekend :)

    Aaron, I’ll email you for the mailing list. If you have even a rough sense of the 0.2b timeline, that would be useful, mostly so I can tell the Brian2 maintainers whether we should think about this PR in terms of weeks or months.

    Thanks again for all the help. Looking forward to digging into this.

  5. wlav commented on Aug 26, 2026

    @wlav
    Collaborator

    Afaik there could be one more final release, cc'ing @wlav on this who can give us more clarity.

    I've been absorbed by QBI (enjoying it a lot, too, so that won't change in a while).

    Note that for a "final" release to make sense, the backend needs updated, which is more work than just building what is currently there and putting it on PyPI.

    As for Windows, to ship "cppyy" as a usable module to allow folks to load their own new code, you kinda need a compiler, to have the complete set of standard headers. However, for a fixed application, shipping a pre-compiled header with all C++ standard includes and the application-specific ones does work (this assumes the VC++ run-time libraries are also installed). You can freeze this out with pyinstaller to have a clickable solution for Windows. (Adjust the hooks in __pyinstaller as needed.)

    Not owning a Windows box is a common problem. There are free images for VMs, though. I've always done all my Windows work that way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions