pybind / pybind/pybind11

[BUG]: Pybind11 conflicts with TBB because of the `PYBIND11_DEBUG_MARKER` hack

Open
#5,136 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

triage
Dominant language
C++
Stars
18k
Forks
2.3k
Avg merge
5d 17h
Merged PRs (30d)
10

Description

Required prerequisites
What version (or hash if on master) of pybind11 are you using?

b07fddb21993d4fa358822dcbf2b36e0fd20db46

Problem description

Just tracked down a really cryptic bug. Pybind11's PYBIND11_DEBUG_MARKER hack (where it does #undef _DEBUG temporarily, and then defines it back) causes errors in TBB headers.

Here's what happens:

  • You set _DEBUG to 1 somehow.

  • Then you include any TBB header. They all include tbb/detail/_config.h that defines macro TBB_USE_DEBUG according to the value of _DEBUG:

    • If _DEBUG is undefined, #define TBB_USE_DEBUG 0
    • If _DEBUG is defined to nothing, #define TBB_USE_DEBUG 1
    • Otherwise (_DEBUG == 0 or 1), #define TBB_USE_DEBUG _DEBUG <- this branch is chosen
  • Then you #include <pybind11/pybind11.h>. That temporarily #undefines _DEBUG, and then defines it back, but the original value is lost, as it always defines it to empty, regardless of the original value.

  • Then you include some other TBB header that does #if TBB_USE_DEBUG. Now TBB_USE_DEBUG expands to empty, and #if with no expression is a compilation error:

    ......./oneapi/tbb/concurrent_vector.h:893:22: error: expected value in expression
    893 |     #if TBB_USE_DEBUG
    

In other words, TBB by itself can handle any value of _DEBUG: both empty and 0/1. What it can't handle is the value suddenly changing from 1 to empty.

Proposed solution:

Improve the PYBIND11_DEBUG_MARKER hack to correctly reproduce the original value of the macro.

Here's how you store the value:

#define __PYBIND11_CONCAT_AUX(A,B) A##B
#define __PYBIND11_IS_MACRO_EMPTY(A,IGNORED) __PYBIND11_CONCAT_AUX(__PYBIND11_MACRO_EMPTY,A)
#define __PYBIND11_MACRO_EMPTY 1

#ifdef _DEBUG
#  if __PYBIND11_IS_MACRO_EMPTY(_DEBUG,IGNORED)==__PYBIND11_MACRO_EMPTY
#    define PYBIND11_DEBUG_MARKER_EMPTY
#  elif _DEBUG
#    define PYBIND11_DEBUG_MARKER_1
#  else
#    define PYBIND11_DEBUG_MARKER_0
#  endif
#  undef _DEBUG
#endif

And this is how you load it back:

#if defined(PYBIND11_DEBUG_MARKER_EMPTY)
#  define _DEBUG
#elif defined(PYBIND11_DEBUG_MARKER_1)
#  define _DEBUG 1
#elif defined(PYBIND11_DEBUG_MARKER_0)
#  define _DEBUG 0
#endif

The macro emptiness check is grabbed straight from TBB.

Reproducible example code

Compile with MSVC (or Clang-cl), and ensure _DEBUG is defined to 1.

#include <tbb/parallel_for.h>
#include <pybind11/pybind11.h>
#include <tbb/enumerable_thread_specific.h>
Is this a regression? Put the last known working version here if it is.

Not a regression

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in pybind11/pybind11.h, where the PYBIND11_DEBUG_MARKER handling is used, and reproduce the reported include order with TBB while _DEBUG is defined as 1. Verify that the original _DEBUG value is preserved across the pybind11 include and that the TBB headers compile without the #if TBB_USE_DEBUG error.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.