Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
path = deps/cpython
url = https://github.com/nanvix/cpython.git
branch = nanvix/v3.12.3
shallow = true
shallow = true
[submodule "deps/cymem"]
path = deps/cymem
url = https://github.com/nanvix/cymem.git
branch = nanvix/v2.0.11
shallow = true
path = deps/cymem
url = https://github.com/nanvix/cymem.git
branch = nanvix/v2.0.11
shallow = true
[submodule "deps/numpy"]
path = deps/numpy
url = https://github.com/nanvix/numpy.git
branch = nanvix/v1.26.4
shallow = true
1 change: 1 addition & 0 deletions deps/numpy
Submodule numpy added at c128af
89 changes: 89 additions & 0 deletions patches/nanvix_numpy_bootstrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""
Lazy import redirector for NumPy built-in modules on Nanvix.
"""
import importlib
import importlib.abc
import importlib.machinery
import sys
import types

_ALIASES = {
"numpy.core._multiarray_umath": "_np_multiarray_umath",
"numpy._core._multiarray_umath": "_np_multiarray_umath",
"_multiarray_umath": "_np_multiarray_umath",
}

# C extension modules that are not statically linked but numpy tries
# to import them during init. Provide permissive stubs so init succeeds.
_STUBS = {
"numpy.core._multiarray_tests",
"numpy.core._simd",
"numpy.linalg._umath_linalg",
"numpy.linalg.lapack_lite",
"numpy.fft._pocketfft_internal",
"numpy.random._common",
"numpy.random._bounded_integers",
"numpy.random._generator",
"numpy.random._mt19937",
"numpy.random._pcg64",
"numpy.random._philox",
"numpy.random._sfc64",
"numpy.random.bit_generator",
"numpy.random.mtrand",
}

_loading = set()


class _StubModule(types.ModuleType):
__all__ = []
"""Module stub that returns None for any missing attribute."""
def __getattr__(self, name):
return None


class _NanvixLoader(importlib.abc.Loader):
def __init__(self, fullname):
self._fullname = fullname

def create_module(self, spec):
fullname = self._fullname

if fullname in _STUBS:
mod = _StubModule(fullname)
mod.__file__ = "<nanvix-stub>"
return mod

builtin_name = _ALIASES.get(fullname)
if not builtin_name:
return None

# Try sys.modules first (C-level pre-registration)
mod = sys.modules.get(builtin_name)
if mod is not None:
return mod

_loading.add(fullname)
try:
mod = importlib.import_module(builtin_name)
finally:
_loading.discard(fullname)
return mod

def exec_module(self, module):
fullname = self._fullname
sys.modules[fullname] = module
if fullname != "_multiarray_umath" and fullname in _ALIASES:
sys.modules["_multiarray_umath"] = module


class _NanvixNumpyFinder(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path, target=None):
if fullname in _STUBS:
return importlib.machinery.ModuleSpec(fullname, _NanvixLoader(fullname))
if fullname in _ALIASES and fullname not in _loading:
return importlib.machinery.ModuleSpec(fullname, _NanvixLoader(fullname))
return None


sys.meta_path.insert(0, _NanvixNumpyFinder())
8 changes: 8 additions & 0 deletions patches/numpy_libm_compat.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <math.h>

long double __kernel_tanl(long double x, long double y, int iy)
{
(void)y;
(void)iy;
return (long double)tan((double)x);
}
25 changes: 25 additions & 0 deletions patches/numpy_makefile_nanvix.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
diff --git a/Makefile.nanvix b/Makefile.nanvix
index 0f9703e9a2..66c67c2814 100644
--- a/Makefile.nanvix
+++ b/Makefile.nanvix
@@ -76,9 +76,9 @@ $(CROSS_FILE):
@echo '' >> $@
@echo '[built-in options]' >> $@
@echo "c_args = ['-I$(SYSROOT_PATH)/include', '-I$(SYSROOT_PATH)/include/python3.12']" >> $@
- @echo "c_link_args = ['-static', '-L$(SYSROOT_PATH)/lib', '-T$(SYSROOT_PATH)/lib/user.ld', '-Wl,--start-group', '$(SYSROOT_PATH)/lib/libposix.a', '$(TOOLCHAIN_PREFIX)/i686-nanvix/lib/libc.a', '$(TOOLCHAIN_PREFIX)/i686-nanvix/lib/libm.a', '-Wl,--end-group']" >> $@
+ @echo "c_link_args = ['-static', '-L$(SYSROOT_PATH)/lib', '-T$(SYSROOT_PATH)/lib/user.ld', '-Wl,--allow-multiple-definition', '-Wl,--start-group', '$(SYSROOT_PATH)/lib/libposix.a', '$(TOOLCHAIN_PREFIX)/i686-nanvix/lib/libc.a', '$(TOOLCHAIN_PREFIX)/i686-nanvix/lib/libm.a', '-Wl,--end-group']" >> $@
@echo "cpp_args = ['-I$(SYSROOT_PATH)/include', '-I$(SYSROOT_PATH)/include/python3.12']" >> $@
- @echo "cpp_link_args = ['-static', '-L$(SYSROOT_PATH)/lib', '-T$(SYSROOT_PATH)/lib/user.ld', '-Wl,--start-group', '$(SYSROOT_PATH)/lib/libposix.a', '$(TOOLCHAIN_PREFIX)/i686-nanvix/lib/libc.a', '$(TOOLCHAIN_PREFIX)/i686-nanvix/lib/libm.a', '-lstdc++', '-Wl,--end-group']" >> $@
+ @echo "cpp_link_args = ['-static', '-L$(SYSROOT_PATH)/lib', '-T$(SYSROOT_PATH)/lib/user.ld', '-Wl,--allow-multiple-definition', '-Wl,--start-group', '$(SYSROOT_PATH)/lib/libposix.a', '$(TOOLCHAIN_PREFIX)/i686-nanvix/lib/libc.a', '$(TOOLCHAIN_PREFIX)/i686-nanvix/lib/libm.a', '-lstdc++', '-Wl,--end-group']" >> $@
@echo '' >> $@
@echo '[host_machine]' >> $@
@echo "system = 'nanvix'" >> $@
@@ -95,7 +95,7 @@ configure: $(CONFIGURED_MARKER)

$(CONFIGURED_MARKER): $(CROSS_FILE)
PKG_CONFIG_LIBDIR="$(SYSROOT_PATH)/lib/pkgconfig" \
- /usr/bin/python3 vendored-meson/meson/meson.py setup $(BUILDDIR) \
+ python3 vendored-meson/meson/meson.py setup $(BUILDDIR) \
--cross-file $(CROSS_FILE) \
--pkg-config-path="$(SYSROOT_PATH)/lib/pkgconfig" \
-Dallow-noblas=true \
131 changes: 131 additions & 0 deletions patches/numpy_static_builtin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# NumPy Static Built-in Patch for Nanvix

This patch modifies NumPy 1.26.4 so that its core C extension
(`_multiarray_umath`) can be **statically linked into the CPython binary**
as a built-in module on the Nanvix microkernel.

Two files are touched:

| File | Purpose |
|------|---------|
| `numpy/core/src/common/npy_cpu_features.c` | CPU feature detection |
| `numpy/core/src/multiarray/multiarraymodule.c` | Module initialisation |

---

## 1. CPU feature detection (`npy_cpu_features.c`)

### Problem

NumPy probes the host CPU at import time with the privileged `CPUID` and
`XGETBV` instructions (inline assembly). On Nanvix these instructions
are not emulated in user mode and cause the process to **hang
indefinitely**.

### Fix

Guard both `npy__cpu_cpuid()` and `npy__cpu_getxcr0()` with
`#ifdef __nanvix__` so that on Nanvix they return zeroed-out results
instead of executing the instructions. NumPy then treats the CPU as
having no optional SIMD extensions, which is correct for the Nanvix
i686 target.

---

## 2. Module initialisation (`multiarraymodule.c`)

Three separate issues are addressed in this file.

### 2a. Import-lock deadlock during `npy_cache_import`

#### Problem

`initialize_static_globals()` calls
`npy_cache_import("numpy.exceptions", ...)` and
`npy_cache_import("numpy.core._exceptions", ...)`. These trigger
`import numpy`, which in turn imports `numpy.core._multiarray_umath` —
the very module whose `PyInit` is currently executing. Because CPython
holds a **per-module import lock** for the duration of `PyInit`, the
re-entrant import attempt deadlocks.

#### Fix

Wrap both `npy_cache_import` calls in `#ifndef __nanvix__`. The two
global pointers (`npy_DTypePromotionError`, `npy_UFuncNoLoopError`)
remain `NULL` after init; they are populated lazily by
`npy_cache_import`'s own `*cache == NULL` guard on first actual use.

An early-return guard (`if (npy_DTypePromotionError != NULL && …)`) is
also added so that a re-entrant call into `initialize_static_globals()`
is a harmless no-op instead of hitting the original `assert()` (which
would abort the process).

### 2b. Module renamed to `_np_multiarray_umath`

#### Problem

If the built-in is registered under the canonical name
`_multiarray_umath`, any Python code that does
`import _multiarray_umath` during `PyInit` will re-enter the same
`PyInit` function (import-lock deadlock, same as 2a but from a different
call site).

#### Fix

On Nanvix the module name in `PyModuleDef` is changed to
`_np_multiarray_umath` and the init function is renamed to
`PyInit__np_multiarray_umath`. A meta-path finder installed in
`sitecustomize.py` at runtime redirects all imports of the dotted name
(`numpy.core._multiarray_umath`) to the renamed flat built-in, breaking
the circular chain.

### 2c. Pre-registration in `sys.modules`

#### Problem

CPython only adds a built-in module to `sys.modules` **after** `PyInit`
returns. Any Python-level import triggered during `PyInit` (e.g.
`import datetime` via `PyDateTime_IMPORT`) that transitively tries to
resolve `numpy.core._multiarray_umath` will not find the module in
`sys.modules` yet, causing an `ImportError` or a second call to
`PyInit`.

#### Fix

Immediately after `PyModule_Create`, the partially-initialised module
object is inserted into `sys.modules` under three keys:

| Key | Why |
|-----|-----|
| `_np_multiarray_umath` | The built-in's own name |
| `_multiarray_umath` | The name NumPy Python code expects |
| `numpy.core._multiarray_umath` | The fully-qualified dotted path |

This lets the import machinery return the already-created (but not yet
fully populated) module object instead of calling `PyInit` again.

---

## Runtime companion: `nanvix_numpy_bootstrap.py`

The patch alone is not sufficient — a Python-side `sitecustomize.py`
module (`patches/nanvix_numpy_bootstrap.py`) is installed alongside
NumPy. It provides:

* A **`find_spec`-based meta-path finder** that intercepts
`import numpy.core._multiarray_umath` (and similar dotted paths) and
redirects them to the renamed `_np_multiarray_umath` built-in.
* **Permissive stub modules** (`_StubModule`) for C extensions that are
not statically linked (e.g. `_multiarray_tests`, `_simd`, linalg,
fft, random sub-modules). These stubs have `__all__ = []` and a
`__getattr__` that returns `None`, allowing NumPy's init to complete
without those extensions.

---

## Companion files

| File | Role |
|------|------|
| `patches/numpy_libm_compat.c` | Provides `__kernel_tanl` stub missing from Nanvix newlib |
| `patches/nanvix_numpy_bootstrap.py` | Runtime meta-path finder and stub modules |
Loading