Skip to content

Fix SWIG 4.4 multi-phase init: replace import_array() with import_array1(-1)#4846

Closed
kyamagu wants to merge 2 commits into
facebookresearch:mainfrom
faiss-wheels:fix/swig44-import-array
Closed

Fix SWIG 4.4 multi-phase init: replace import_array() with import_array1(-1)#4846
kyamagu wants to merge 2 commits into
facebookresearch:mainfrom
faiss-wheels:fix/swig44-import-array

Conversation

@kyamagu
Copy link
Copy Markdown
Contributor

@kyamagu kyamagu commented Feb 27, 2026

Problem

Building faiss with SWIG 4.4.0+ fails to compile the generated wrapper:

In function 'int SWIG_mod_exec(PyObject*)':
error: cannot convert 'std::nullptr_t' to 'int' in return
  import_array();

SWIG 4.4.0 introduced PEP 489 multi-phase module initialization, where SWIG_mod_exec returns int instead of PyObject*. NumPy's import_array() macro expands to return NULL, which is a type mismatch.

Fixes #4845.

Why not just use import_array1(-1)?

import_array1(-1) expands to return -1. This fixes SWIG 4.4+ but breaks older SWIG versions, which generate PyInit_() returning PyObject* — and the upstream CI (SWIG 4.0.2) confirmed exactly this failure:

In function 'PyObject* PyInit__swigfaiss()':
error: invalid conversion from 'int' to 'PyObject*'
  import_array1(-1);

Fix

Isolate the NumPy import into a helper function that always returns int, then return the correct error value from %init using a SWIG_VERSION preprocessor check:

+%{
+static int _faiss_init_numpy() {
+    import_array1(-1);
+    return 0;
+}
+%}
+
 %init %{
-    import_array();
+    if (_faiss_init_numpy() < 0) {
+#if SWIG_VERSION >= 0x040400
+        return -1;
+#else
+        return NULL;
+#endif
+    }
     PythonInterruptCallback::reset();
 %}
  • _faiss_init_numpy() always returns int, so import_array1(-1) inside it compiles cleanly under any SWIG version
  • The %init block then returns the correct type for its enclosing function: -1 (int) for SWIG 4.4+ multi-phase init, NULL (PyObject*) for older single-phase init

SWIG 4.4+ generates SWIG_mod_exec() returning int (PEP 489 multi-phase
init), while older SWIG generates PyInit_() returning PyObject*. NumPy's
import_array() expands to 'return NULL' and import_array1(-1) expands to
'return -1', each incompatible with one of the two return types.

Fix by isolating the NumPy import into a helper returning int, then
returning the appropriate error value from %init based on SWIG_VERSION.

Fixes: #4845
@kyamagu kyamagu force-pushed the fix/swig44-import-array branch from 2c00f54 to 5c3c0bf Compare February 27, 2026 07:13
@meta-codesync
Copy link
Copy Markdown
Contributor

meta-codesync Bot commented Mar 2, 2026

@mnorris11 has imported this pull request. If you are a Meta employee, you can view this in D94921302.

@mnorris11
Copy link
Copy Markdown
Contributor

mnorris11 commented Mar 3, 2026

Hi @kyamagu thanks for the report and contribution, my repro is WIP.

If the import_array1 fails and returns -1, we do not get to PythonInterruptCallback::reset(); regardless of SWIG version, is that the right understanding? Is that intended? I think now the behavior is to call PythonInterruptCallback::reset(); regardless?

@kyamagu
Copy link
Copy Markdown
Contributor Author

kyamagu commented Mar 3, 2026

@mnorris11 import_array() is a macro that returns on failure, and the reset was never called in that case. https://numpy.org/doc/2.0/reference/c-api/array.html#c.import_array

@mnorris11
Copy link
Copy Markdown
Contributor

@mnorris11 import_array() is a macro that returns on failure, and the reset was never called in that case. https://numpy.org/doc/2.0/reference/c-api/array.html#c.import_array

Got it thanks!

I was not able to repro in https://github.com/facebookresearch/faiss/actions/runs/22603509873/job/65492234884?pr=4853 (all I did was change swig version to 4.4 in the yml, tests still pass) but the change itself seems fine. Is this something you saw with pip installs?

@kyamagu
Copy link
Copy Markdown
Contributor Author

kyamagu commented Mar 3, 2026

@mnorris11 This was causing the error when building a wheel package on Linux arm64 arch + cpython 3.10 + numpy >= 2.0 + swig 4.4.1: https://github.com/faiss-wheels/faiss-wheels/actions/runs/22444196417/job/64994127319

I'm not sure what is the difference there, but one possibility is the numpy version?

@meta-codesync meta-codesync Bot closed this in 28f79bd Mar 3, 2026
@meta-codesync
Copy link
Copy Markdown
Contributor

meta-codesync Bot commented Mar 3, 2026

@mnorris11 merged this pull request in 28f79bd.

@kyamagu
Copy link
Copy Markdown
Contributor Author

kyamagu commented Mar 3, 2026

This error is likely specific to musllinux environment.

On manylinux aarch64:

  [213/216] Building CXX object python/CMakeFiles/swigfaiss.dir/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx.o
  /tmp/tmppohji16s/build/python/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx: In function ‘int SWIG_mod_exec(PyObject*)’:
  /tmp/tmppohji16s/build/python/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx:284406:3: warning: converting to non-pointer type ‘int’ from NULL [-Wconversion-null]
  284406 |   import_array();
         |   ^~~~~~~~~~~~

On musllinux aarch64:

  [210/216] Building CXX object python/CMakeFiles/swigfaiss.dir/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx.o
  FAILED: [code=1] python/CMakeFiles/swigfaiss.dir/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx.o 
  /usr/bin/g++ -DPy_LIMITED_API=0x03090000 -Dswigfaiss_EXPORTS -I/project/third-party/faiss -isystem /tmp/build-env-uw_j0f32/lib/python3.10/site-packages/numpy/_core/include -isystem /opt/python/cp310-cp310/include/python3.10 -O3 -DNDEBUG -std=c++17 -fPIC -fdata-sections -ffunction-sections -fopenmp -MD -MT python/CMakeFiles/swigfaiss.dir/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx.o -MF python/CMakeFiles/swigfaiss.dir/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx.o.d -o python/CMakeFiles/swigfaiss.dir/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx.o -c /tmp/tmpgkdazt1b/build/python/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx
  In file included from /usr/include/c++/14.2.0/clocale:42,
                   from /usr/include/c++/14.2.0/aarch64-alpine-linux-musl/bits/c++locale.h:41,
                   from /usr/include/c++/14.2.0/bits/localefwd.h:40,
                   from /usr/include/c++/14.2.0/string:45,
                   from /usr/include/c++/14.2.0/stdexcept:39,
                   from /tmp/tmpgkdazt1b/build/python/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx:4047:
  /tmp/tmpgkdazt1b/build/python/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx: In function 'int SWIG_mod_exec(PyObject*)':
  /tmp/tmpgkdazt1b/build/python/CMakeFiles/swigfaiss.dir/swigfaissPYTHON_wrap.cxx:284406:3: error: cannot convert 'std::nullptr_t' to 'int' in return
  284406 |   import_array();
         |   ^~~~~~~~~~~~

dimitraseferiadi pushed a commit to dimitraseferiadi/SuCo that referenced this pull request Mar 8, 2026
…ay1(-1) (facebookresearch#4846)

Summary:
## Problem

Building faiss with SWIG 4.4.0+ fails to compile the generated wrapper:

```
In function 'int SWIG_mod_exec(PyObject*)':
error: cannot convert 'std::nullptr_t' to 'int' in return
  import_array();
```

SWIG 4.4.0 introduced PEP 489 multi-phase module initialization, where `SWIG_mod_exec` returns `int` instead of `PyObject*`. NumPy's `import_array()` macro expands to `return NULL`, which is a type mismatch.

Fixes facebookresearch#4845.

## Why not just use `import_array1(-1)`?

`import_array1(-1)` expands to `return -1`. This fixes SWIG 4.4+ but breaks older SWIG versions, which generate `PyInit_()` returning `PyObject*` — and the upstream CI (SWIG 4.0.2) confirmed exactly this failure:

```
In function 'PyObject* PyInit__swigfaiss()':
error: invalid conversion from 'int' to 'PyObject*'
  import_array1(-1);
```

## Fix

Isolate the NumPy import into a helper function that always returns `int`, then return the correct error value from `%init` using a `SWIG_VERSION` preprocessor check:

```diff
+%{
+static int _faiss_init_numpy() {
+    import_array1(-1);
+    return 0;
+}
+%}
+
 %init %{
-    import_array();
+    if (_faiss_init_numpy() < 0) {
+#if SWIG_VERSION >= 0x040400
+        return -1;
+#else
+        return NULL;
+#endif
+    }
     PythonInterruptCallback::reset();
 %}
```

- `_faiss_init_numpy()` always returns `int`, so `import_array1(-1)` inside it compiles cleanly under any SWIG version
- The `%init` block then returns the correct type for its enclosing function: `-1` (int) for SWIG 4.4+ multi-phase init, `NULL` (PyObject*) for older single-phase init

Pull Request resolved: facebookresearch#4846

Reviewed By: junjieqi

Differential Revision: D94921302

Pulled By: mnorris11

fbshipit-source-id: 8d4fab61ffce76a10ee7b3f00bab87471b48e02a
dimitraseferiadi pushed a commit to dimitraseferiadi/SuCo that referenced this pull request Mar 16, 2026
…ay1(-1) (facebookresearch#4846)

Summary:
## Problem

Building faiss with SWIG 4.4.0+ fails to compile the generated wrapper:

```
In function 'int SWIG_mod_exec(PyObject*)':
error: cannot convert 'std::nullptr_t' to 'int' in return
  import_array();
```

SWIG 4.4.0 introduced PEP 489 multi-phase module initialization, where `SWIG_mod_exec` returns `int` instead of `PyObject*`. NumPy's `import_array()` macro expands to `return NULL`, which is a type mismatch.

Fixes facebookresearch#4845.

## Why not just use `import_array1(-1)`?

`import_array1(-1)` expands to `return -1`. This fixes SWIG 4.4+ but breaks older SWIG versions, which generate `PyInit_()` returning `PyObject*` — and the upstream CI (SWIG 4.0.2) confirmed exactly this failure:

```
In function 'PyObject* PyInit__swigfaiss()':
error: invalid conversion from 'int' to 'PyObject*'
  import_array1(-1);
```

## Fix

Isolate the NumPy import into a helper function that always returns `int`, then return the correct error value from `%init` using a `SWIG_VERSION` preprocessor check:

```diff
+%{
+static int _faiss_init_numpy() {
+    import_array1(-1);
+    return 0;
+}
+%}
+
 %init %{
-    import_array();
+    if (_faiss_init_numpy() < 0) {
+#if SWIG_VERSION >= 0x040400
+        return -1;
+#else
+        return NULL;
+#endif
+    }
     PythonInterruptCallback::reset();
 %}
```

- `_faiss_init_numpy()` always returns `int`, so `import_array1(-1)` inside it compiles cleanly under any SWIG version
- The `%init` block then returns the correct type for its enclosing function: `-1` (int) for SWIG 4.4+ multi-phase init, `NULL` (PyObject*) for older single-phase init

Pull Request resolved: facebookresearch#4846

Reviewed By: junjieqi

Differential Revision: D94921302

Pulled By: mnorris11

fbshipit-source-id: 8d4fab61ffce76a10ee7b3f00bab87471b48e02a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SWIG 4.4 multi-phase init breaks build: import_array() incompatible with int-returning SWIG_mod_exec

3 participants