[nvx] E: Add nvx-crt0 startup crate (skeleton)#24
Open
esaurez wants to merge 1 commit into
Open
Conversation
Introduces a new `nvx-crt0` static library that owns the executable
entry point (`_do_start`), the `_start` Rust function it dispatches to,
the C / Rust trampolines that bridge to the application's `main`
function, and the `argc` / `argv` / `environ` parsing logic.
This PR is **purely additive**: the crate is created, registered in the
workspace, and built into the sysroot as `libnvx_crt0.a`, but no
consumer has been migrated to use it yet. All existing behaviour --
`libposix.a`'s embedded startup, every Rust no_std executable's link
graph, every test, the cpython link line -- is unchanged.
The split lets a follow-up PR cut `libposix.a` over to depend only on
the runtime helpers in `nvx`, removing the strong undefined `main`
reference that currently forces a workaround (`rust-objcopy
--weaken-symbol=main libposix.a`) in the build harness. The companion
loader-side fix that resolves weak undefined symbols to zero at dlopen
time landed in `[syscall] E: Honour STB_WEAK undefined symbols`.
What this PR contains:
- `src/libs/nvx-crt0/Cargo.toml` -- new package, `crate-type = [lib,
staticlib]`, dependencies mirror `nvx` plus a direct `nvx`
dependency for the shared `init` / `cleanup` / `pie::relocate_pie_binary`
helpers. Features `c-main` (default) and `rust-main` select which
trampoline gets compiled; a `compile_error!` guard rejects builds
with neither or both set.
- `src/libs/nvx-crt0/src/lib.rs` (~340 LOC) -- the startup code,
factored out of `src/libs/nvx/src/lib.rs` without behavioural
change. The `global_asm!` block defining `_do_start`, the `_start`
Rust function, both trampolines, the `ARGC` / `ARGV` / `environ`
globals, and `build_string_table` / `parse_argp` / `parse_envp`
helpers all move here. Doc comments expanded to call out the
new feature-set contract and the kernel-trap-frame ABI.
- `src/libs/nvx/src/lib.rs` -- minimal touch: `pub mod pie;`
(was `mod pie;`) and `pub fn init() / pub fn cleanup()` (were
private) so the new crate can call them. No symbols moved out of
`nvx` in this PR -- that happens in the follow-up cut-over PR.
- `Cargo.toml` (workspace) -- register `src/libs/nvx-crt0` as a
member and expose the `nvx-crt0` workspace dependency with
`default-features = false`.
- `Makefile` -- add `nvx-crt0` to `ALL_GUEST_STATIC_LIBS` so the
sysroot ships `libnvx_crt0.a` for downstream C consumers.
- `build/make/generic-guest-staticlibs.mk` -- introduce per-package
feature overrides (`GUEST_STATICLIB_FEATURES_<pkg>`) and an
artifact-name helper (`lib<pkg-with-_>.a`) so the existing batched
build path keeps working: `posix` continues to use `staticlib
[+ standalone]`, while `nvx-crt0` is built with `c-main` for the
sysroot copy. Rust no_std binaries that want the `rust-main`
flavour will pull `nvx-crt0` directly via cargo with the matching
feature toggle in a future PR.
Validation:
- `./z build` of the full `all` target succeeds.
- `i686-nanvix-nm libnvx_crt0.a` reports `T _do_start`, `T _start`,
`T c_trampoline`, `B environ`, `U main` (the intended UND that an
executable's `int main(...)` resolves at link time).
- `i686-nanvix-nm libposix.a` is byte-identical to the pre-PR build
(no consumer cut-over yet).
- CPython + numpy end-to-end on the Nanvix microvm still produces
`NUMPY_TEST_OK` -- behaviour unchanged.
- `cargo fmt --check`, `cargo clippy -- -D warnings`, and the
pipeline spellcheck all pass.
Follow-up PRs:
1. Cut `libposix.a` over: stop enabling `nvx/staticlib`, add the
`libnvx_crt0.a` link directive to the cpython Makefile.
2. Cut every Rust no_std executable over: add `nvx-crt0` as a cargo
dependency, drop `extern crate nvx;` in favour of `extern crate
nvx_crt0;`, and remove the startup symbols from `nvx` itself.
Also remove the temporary `POST_STATICLIB_HOOK_posix` objcopy
workaround in `generic-guest-staticlibs.mk`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4d9805b to
5af10eb
Compare
This was referenced May 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces a new
nvx-crt0static library that owns the executable entry point (_do_start), the_startRust function it dispatches to, the C / Rust trampolines that bridge to the application'smainfunction, and theargc/argv/environparsing logic.This PR is purely additive: the crate is created, registered in the workspace, and built into the sysroot as
libnvx_crt0.a, but no consumer has been migrated to use it yet. All existing behaviour is unchanged.Why
Today the
nvxruntime crate exposes startup symbols (_do_start,_start,c_trampoline, etc.) behind itsstaticlibfeature, andlibposixenables that feature via its ownstaticlib = ["nvx/staticlib"]wiring. As a result,libposix.aembeds a strong undefined reference tomain(via thec_trampoline'sextern "C" fn main(int, char **)declaration). That UND is what makes extension.sofiles fail to link againstlibposix.a--.sos never definemain. The current workaround is a build-harness post-process (rust-objcopy --weaken-symbol=main libposix.a) so the UND becomes weak and the System V ABI's "weak undef -> 0" rule (implemented in the dlfcn loader by #22) can absorb it at dlopen time.The proper fix is to stop carrying startup symbols in
libposix.aat all. Splitting them into a dedicatednvx-crt0crate is step one: executables explicitly linklibnvx_crt0.a, libraries (notablylibposix) link only the runtime helpers innvx.This PR delivers only the new crate. The follow-up PRs cut existing consumers over.
What this PR contains
src/libs/nvx-crt0/Cargo.toml(new)crate-type = [lib, staticlib]. Direct dependency onnvxfor the sharedinit/cleanup/pie::relocate_pie_binaryhelpers. Featuresc-main(default) andrust-mainselect the trampoline; acompile_error!guard rejects builds with neither or both set.src/libs/nvx-crt0/src/lib.rs(new, ~340 LOC)nvx'slib.rswithout behavioural change:global_asm!defining_do_start,_start, both trampolines,ARGC/ARGV/environglobals,build_string_table/parse_argp/parse_envphelpers. Doc comments expanded to call out the feature-set contract and the kernel-trap-frame ABI.src/libs/nvx/src/lib.rspub mod pie;(wasmod pie;) andpub fn init() / pub fn cleanup()(were private) so the new crate can call them. No symbols are moved out ofnvxin this PR -- that happens in the follow-up cut-over PR.Cargo.toml(workspace)src/libs/nvx-crt0as a member; exposenvx-crt0 = { path = "src/libs/nvx-crt0", default-features = false }.Cargo.lockMakefilenvx-crt0toALL_GUEST_STATIC_LIBSso the sysroot shipslibnvx_crt0.afor downstream C consumers (e.g.nanvix/cpython).build/make/generic-guest-staticlibs.mkposix: (1) per-package feature overrides (GUEST_STATICLIB_FEATURES_<pkg>->GUEST_STATICLIB_PKG_FEATURESlookup) soposixkeeps itsstaticlib [+ standalone]features whilenvx-crt0builds withc-main; (2) an artifact-name helper (lib<pkg-with-_>.a) so the cargo crate namenvx-crt0maps correctly to the producedlibnvx_crt0.aforcp/rmpaths.Why "modified Option A" (and not the alternatives)
Three approaches were considered:
nvx-crt0is a thin crate that owns the startup glue and reusesnvx's runtime helpers (heap setup, TDA setup, PIE relocation, panic handler, allocator). One source of truth per concern;nvx-crt0depends onnvx.nvx-crt0fully self-contained -- duplicateinit/cleanup/pieinto the new crate, so the crate has nonvxdependency. Avoids one dependency edge but doesn't actually eliminate duplicate-symbol risk becausenvx-crt0andlibposix.astill share other deps (sys,sysalloc,config, ...). Pure churn.nvxmore aggressively intonvx-rt+nvx-crt0, then haveposixdepend only onnvx-rt. Cleanest end-state but a bigger refactor than what's needed to remove themainUND, and the boundary betweennvxand a hypotheticalnvx-rtisn't obviously well-defined today.Option A delivers the architectural separation the upstreaming chain needs without changing what
nvxdoes for its existing consumers.Validation
./z buildof thealltarget succeeds:[OK] Build complete.i686-nanvix-nm libnvx_crt0.areports:T _do_start(kernel-entry stub)T _start(Rust entry, dispatches to trampoline)T c_trampoline(mangled)B environU main(the intended undefined reference that an executable'sint main(...)resolves at link time)i686-nanvix-nm libposix.ais byte-identical to the pre-PR build -- no consumer cut-over yet.NUMPY_TEST_OK../z build -- format-check rust-lint-check spellcheckall green; pre-commit hook passes.cargo build -p nvx-crt0 --features c-mainworks in isolation, and the batchedall-guest-staticlibstarget produces bothlibposix.a(withstaticlib standalone) andlibnvx_crt0.a(withc-main) without feature unification issues.Compatibility
nvx.init/cleanupwere private; making thempubis an extension. Themod pie;->pub mod pie;switch exposes the existing publicrelocate_pie_binaryfunction under the conventional path.libposix,cpython, downstream.sos -- all continue to build exactly as before.libnvx_crt0.ais produced and installed but nothing links it yet.Follow-up PRs
libposix.aover: stop enablingnvx/staticlib, add thelibnvx_crt0.alink directive to the cpythonMakefile.nanvix.nvx-crt0as a cargo dependency on the ~14 affected binaries, dropextern crate nvx;in favour ofextern crate nvx_crt0;, remove the startup symbols fromnvxitself, and delete the temporaryPOST_STATICLIB_HOOK_posixobjcopy workaround currently sitting in our local build harness.