Skip to content

[nanvix] E: Unbundle libffi via DT_NEEDED libffi.so#14

Open
esaurez wants to merge 1 commit into
feat/phase4-tier4-lxml-sharedfrom
feat/phase3b-unbundle-libffi-so
Open

[nanvix] E: Unbundle libffi via DT_NEEDED libffi.so#14
esaurez wants to merge 1 commit into
feat/phase4-tier4-lxml-sharedfrom
feat/phase3b-unbundle-libffi-so

Conversation

@esaurez

@esaurez esaurez commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Summary

Unbundle libffi from _ctypes.cpython-312.so. Switches the extension from statically bundling its own copy of libffi (744 KB) to dlopen'ing a shared libffi.so at runtime via DT_NEEDED. Companion to esaurez/libffi#1, which produces the libffi.so.

This is the first of the deferred Group B unbundlings tracked in nanvix-todo/phase2-3-unbundle-bundled-libs.md.

Architecture

_ctypes.cpython-312.so       102 KB stripped (was 744 KB bundled)
  └─> libffi.so              16 KB    (DT_NEEDED, ships once in /lib/)
        └─> python.elf      .dynsym   (libposix / libc / libm / abort etc.)

The Nanvix loader walks the chain at dlopen time (esaurez/nanvix#27). libffi UND symbols (abort, memcpy, etc.) bind against python.elf's .dynsym — same model as the lxml / libxml2 / libxslt chain shipped in esaurez/cpython#11.

Size impact

Artifact Before After
_ctypes.cpython-312.so (stripped) 744 KB 102 KB
libffi.so (none — bundled in _ctypes) 16 KB (shared, on disk)
python.elf 9.98 MB 9.98 MB (unchanged — -lffi was already dead-stripped)

Net ramfs save: ~626 KB. More importantly, future libffi consumers (e.g. a hypothetical Python C API user) can share the same .so instead of carrying their own copy.

Mechanics

File Change
.nanvix/config.py Drop -lffi from python.elf LIBS. Was eagerly pulling libffi.a into python.elf .dynsym. With libffi.so present in the sysroot, ld resolves -lffi to libffi.so (during the per-module _ctypes link) and emits a DT_NEEDED libffi.so entry instead of bundling.
.nanvix/z.py _DEP_EXPECTED_LIBS["libffi"] now expects both libffi.a and libffi.so (provided by esaurez/libffi#1).
.nanvix/test.py Stage libffi.so into the test ramfs sysroot at /lib/libffi.so. Hard-fail (FileNotFoundError) when missing, matching the lxml/libxml2 pattern already shipped in cpython#11.
.nanvix/package.py Same staging + hard-fail for the release tarball.
.nanvix/docker.py Setup.local comment block updated to distinguish Group A (bundled into python.elf via --whole-archive) from Phase 3b (libffi via DT_NEEDED .so). The _ctypes line is unchanged — LIBFFI_LIBS=-L$sr/lib -lffi was already in place from Phase 3; the only difference is what -lffi resolves to once libffi.so is present.

Validation

End-to-end test on nanvix-dev:

STEP_1:python_started (3, 12, 3)
STEP_2:_ctypes imported /lib/python3.12/lib-dynload/_ctypes.cpython-312.so
STEP_3:ctypes imported
STEP_4:created CFUNCTYPE prototype <class 'ctypes.CFUNCTYPE.<locals>.CFunctionType'>
STEP_5:Structure: x=3 y=4 sizeof=8
STEP_6:ffi_call via callback returned 42
CTYPES_CHAIN_PASS

STEP_6 exercises a real ffi_call round-trip: Python -> C trampoline -> libffi ffi_call -> Python callback -> result. Confirms the full machinery (CIF prep, closure allocation, calling-convention marshalling) works through the dlopen'd libffi.so.

readelf confirms the DT_NEEDED chain:

$ readelf -d _ctypes.cpython-312.so | grep NEEDED
 0x00000001 (NEEDED)  Shared library: [libffi.so]

$ nm -D _ctypes.cpython-312.so | grep -E 'T ffi_'
  (none — unbundled)

$ nm -D python.elf | grep -E 'T ffi_'
  (none — also unbundled from python.elf)

The lxml chain (shipped in cpython#11) is regression-tested and still passes (LXML_CHAIN_PASS).

Dependencies

Diamond fix (esaurez/nanvix#28) is not required for this PR — the libffi chain is linear (_ctypes.so → libffi.so → python.elf), no diamond.

Sequenced rollout

  1. Merge & release [build] E: Build libffi.so alongside libffi.a libffi#1 (so libffi.so ships in the buildroot).
  2. Bump cpython's nanvix.toml libffi pin to the new release.
  3. Merge cpython#11 (lxml Phase 4 — this PR stacks on it).
  4. Merge this PR.

Until step 2 the build will fail at the test/package staging step with FileNotFoundError: libffi.so missing from buildroot/lib — by design (hard-fail rather than silently shipping a broken _ctypes.so).

Switches the _ctypes.cpython-312.so extension from statically bundling libffi (744 KB per .so) to dlopen'ing libffi.so at runtime via DT_NEEDED. Saves ~700 KB by sharing libffi.so across all consumers; opens the door for future C-extension consumers of libffi to share the same .so.

Mechanics:

- Drop -lffi from python.elf LIBS (was eagerly pulling libffi.a into python.elf .dynsym). With libffi.so present in the sysroot, ld now resolves -lffi to libffi.so and emits a DT_NEEDED entry on _ctypes.cpython-312.so. libffi UND symbols (abort etc.) bind to python.elf .dynsym at dlopen time -- same model as the lxml/libxml2/libxslt .so chain.

- z.py _DEP_EXPECTED_LIBS: libffi now expects both libffi.a and libffi.so in the buildroot (esaurez/libffi#1).

- test.py / package.py: stage libffi.so into the ramfs sysroot at /lib/libffi.so. Hard-fail when missing, matching the lxml/libxml2 pattern.

- docker.py Setup.local comment block updated to distinguish Group A (bundled into python.elf) from Phase 3b (libffi via DT_NEEDED .so).

Validation: end-to-end _ctypes.so dlopen smoke test passes on nanvix-dev:

  STEP_1:python_started (3, 12, 3)

  STEP_2:_ctypes imported /lib/python3.12/lib-dynload/_ctypes.cpython-312.so

  STEP_6:ffi_call via callback returned 42

  CTYPES_CHAIN_PASS

_ctypes.cpython-312.so: 744 KB bundled -> 102 KB stripped shim; libffi.so 16 KB ships once in /lib/. python.elf size unchanged (was already gc-section-eliminating unreached libffi code).

Depends on: esaurez/libffi#1 (libffi.so build target + nested-archive fix), esaurez/nanvix#27 (DT_RUNPATH / .init_array walking).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant