Skip to content

[nanvix] E: Build array as .so (Phase 0 of .a -> .so migration) - #690

Merged
ppenna merged 1 commit into
nanvix/v3.12.3from
feat/phase0-array-so
Jul 16, 2026
Merged

[nanvix] E: Build array as .so (Phase 0 of .a -> .so migration)#690
ppenna merged 1 commit into
nanvix/v3.12.3from
feat/phase0-array-so

Conversation

@esaurez

@esaurez esaurez commented Jun 2, 2026

Copy link
Copy Markdown

Summary

First step of the .a.so migration plan documented in nanvix-todo/cpython-static-to-shared-migration.md. Converts array from a static built-in module into a shared extension loaded via dlopen at runtime, matching upstream CPython's layout for stdlib extension modules.

The diff is small (3 files, 32 / -2 lines) and is intentionally a proof-of-concept for one module so the round-trip (configure → setup.py build_ext → install → import via dlopen) can be validated end-to-end before the bulk Tier-1 conversion lands.

Changes

  • .nanvix/docker.py + .nanvix/lxml.pySetup.local generation:

    • Group the existing static modules under an explicit *static* directive (was an implicit # comment that makesetup tolerated).
    • Add a *shared* group with array arraymodule.c so CPython's setup.py build_ext produces array.cpython-312-i686-nanvix.so at lib/python3.12/lib-dynload/.
  • .nanvix/test.py — extend the test_hello.py smoke test to import array, assert it is NOT in sys.builtin_module_names, and exercise a tiny array operation. This proves the dlopen path end-to-end on every test run; if the .so fails to load (loader bug, missing PIE relocation, symbol-resolution issue, ...) the smoke test fails immediately rather than silently regressing.

Validation

./z build and ./z test:

  • array.cpython-312.so is produced at <sysroot>/lib/python3.12/lib-dynload/array.cpython-312.so.
  • Hello smoke prints CPYTHON_TEST_ARRAY_SO: array loaded via dlopen from /lib/python3.12/lib-dynload/array.cpython-312.so.
  • lxml + HTTP server smoke continue to PASS.
  • regrtest: 39 of 40 batches pass. The remaining failure is test_struct with 32 real subtest failures (not import errors); this is investigated in a follow-up as it appears unrelated to the .a.so migration itself.

Dependencies

This PR depends on the following prereq PRs landing first. Reviewers: please wait for these (and the matching ghcr.io/nanvix/toolchain-python:latest rebuild) before testing this one.

  1. nanvix/cpython#682--whole-archive + --export-dynamic in python.elf LIBS, so dlopen'd .sos can resolve C/C++ runtime symbols against the main executable.
  2. nanvix/cpython#683 — bake ninja and Cython into the toolchain image (needed by future meson-based extensions; harmless for array).
  3. nanvix/cpython#684 — link libnvx_crt0.a into python.elf (becomes unnecessary once [nvx] Cut over to nvx-crt0 startup crate nanvix#2453 lands, but harmless meanwhile).
  4. nanvix/cpython#687i686-nanvix-gcc wrapper that strips exe-only LDFLAGS for .so links. The .so link uses it.
  5. nanvix/nanvix#2450 — loader honours STB_WEAK undefined symbols. Without this, any .so with a weak UND fails to dlopen.
  6. nanvix/nanvix#2453libposix.a is the sole owner of sysalloc state ("Fix 2 v3"). Without this, the first dlopen collides with the heap.
  7. nanvix/nanvix#2458dlfcn keeps the leading / on absolute paths. Without this, dlopen() of array.cpython-312.so fails inside CPython's regrtest (which chdirs into a scratch dir).

Copilot AI review requested due to automatic review settings June 2, 2026 20:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR advances the Nanvix “static stdlib modules (.a in python.elf) → shared extensions (.so via dlopen)” migration by converting array into a shared extension module and updating the Nanvix build/test harness so the shared-module path is exercised during smoke testing. In the current stacked diff, it also includes supporting toolchain/build changes needed for reliable .so linking/loading (wrapper + link flags), aligning Nanvix’s layout more closely with upstream CPython’s lib-dynload model.

Changes:

  • Generate Modules/Setup.local with explicit *static* and *shared* groups, adding array arraymodule.c under *shared*.
  • Extend the Nanvix smoke test to import array, assert it’s not built-in, and run a small runtime check (verifying dlopen path).
  • Add/propagate toolchain/linker support for shared extensions (cc-wrapper in the toolchain Dockerfile and updated link inputs for python.elf symbol export/availability).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Makefile.nanvix Updates Nanvix link inputs and adds an existence guard for libnvx_crt0.a to support dlopen’d extensions resolving against python.elf.
.nanvix/test.py Extends the hello/smoke script to validate array loads as a shared extension and performs a minimal correctness check.
.nanvix/lxml.py Generates Modules/Setup.local with explicit *static*/*shared* groups and adds array as a shared module entry.
.nanvix/docker/Dockerfile Installs host build helpers (pip/ninja/Cython) and installs a gcc/g++ wrapper to strip exe-only flags for -shared links.
.nanvix/docker/cc-wrapper.sh New compiler-driver wrapper to distinguish exe vs shared links and filter toxic LDFLAGS for .so builds.
.nanvix/docker.py Updates Docker-side Setup.local generation to use *static* and add the *shared* array entry.
.nanvix/config.py Keeps the (currently unused) configure_env() helper in sync with updated linker inputs and adds libnvx_crt0 to toolchain paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Makefile.nanvix Outdated
Comment on lines +137 to +143
ifdef CONFIG_NANVIX
ifneq ($(filter clean distclean,$(MAKECMDGOALS)),$(MAKECMDGOALS))
ifeq ($(wildcard $(LIBNVX_CRT0)),)
$(error libnvx_crt0.a not found at $(LIBNVX_CRT0). Update the Nanvix sysroot to one that ships libnvx_crt0.a (the nvx-crt0 crate must be present and built into the sysroot lib/ directory).)
endif
endif
endif
Comment thread .nanvix/test.py Outdated
Comment on lines +451 to +455
# Phase 0 of the .a -> .so migration: `array` is now a shared
# extension at lib/python3.12/lib-dynload/array.cpython-312.so
# (built from `*shared* array arraymodule.c` in Setup.local).
# Asserting it is NOT in `sys.builtin_module_names` proves the
# dlopen path is exercised end-to-end; if the .so failed to load,
@ppenna

ppenna commented Jul 16, 2026

Copy link
Copy Markdown

Reworked onto current nanvix/v3.12.3 at 125d9e40033. The branch drops the retired GCC/Newlib wrapper and manual runtime-link prerequisites, uses the pinned Nanvix SDK static-PIE/dynamic-loader contract, stages the SDK runtime libraries, and verifies array is loaded from lib-dynload.

Local validation: ./z build, ./z test (all 160 configured modules plus hello/HTTP/lxml smoke tests), and ./z lint all pass.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 4/4 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment thread Makefile.nanvix Outdated
Comment on lines +142 to +146
install: build
$(MAKE) install DESTDIR="$(DESTDIR)"
mkdir -p "$(DESTDIR)$(INSTALL_PREFIX)/lib"
cp -f "$(NANVIX_SDK_ROOT)/lib/libc.so" "$(DESTDIR)$(INSTALL_PREFIX)/lib/libc.so"
cp -f "$(NANVIX_SDK_ROOT)/lib/libm.so" "$(DESTDIR)$(INSTALL_PREFIX)/lib/libm.so"
Comment thread .nanvix/lxml.py
_SETUP_LOCAL_TEMPLATE = """\
# Auto-generated by .nanvix/lxml.py -- do not edit manually.

# Statically-linked extension modules for Nanvix builds.
Comment thread .nanvix/_docker.py
f"printf '%s\\n' "
f"'# Auto-generated by .nanvix/docker.py -- do not edit manually.' "
f"'' "
f"'# Statically-linked extension modules for Nanvix builds.' "
Copilot AI review requested due to automatic review settings July 16, 2026 16:21
@ppenna
ppenna force-pushed the feat/phase0-array-so branch from 125d9e4 to f448a3b Compare July 16, 2026 16:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review details

  • Files reviewed: 4/4 changed files
  • Comments generated: 4
  • Review effort level: Low

Comment thread .nanvix/lxml.py
_SETUP_LOCAL_TEMPLATE = """\
# Auto-generated by .nanvix/lxml.py -- do not edit manually.

# Statically-linked extension modules for Nanvix builds.
Comment thread .nanvix/_docker.py
f"printf '%s\\n' "
f"'# Auto-generated by .nanvix/docker.py -- do not edit manually.' "
f"'' "
f"'# Statically-linked extension modules for Nanvix builds.' "
Comment thread Makefile.nanvix
Comment on lines +60 to +65
CFLAGS="-O3 -fPIE -fomit-frame-pointer -fno-unwind-tables -fno-asynchronous-unwind-tables" \
CFLAGS_NODIST="-fno-semantic-interposition" \
LDFLAGS="$(DEPENDENCY_LDFLAGS) -static -Wl,--export-dynamic" \
LDFLAGS="$(DEPENDENCY_LDFLAGS) -Wl,--export-dynamic" \
LINKFORSHARED="-Wl,-pie,--export-dynamic,--no-dynamic-linker,-z,notext,-z,norelro,--hash-style=sysv" \
LDSHARED="$(TARGET_CC) -shared -Wl,--no-as-needed,-z,now,-rpath,/lib,-Bdynamic -L$(NANVIX_SDK_ROOT)/lib -l:libc.so -l:libm.so -Wl,-Bstatic" \
LDCXXSHARED="$(TARGET_CXX) -shared -Wl,--no-as-needed,-z,now,-rpath,/lib,-Bdynamic -L$(NANVIX_SDK_ROOT)/lib -l:libc.so -l:libm.so -Wl,-Bstatic" \
Comment thread .nanvix/_test.py
Comment on lines +229 to +233
def stage(
args: build_mod.MakeArgs,
*,
require_array_so: bool = True,
) -> None:
Migrate the `array` stdlib module from a statically-linked builtin to a
shared extension loaded via dlopen (Phase 0 of the .a -> .so migration).
Use the SDK static-PIE and dynamic-loader contract instead of the retired
GCC wrapper and manual Newlib runtime composition. Shared extensions bind
to the SDK libc.so/libm.so through /lib, and the install flow stages those
runtime libraries.

Build (Makefile.nanvix):
- Compile with -fPIE and drop -static from LDFLAGS so the interpreter is a
  static-PIE that can dlopen extensions.
- Add LINKFORSHARED, LDSHARED, and LDCXXSHARED so shared modules link
  against the SDK libc.so/libm.so with runpath /lib.
- Install libc.so and libm.so into $(DESTDIR)/lib so the loader finds them
  regardless of INSTALL_PREFIX.
- Make the configure marker depend on Makefile.nanvix and clean before
  reconfiguring, so changed compiler/linker flags never relink stale,
  non-PIE objects.

Setup generation (.nanvix/lxml.py, .nanvix/_docker.py):
- Emit explicit *static* / *shared* sections in Modules/Setup.local and
  declare `array` under *shared*, placed before Setup.stdlib's static
  declaration so makesetup's first-rule-wins picks the shared build.
- Keep the native and Docker Setup.local generators in sync.

Docker (.nanvix/_docker.py):
- Include the effective make invocation and Makefile.nanvix contents in the
  persistent-volume input hash so switching release/test mode or changing
  build settings forces a clean rebuild.

Tests (.nanvix/_test.py):
- Extend the hello-world smoke test to import `array`, exercise a round
  trip, and assert it loaded via dlopen (CPYTHON_TEST_ARRAY / ARRAY_SO);
  run_hello now fails if those markers are absent.
- Gate the shared-module assertion behind require_array_so so downloaded
  older release artifacts remain testable via a .downloaded-release marker.
- Select the exact linux-x86 runtime release asset, cache it by immutable
  asset id with an atomic partial-file download, and record the release tag.
- Remove any prior ramfs image before rebuilding so it is not embedded into
  its replacement.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ppenna
ppenna force-pushed the feat/phase0-array-so branch from f448a3b to 9ed60e4 Compare July 16, 2026 16:51
@ppenna
ppenna merged commit 32d0393 into nanvix/v3.12.3 Jul 16, 2026
12 checks passed
@ppenna
ppenna deleted the feat/phase0-array-so branch July 16, 2026 17:37
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.

3 participants