Skip to content

Load CppInterOp explicitly instead of from a global constructor - #26

Open
aaronj0 wants to merge 1 commit into
mainfrom
explicit-load-dladdr
Open

Load CppInterOp explicitly instead of from a global constructor#26
aaronj0 wants to merge 1 commit into
mainfrom
explicit-load-dladdr

Conversation

@aaronj0

@aaronj0 aaronj0 commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

The backend used to dlopen libclangCppInterOp from a global constructor, which runs inside the dlopen of libcppjit itself when Python loads the extension. The nested dlopen re-enters the loader on partially consistent state, runs the inner library's initializers with no ordering guarantees, and holds the loader lock through the entire interpreter and JIT setup, deadlocking any other thread that touches the loader (as reported by @Vipul-Cariappa). The constructor also had no error channel: on failure it printed and left a half-initialized library.

CppInterOp loading and interpreter setup now run in an exported, idempotent LoadCppInterOp() that _cpython_cppjit.py calls before the extension module import. This returns a status that turns into a proper exception on the Python side. The ApplicationStarter global is dropped in favor of a clean set of statics, all called by LoadCppInterOp().

@aaronj0
aaronj0 requested a review from Vipul-Cariappa August 18, 2026 15:37
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

Test Results

Configuration Result
macos-26-intel-llvm21-py3.14-cxx20 = 437 passed, 44 skipped, 85 xfailed, 32 xpassed, 100 warnings in 102.19s (0:01:42) =
macos-26-llvm21-py3.14-cxx20 ==== 438 passed, 37 skipped, 93 xfailed, 30 xpassed, 100 warnings in 55.72s ====
ubuntu-24.04-llvm21-py3.14-cxx20-vg ====== 553 passed, 25 skipped, 18 xfailed, 2 xpassed in 91.02s (0:01:31) =======
ubuntu-24.04-llvm22-py3.14-cxx20 ====== 553 passed, 25 skipped, 19 xfailed, 1 xpassed in 88.07s (0:01:28) =======
ubuntu-24.04-llvm22-py3.14-cxx20-cling ====== 543 passed, 25 skipped, 27 xfailed, 3 xpassed in 106.89s (0:01:46) ======
ubuntu-24.04-llvm22-py3.14-cxx23 ====== 574 passed, 3 skipped, 20 xfailed, 1 xpassed in 115.63s (0:01:55) =======

@aaronj0
aaronj0 force-pushed the explicit-load-dladdr branch from 7b7fc7e to 7f3655e Compare August 19, 2026 07:33
Comment thread src/interop/interop_wrapper.cxx Outdated
if (getenv("CPPJIT_OPT_LEVEL"))
optLevel = atoi(getenv("CPPJIT_OPT_LEVEL"));
// set opt level (default to 2 if not given; Cling itself defaults to 0)
int optLevel = 2;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That in practice slows things down because most of the functions are not in hot loops.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That means it is already slow, because this is not a change that I introduced in this PR, it always has been like this in the forks (and cppyy upstream). My changes are non functional w.r.t what we actually do at interpreter setup time.

What would be the right optlevel?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 is the right default and #pragma XXX optimize N should be the annotation for hot code. If that's not new we can measure before and after changing but ROOT had 50% slowdowns when switching from 0 to 1 iirc.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay but currently we don't have a way of #pragma XXX optimize N for clang-repl. Ideally we should guard the following:

  if (optLevel != 0) {
    std::ostringstream s;
    s << "#pragma cling optimize " << optLevel;
    Cpp::Process(s.str().c_str());
  }

with CPPJIT_USE_CLING since it does not do anything in the clang-repl case.

And in the case of ROOT none of this would apply because the interpreter is created by ROOT, and none of this code runs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 is the right default and #pragma XXX optimize N should be the annotation for hot code. If that's not new we can measure before and after changing but ROOT had 50% slowdowns when switching from 0 to 1 iirc.

And yes, that is not new. You can see in the diff.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should set the opt level to 0 and move on for now.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address in a follow up patch. I realise we should not unconditionally do Cpp::Process("#pragma cling optimize " << optLevel;")) because that only runs in the clang-repl case, so that would be an unrelated change to what this patch does.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we can update that once llvm/llvm-project#214793 lands

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address in a follow up patch. I realise we should not unconditionally do Cpp::Process("#pragma cling optimize " << optLevel;")) because that only runs in the clang-repl case, so that would be an unrelated change to what this patch does.

The equivalent of that code would be to pass the -O2 flags to the interpreter. No pragma is needed because it is done on global scope probably intends to make a TU-wide setting.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay makes sense. So we pass -02 by default when creating the interpreter

@aaronj0
aaronj0 force-pushed the explicit-load-dladdr branch from 7f3655e to 423f302 Compare August 19, 2026 08:30
@aaronj0

aaronj0 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator Author

@vgvassilev Addressed your request to separate the fix from the NFC refactor. I would prefer to keep them in the same PR though, having just the refactor into smaller static functions does not seem worth bloating the history.

Commit 1: Replace _applicationStarter with a thread-safe static LoadCppInterOp invoked from Python at setup, that sovles the nested dlopen
Commit 2: NFC: Refactor the body into subfunctions that improve readability and localise each substep of setting up the interpreter

@aaronj0
aaronj0 force-pushed the explicit-load-dladdr branch from 423f302 to 121235f Compare August 19, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants