[nanvix] E: Build stdlib extensions as .so#732
Conversation
10289fb to
44512fc
Compare
Keep third-party lxml outside CPython, remove its SDK dependency graph and payload staging, and retain Setup.local generation for the shared stdlib modules introduced by #732. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23312a7e-62f5-477a-a83f-65fd99522fe3
2b881a1 to
125d9e4
Compare
|
Reworked onto the SDK-compatible #690 branch at Local validation: |
Keep third-party lxml outside CPython, remove its SDK dependency graph and payload staging, and retain Setup.local generation for the shared stdlib modules introduced by #732. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23312a7e-62f5-477a-a83f-65fd99522fe3
|
Windows follow-up: the first dispatched run exposed a CP1252 encoding failure in the new smoke-test source. That source is now ASCII-safe at |
f448a3b to
9ed60e4
Compare
Generate one Setup.local for native and Docker builds, link migrated extensions against the exported Nanvix SDK PIE without duplicating libc state, and retain static C-API anchors required by the current loader. Strip installed modules and exercise real nested-import and dlopen paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23312a7e-62f5-477a-a83f-65fd99522fe3
Emit the gb2312 fixture through Unicode escapes so Windows runners using CP1252 can write test_hello.py without changing the guest assertion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23312a7e-62f5-477a-a83f-65fd99522fe3
9def47b to
96fc2a7
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the Nanvix CPython build to generate and install many stdlib C extensions as runtime-loaded shared objects (.so) via a unified Modules/Setup.local generator, and adds smoke-test coverage to validate that these modules load via dlopen() rather than being built-in.
Changes:
- Add
.nanvix/setup_local.pyas a single source of truth to renderModules/Setup.localwith*shared*entries for many stdlib extensions. - Switch both native-host and Docker build paths to use the shared
Setup.localrenderer. - Adjust
Makefile.nanvixand.nanvix/_test.pyto support/validate the shared-extension flow (including extra smoke checks and stripping installed.sofiles).
Show a summary per file
| File | Description |
|---|---|
Makefile.nanvix |
Updates shared-link settings and propagates COMPILER_RT_BUILTINS; strips installed .so extensions. |
.nanvix/setup_local.py |
New renderer and module table driving Modules/Setup.local for both host and Docker builds. |
.nanvix/lxml.py |
Uses the new render_setup_local() helper for host-side Setup.local generation. |
.nanvix/_docker.py |
Uses the new render_setup_local() helper and quotes lines for container-side Setup.local generation. |
.nanvix/_test.py |
Expands smoke tests to import and sanity-check shared extensions and nested-import anchors. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 5/5 changed files
- Comments generated: 3
- Review effort level: Low
| TARGET_AR := $(SDK_BIN)/llvm-ar | ||
| TARGET_RANLIB := $(SDK_BIN)/llvm-ranlib | ||
| TARGET_STRIP := $(SDK_BIN)/llvm-strip | ||
| COMPILER_RT_BUILTINS := $(shell $(TARGET_CC) --print-libgcc-file-name) |
| ("_bisect", "m.bisect_left([1, 3, 5], 4) == 2"), | ||
| ("_heapq", "m.heappush([], 1) is None"), | ||
| ("_struct", "m.pack('i', 42) == b'\\x2a\\x00\\x00\\x00'"), | ||
| ("_random", "hasattr(m, 'Random')"), |
| "import xml.etree.ElementTree as _elementtree_api\n" | ||
| "assert _elementtree_api.fromstring('<root/>').tag == 'root'\n" | ||
| "assert '_elementtree' not in sys.builtin_module_names\n" | ||
| "assert 'pyexpat' in sys.builtin_module_names\n" | ||
| "import encodings.gb2312\n" | ||
| "assert '\\u4e2d\\u6587'.encode('gb2312') == b'\\xd6\\xd0\\xce\\xc4'\n" | ||
| "assert '_codecs_cn' not in sys.builtin_module_names\n" | ||
| "assert '_multibytecodec' in sys.builtin_module_names\n" |
Summary
Builds 39 CPython stdlib extension modules as runtime-loaded
.sofiles under
lib/python3.12/lib-dynload/<name>.cpython-312.soinsteadof statically linking them into
python.elf. Continues the workstarted by
feat/phase0-array-so(the
arrayproof of concept), generalising the*shared*Setup.localflow across the CPython stdlib.Base branch:
feat/phase0-array-so— this PR is filed against
feat/phase0-array-soso it should bemerged after #690 lands.
Why
Before this change, every CPython extension that Nanvix builds was
forced into
*static*inModules/Setup.local, so the entire stdlibextension surface was linked into a monolithic
python.elf.lib-dynload/was empty. Side effects:python.elfcarried every extension's code even when a workloaduses only a fraction of it.
dlopen()at runtime because the CPython build was not actually producing
extension
.sofiles. This blocked any out-of-tree extensionstory.
libraries (
_decimal/libmpdec,pyexpat/libexpat, the SHA-2hash module /
libHacl_Hash_SHA2) paid the bundling cost as partof the monolith.
This PR enables the dlopen flow for the stdlib by making 39 modules
*shared*— matching upstream CPython's default./configurebehavior on Linux.
What changed
39 stdlib modules migrated from
*static*to*shared*:_bisect,_heapq,_struct,_random,_opcode,_queue,_csv,binascii,_json,_pickle,_zoneinfo.python.elf:math,cmath,_statistics,mmap,_contextvars.unicodedata,_multibytecodec,_codecs_cn,_codecs_hk,_codecs_iso2022,_codecs_jp,_codecs_kr,_codecs_tw._asyncio,_datetime,select,_socket,_posixsubprocess,fcntl,termios..sobundles its own.a:_decimal,pyexpat,_elementtree,_md5,_sha1,_sha2,_sha3,_blake2.Architecture
For modules with bundled-in-CPython C deps (
_decimal,pyexpat,_sha2), each.sobundles its own copy of the vendored.a—exactly the behavior of CPython's default
./configurerun on Linuxwithout
--with-system-libmpdec/--with-system-expat:.a_decimal.cpython-312.solibmpdec.aMODULE__DECIMAL_LDFLAGS=$(LIBM) $(LIBMPDEC_A)automatically (see B-1 fix below)pyexpat.cpython-312.solibexpat.aMODULE_PYEXPAT_LDFLAGS=$(LIBM) $(LIBEXPAT_A)automatically (see B-1 fix below)_sha2.cpython-312.solibHacl_Hash_SHA2.a_sha2line inSetup.localreferences the.aexplicitly, matching upstreamModules/Setup.stdlib.in_elementtree.cpython-312.sopyexpatviaPyExpat_CAPIcapsule)_elementtreeimportspyexpatand pulls aPyCapsuleof function pointers, so libexpat lives inpyexpat.soonlympdec,expat, andlibHacl_Hash_SHA2are stateless C APIs withat most one consumer each. Duplicating their code into
python.elfwould buy nothing — none of them maintain process-global state that
needs to be shared across multiple loaded extensions.
--with-libm=is cleared so libm symbols stay inpython.elf—math/cmathresolve them via the existing--whole-archiveoflibm.ainLIBNVX_CRT0at dlopen time.Commits
This PR contains two commits:
1.
[nanvix] E: Build stdlib extensions as .soThe main change. See file table below.
2.
configure: use $(LIBM) instead of literal -lm in LIBMPDEC/LIBEXPAT LDFLAGSSmall follow-up fix to CPython's
configure.ac. The bundled-librarycode paths at
configure.ac:3834and:3918hardcoded a literal-lminLIBEXPAT_LDFLAGSandLIBMPDEC_LDFLAGS. On Linux this isa no-op because
LIBMdefaults to-lm, but on Nanvix we pass--with-libm=empty (so libm.a stays whole-archived intopython.elfonly). The literal-lmdefeated that and bundled a~400 KB copy of
libm.ainto both_decimal.soandpyexpat.so.Switching the literal to
$(LIBM)is equivalent on Linux andcorrectly drops the duplicate libm when
LIBMis empty. Bothconfigure.acand the generatedconfigureare patched in lockstepso no
autoreconfis required.Mechanics
.nanvix/setup_local.py(new)SETUP_LOCAL_ENTRIESdata table +render_setup_local()— single source of truth forModules/Setup.localbody, consumed by both the host (.nanvix/lxml.py) and Docker (.nanvix/docker.py) build paths..nanvix/lxml.pygenerate_setup_local()renders viasetup_local.render_setup_local()..nanvix/docker.py_generate_setup_local_cmd()renders via the same helper and single-quotes each line for aprintf '%s\n' ... > Setup.localinvocation inside the container..nanvix/test.py_SO_MODULE_SANITY_CHECKStable +_render_so_sanity_snippets()generator emit smoke-test snippets that exercise every migrated module viaimport+ a trivial method call, and assert each module is no longer insys.builtin_module_names.Makefile.nanvix--with-libm=(empty) so libm symbols stay inpython.elf. No other configure-time or post-configure changes are needed; CPython's defaultMODULE__DECIMAL_LDFLAGS/MODULE_PYEXPAT_LDFLAGShandle the bundling automatically.configure.ac/configure-lmwith$(LIBM)inLIBEXPAT_LDFLAGSandLIBMPDEC_LDFLAGS.Dependencies
Base branch:
feat/phase0-array-so(the
arrayPhase 0 proof of concept).Runtime dependencies (already merged upstream):
libm visibility fix. Required so
math.so/cmath.socanresolve libm symbols against
python.elf .dynsymat dlopen time.dlfcninit-array + DT_RUNPATH support. Required sodlopen()runs initialisers for the new
.somodules and the loader canfind dependencies.
Validation
./z lint(black + pyright) clean.pre-commit runclean on all changed files (end-of-file-fixer,trailing-whitespace, check-case-conflict, check-merge-conflict).