Repository navigation
Non-externed Cython type definitions cause redefinition errors with transitive header includes #1663
Description
Summary
Several cuda.bindings Cython interfaces (cynvvm, cynvml, cynvjitlink, cynvfatbin) define types using Cython's quoted C name syntax without cdef extern from blocks. This causes C compilation errors when a user cimports these types in a module that also transitively includes the corresponding C header.
Problem
The externed interfaces (cynvrtc, cydriver, cyruntime) use cdef extern from "header.h" blocks, so Cython emits a #include directive and the header's include guards prevent duplicate definitions.
The non-externed interfaces instead define types inline:
# cynvvm.pxd (current)
ctypedef enum nvvmResult "nvvmResult":
NVVM_SUCCESS "NVVM_SUCCESS" = 0
NVVM_ERROR_OUT_OF_MEMORY "NVVM_ERROR_OUT_OF_MEMORY" = 1
...When Cython compiles a .pyx that cimports these types, it generates its own enum/typedef definitions in the C output. If any cdef extern from block in the same module (directly or via its .pxd) includes a header that transitively includes nvvm.h, the C compiler sees duplicate enum constant definitions and fails.
Minimal Reproducer
nvvm.h (or the real one from the CUDA Toolkit):
#ifndef NVVM_H
#define NVVM_H
typedef enum {
NVVM_SUCCESS = 0,
NVVM_ERROR_OUT_OF_MEMORY = 1,
NVVM_ERROR_PROGRAM_CREATION_FAILURE = 2,
NVVM_ERROR_INVALID_INPUT = 4,
NVVM_ERROR_INVALID_PROGRAM = 5,
} nvvmResult;
typedef void *nvvmProgram;
#endifexample.h — a user header that happens to transitively include nvvm.h:
#ifndef EXAMPLE_H
#define EXAMPLE_H
#include "nvvm.h"
int test_func(int a);
#endifexample.pxd — declares the user's C function:
cdef extern from "example.h":
int test_func(int a)example.pyx — cimports the nvvm types from cuda.bindings:
from cuda.bindings.cynvvm cimport nvvmResult
def call_test(int x):
return test_func(x)Build Output
example.c:1541:3: error: redeclaration of enumerator 'NVVM_SUCCESS'
1541 | NVVM_SUCCESS = 0,
| ^~~~~~~~~~~~
In file included from example.h:4,
from example.c:1138:
nvvm.h:5:5: note: previous definition of 'NVVM_SUCCESS' with type 'enum <anonymous>'
5 | NVVM_SUCCESS = 0,
| ^~~~~~~~~~~~
example.c:1542:3: error: redeclaration of enumerator 'NVVM_ERROR_OUT_OF_MEMORY'
...
example.c:1547:25: error: conflicting types for 'nvvmResult'; have 'enum nvvmResult'
The generated C file first emits #include "example.h" (which transitively includes nvvm.h), then later emits Cython's own enum nvvmResult { NVVM_SUCCESS = 0, ... } from the cimport. The duplicate enum constants are a hard C compilation error.
Impact
Any downstream Cython project that wants to:
- Use types from
cynvvm,cynvml,cynvjitlink, orcynvfatbinviacimport, and - Has any
cdef extern fromthat transitively includes the corresponding C header
...will fail to compile. The user doesn't need to redeclare any types themselves — the mere transitive include is sufficient.
This does not affect the externed interfaces (cynvrtc, cydriver, cyruntime) since they use cdef extern from, which emits #include directives that are deduplicated by include guards.
Affected Interfaces
| Interface | Style | Affected |
|---|---|---|
cynvvm.pxd |
Non-externed (quoted names) | Yes |
cynvml.pxd |
Non-externed (quoted names) | Yes |
cynvjitlink.pxd |
Non-externed (quoted names) | Yes |
cynvfatbin.pxd |
Non-externed (quoted names) | Yes |
cynvrtc.pxd |
cdef extern from "nvrtc.h" |
No |
cydriver.pxd |
cdef extern from "cuda.h" |
No |
cyruntime.pxd |
cdef extern from "driver_types.h" |
No |
Suggested Fix
Switch the non-externed interfaces to use cdef extern from blocks, consistent with cynvrtc.pxd, cydriver.pxd, and cyruntime.pxd. This causes Cython to emit #include directives instead of generating its own type definitions, allowing include guards to deduplicate.
Edit: On further analysis, this is not believed to be an ABI break. The .pxd file is purely a compile-time artifact — it is not referenced at runtime by already-compiled extensions. Since the quoted C names already produce the exact same C type identifiers and enum constant values as the header, the generated machine code is identical regardless of declaration style. Already-compiled downstream extensions would continue to work without recompilation against an updated cuda.bindings that makes this change.
Activity
mdboom commentedon Feb 20, 2026
If we add
cdef extern from, then the C header is required to be available at (cuda-bindings) build time, which isn't currently the case. Not to mention that the.pxdis likely a "megaheader" combination of multiple versions of the upstream C header -- which one would we include -- all of them?Maybe as an alternative, we can try to just add the header guard?
kkraus14 commentedon Feb 20, 2026
Adding the header guard would be ideal, but I didn't see a clean way of getting Cython to generate code to do that for someone arbitrarily cimporting a specific type or function from a given pxd file
mdboom commentedon Feb 20, 2026
I'd have to try it, but I was thinking along the lines of:
leofang commentedon Feb 20, 2026
I had a long conversation with Keith yesterday on this. My position is that this is a feature, not a bug (hence I removed the bug label). We need proper planning for this and not rush fixing anything, because the use case is at this point theoretical and it is likely that we would
cimportuser that megaheader's pros outweigh its consThis is really a big feature that we'll lose if we move away from megaheader. OTOH, it's less relevant for cuda-bindings because we version-lock to CUDA major.minor.
leofang commentedon Feb 20, 2026
FYI in the "Affected Interfaces" table
cycufileis missing. It is currently NOT using mega header.mdboom commentedon Jun 26, 2026
@kkraus14 asked elsewhere whether the move of the core nvrtc/runtime/driver modules to cybind means that they will now suffer from this bug.
Experimentally, no. These three modules have the
need_headers_at_buildflag set totrue, which means they generatecdef extern from "..."and everything works.Reproducer:
This bug is still relevant for modules where
need_headers_at_buildisfalse-- and it's not immediately obvious how we could resolve that without doing what it says on the tin (needing headers at build).