Skip to content

Pin exported surface to intended API and shrink - #77

Merged
jserv merged 1 commit into
mainfrom
reduce-symbol
Aug 4, 2026
Merged

Pin exported surface to intended API and shrink#77
jserv merged 1 commit into
mainfrom
reduce-symbol

Conversation

@jserv

@jserv jserv commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

libX11-compat exported every non-static symbol (~1100), leaking ~400 internal helpers into the dynamic table. Restrict the exported set to the surface the project already enumerates, and shrink the library along the way.

Generate a linker export list (ELF version script / Mach-O -exported_symbols_list) from the union of {api,shim,private}-symbols.txt (the core->libXft private contract) and whitebox-symbols.txt, intersected with the symbols the build actually defines. The intersection keeps the list valid under strict linkers (lld and binutils 2.43 default to --no-undefined-version), so the manifests can stay a cross-platform superset. GLX/FORMAT identity is tracked by a single link-config stamp that regenerates the map and relinks on change.

Export the core-defined libX11 underscore ABI (_Xdebug, _XGetHostname, ...) by pattern: real libX11 exports those globals and legacy clients link them directly (violawww's libIMG references _Xdebug), which the X[A-Z]* manifests do not cover.

Shrink: -ffunction-sections/-fdata-sections plus --gc-sections / -dead_strip collect code no exported symbol reaches, notably the duplicated in-core Xft/Fc copy that only libXft-compat consumes. Install-time strip -x slims the deployed libraries a further ~6% while the dev build keeps local symbols for backtraces. A source pass marks 12 single-TU helpers static and deletes 2 dead functions.

Net exported surface: 727 (macOS) / 725 (Linux) = 682 public + 24 shim + 13 libXft-private + 7 underscore-ABI + 1 Motif-tier whitebox. Shipped library 588,720 to 524,192 bytes installed.


Summary by cubic

Pin libX11-compat exports to the intended API and dead-strip unused code to shrink the installed library (588,720 → 524,192 bytes). Keeps legacy _X* ABI available while hiding ~400 internal helpers.

  • Refactors

    • Generate export lists from tests/api-symbols.txt, tests/shim-symbols.txt, tests/private-symbols.txt, and tests/whitebox-symbols.txt, intersected with defined symbols; auto-regenerated on GLX/format changes; generator fails on missing/unreadable inputs.
    • Use ELF version script with --gc-sections on Linux and -exported_symbols_list with -dead_strip on macOS; keep _X* ABI exported by pattern; add tests/test-xlibint-link.c.
    • Add unpinned test twin libX11-compat-test.so for whitebox tests; shipped libX11-compat.so remains pinned.
    • Enable code GC with -ffunction-sections -fdata-sections; mark 12 helpers static and remove 2 dead functions.
    • Tighten libXft-compat link with -Wl,--no-undefined on Linux; gate off under -fsanitize builds.
  • Migration

    • Installed libs are stripped of local symbols via strip -x; on macOS they are re-signed after strip. Override STRIP=: (or a custom tool) if needed.

Written for commit 7851587. Summary will update on new commits.

Review in cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

jserv added a commit that referenced this pull request Aug 4, 2026
…macOS dylibs

Address the cubic review on PR #77.

gen-export-list.sh: a missing or unreadable manifest was masked because set -e
saw the successful trailing sort, so a broken input could emit a map that omits
the API and still reach the linker. Read the manifests in a checked step, and
drop the || true on the comm intersection: comm reads $defined, and already
returns 0 for an empty intersection, so the guard only swallowed real read
errors. A missing manifest or defined-syms now fails the build; a legitimate
empty intersection still succeeds.

install.mk: strip -x rewrites the Mach-O and invalidates the ad-hoc signature
ld64 puts on arm64 binaries. Apple's strip re-signs, but lld or an older
toolchain does not, and dyld then refuses the installed dylib. Re-sign with
codesign --force --sign - after strip on Darwin; CODESIGN_RESIGN is the no-op :
builtin elsewhere.

Two other review items are not defects. The underscore ABI is exported on
macOS: nm prints the Mach-O double underscore __Xdebug, the Darwin sed strips
one to _Xdebug which the ^_X match keeps, and the macho formatter re-adds it
(verified: all 7 _X* globals exported). The test twin's .so name matches this
tree's compat-lib convention; .dylib is only autoconf-built Motif.

Validated on macOS and Linux (docker ubuntu24, clang-18, GNU ld 2.42):
check-unit, symbol-coverage, generator error-propagation, and make install with
a codesign-valid stripped dylib.
@jserv

jserv commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in e9a1c5b.

Fixed:

  • gen-export-list.sh (both P1s): the manifests are now read in a checked step, so a missing or unreadable manifest fails the build instead of being masked by the trailing sort. The || true is removed from the comm intersection so a defined-syms read error propagates; comm already returns 0 for an empty intersection, so the legitimate empty case still succeeds. Confirmed: a missing manifest or defined-syms now exits non-zero, an empty intersection exits 0.
  • install.mk (P2): strip -x rewrites the Mach-O and invalidates the ad-hoc signature ld64 puts on arm64 binaries, so the install now re-signs with codesign --force --sign - after strip on Darwin (a no-op : elsewhere), rather than relying on the STRIP=: escape hatch. Verified the installed dylib passes codesign -v and dlopens.

Not defects, so left as-is:

  • library.mk:86 (underscore ABI on macOS): the ABI is exported. nm prints the Mach-O double underscore __Xdebug; the Darwin sed 's/^_//' strips one to _Xdebug, which the ^_X match keeps; the macho formatter re-adds the underscore. Verified all 7 _X* globals (_Xdebug, _XGetHostname, _XDefaultError, _XIDHandler, _XInitImageFuncPtrs, _XrmInternalStringToQuark, _XSeqSyncFunction) are exported on macOS.
  • library.mk:113 (.so vs .dylib twin): compat libraries in this tree use .so on both platforms (TARGET := $(OUT)/libX11-compat.so); .dylib is only autoconf-built Motif. The whitebox suite already runs on macOS and the .so-named Mach-O twin builds and loads there.

Validated on macOS and Linux (docker ubuntu 24.04, clang-18, GNU ld 2.42): check-unit, symbol-coverage, generator error-propagation, and make install producing a codesign-valid stripped dylib.

libX11-compat exported every non-static symbol (~1100), leaking ~400
internal helpers into the dynamic table. Restrict the exported set to
the surface the project already enumerates, and shrink the library along
the way.

Generate a linker export list (ELF version script / Mach-O
-exported_symbols_list) from the union of {api,shim,private}-symbols.txt
(the core->libXft private contract) and whitebox-symbols.txt, intersected
with the symbols the build actually defines. The intersection keeps the
list valid under strict linkers (lld and binutils 2.43 default to
--no-undefined-version), so the manifests can stay a cross-platform
superset. GLX/FORMAT identity is tracked by a single link-config stamp
that regenerates the map and relinks on change.

Export the core-defined libX11 underscore ABI (_Xdebug, _XGetHostname,
...) by pattern: real libX11 exports those globals and legacy clients
link them directly (violawww's libIMG references _Xdebug), which the
X[A-Z]* manifests do not cover.

Shrink: -ffunction-sections/-fdata-sections plus --gc-sections /
-dead_strip collect code no exported symbol reaches, notably the
duplicated in-core Xft/Fc copy that only libXft-compat consumes.
Install-time strip -x slims the deployed libraries a further ~6% while
the dev build keeps local symbols for backtraces. A source pass marks 12
single-TU helpers static and deletes 2 dead functions.

Net exported surface: 727 (macOS) / 725 (Linux) = 682 public + 24 shim +
13 libXft-private + 7 underscore-ABI + 1 Motif-tier whitebox. Shipped
library 588,720 to 524,192 bytes installed.
@jserv
jserv merged commit 93e0505 into main Aug 4, 2026
56 checks passed
@jserv
jserv deleted the reduce-symbol branch August 4, 2026 17:05
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