sunfishcode / eyra

Rust programs written entirely in Rust
Other
765 stars 12 forks source link

Document compatible target? #27

Closed polarathene closed 10 months ago

polarathene commented 11 months ago

UPDATE (7th Oct 2024):

libc + no_std: Unlike the static no_std example below, the revised variant instead avoids the libc crate calls - which removes the need for glibc/musl targets to include:

// Uses the internal rust libc (glibc/musl) for static builds instead? For this to work:
// - `Cargo.toml` should not have a `libc` dependency specified.
// - Do not build std via the `-Z build-std=std` option, which would replace the prebuilt private copy.
#![feature(rustc_private)]
extern crate libc;

Alternatively with cargo add libc --no-default-features instead you do not need those two lines in src/main.rs.

With libc crate in Cargo.toml, now -Z build-std will be compatible to build with (_redundant for no_std?_), but doing so requires additional RUSTFLAGS depending on glibc or musl target (_just like the above linked revised no_std example_):

NOTE: Related to these caveats for libc usage, if considering the optimizations detailed in the sunfishcode/origin tiny example README, the -N / --omagic link arg introduces a segfault with -musl target and the implicit -C link-self-contained=yes, but not when set to =no explicitly (or using -Z build-std which has roughly the same effect).


Original message

I am used to building with the *-musl target, but noticed that eyra is not compatible (default hello world example generated from cargo init).

--target:

I know the README clarifies the support constraints to linux, but doesn't highlight the -gnu vs -musl target compatibility? I didn't think it would matter with eyra?

Might be worthwhile to convey that incompatibility in the README too?


Reproduction

$ cargo init
$ cargo add eyra
# Prepend to `src/main.rs`:
$ echo 'extern crate eyra;' | cat - src/main.rs | sponge src/main.rs
# Build with `*-musl` target (fail):
$ RUSTFLAGS="-C link-arg=-nostartfiles -C relocation-model=static -C target-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-musl --release
# Build with `*-gnu` target (success):
$ RUSTFLAGS="-C link-arg=-nostartfiles -C relocation-model=static -C target-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release

Some error messages from -musl target attempt:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
   --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/c-scape-0.15.23/src/use_libc.rs:22:17
    |
22  |                 core::mem::transmute(crate::use_libc::Pad::new(core::ptr::read(src_ptr)));
    |                 ^^^^^^^^^^^^^^^^^^^^
    |
   ::: /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/c-scape-0.15.23/src/thread/mod.rs:653:44
    |
653 |     libc!(libc::pthread_rwlockattr_destroy(checked_cast!(_attr)));
    |                                            -------------------- in this macro invocation
    |
    = note: source type: `Pad<PthreadRwlockattrT>` (128 bits)
    = note: target type: `Pad<pthread_rwlockattr_t>` (96 bits)
    = note: this error originates in the macro `checked_cast` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
   --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/c-scape-0.15.23/src/time/mod.rs:104:33
    |
104 |     libc!(libc::gettimeofday(t, _tz));
    |           ------------------    ^^^ expected `*mut c_void`, found `*mut timezone`
    |           |
    |           arguments to this function are incorrect
    |
    = note: expected raw pointer `*mut c_void`
               found raw pointer `*mut timezone`
note: function defined here
   --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/libc-0.2.150/src/unix/linux_like/linux/musl/mod.rs:740:12
    |
740 |     pub fn gettimeofday(tp: *mut ::timeval, tz: *mut ::c_void) -> ::c_int;

Potentially related to?:

polarathene commented 11 months ago

Below is unrelated to issue addressed above (-musl target support with eyra).

Slightly off-topic, but might have some relevance. I noticed eyra made it possible for the -gnu target to successfully build statically linked that'd otherwise fail without eyra (that or I'm just too inexperienced to know better).

eyra helps enable no_std static builds for -gnu target

I have noticed that:

// Ref: https://lifthrasiir.github.io/rustlog/why-is-a-rust-executable-large.html
// Ref: https://darkcoding.net/software/a-very-small-rust-binary-indeed/
#![no_std]
#![no_main]

// cargo add libc --no-default-features
extern crate libc;

#[no_mangle]
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
    // since we are passing a C string, the final null character is mandatory:
    const HELLO: &'static str = "Hello, world!\n\0";
    unsafe { libc::printf(HELLO.as_ptr() as *const _); }
    0
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }

Will build for -gnu target dynamically fine (with glibc), but fail attempting a static build. Some changes with eyra fixes that (add extern + drop panic handler):

#![no_std]
#![no_main]

extern crate eyra;
extern crate libc;

#[no_mangle]
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
    // since we are passing a C string, the final null character is mandatory:
    const HELLO: &'static str = "Hello, world!\n\0";
    unsafe { libc::printf(HELLO.as_ptr() as *const _); }
    0
}

// Must remove panic handler, as eyra provides one
# ...

# Ref: https://github.com/johnthagen/min-sized-rust
[profile.release]
codegen-units = 1
lto = true
opt-level = "z"
panic = "abort"
strip = true
# Ensure `--target` is specified when using `RUSTFLAGS`:
# https://msfjarvis.dev/posts/building-static-rust-binaries-for-linux/
$ 14K dynamically linked glibc (no_std):
$ RUSTFLAGS="-Zlocation-detail=none -C relocation-model=static" cargo +nightly build --target x86_64-unknown-linux-gnu --release

# 414K static linked with eyra (despite no_std)
$ RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release

# 49K (with -Z build-std):
$ RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target x86_64-unknown-linux-gnu --release

I found that a bit interesting since the standard cargo init hello world example didn't have a problem (31K -gnu dynamic vs 945K -gnu static vs 37K eyra).

UPDATE: The above example will compile for -musl target if removing libc crate from Cargo.toml and giving the extern an attribute (compile error communicates this if not building with -Z build-std):

#![feature(rustc_private)]
extern crate libc;

Similar example (but compatible with -musl target) without libc crate

That no_std example above would fail to build on -musl targets too (Workaround provided in update above). Not sure if eyra could likewise fix that, might have something to do with the libc crate used? (possibly due to what is discussed here)

Here's a similar hello world no_std example without reliance on the libc crate. This builds fine with -musl targets, but fails with -gnu like the earlier example. Applying the same changes to build with eyra also enabled -gnu static builds to work šŸ‘

// Ref: https://www.reddit.com/r/rust/comments/bf8l2b/comment/elbzd5h/
#![no_std]
#![no_main]

#[no_mangle]
pub extern "C" fn main() -> isize {
    const HELLO: &'static str = "Hello, world!\n";
    unsafe { write(1, HELLO.as_ptr() as *const i8, HELLO.len()) };
    0
}

#[link(name = "c")]
extern "C" {
    fn write(fd: i32, buf: *const i8, count: usize) -> isize;
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }

With -Z build-std command shared earlier, this will output a 13.1K binary for both -musl and -gnu (with eyra), the eyra build being slightly smaller.


Not sure if the issues with -gnu static linking glibc were related to caveats like this:

Note the standard limitation of static glibc: If you do anything using NSS (notably domain name resolution), glibc will require matching system NSS libraries. glibc is working on improving that.

sunfishcode commented 11 months ago

Yes, Eyra works by implementing the existing C ABIs. Currently it's only compatible with "-gnu" ABIs. There's no fundamental reason "-musl" ABIs couldn't be supported, it just hasn't been implemented yet. It's an interesting question whether that's worth implementing; Eyra doesn't use any of the glibc or musl libraries itself, so the choice of target doesn't matter that much to Eyra.

And yes, thanks for pointing out that that's not documented in the README.md; I've now posted #28.

I have noticed that: [...] Will build for -gnu target dynamically fine (with glibc), but fail attempting a static build. Some changes with eyra fixes that (add extern + drop panic handler):

I needed to add #![feature(rustc_private)], but otherwise, that example build for me without Eyra, with RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release.

// Must remove panic handler, as eyra provides one

Eyra itself doesn't provide a panic handler; Eyra depends on libraries that depend on std, which provides a panic handler.

Eyra previously didn't support no_std applications. With https://github.com/sunfishcode/eyra/pull/29 I've now added support for no_std. In this mode, there is no std, so there's no panic handler, though keep in mind that there's also no global allocator or eh_personality provided either. I've added a no-std example to show how to make these work.

That said, this no-std mode doesn't currently support printf.

Not sure if the issues with -gnu static linking glibc were related to caveats like this:

Note the standard limitation of static glibc: If you do anything using NSS (notably domain name resolution), glibc will require matching system NSS libraries. glibc is working on improving that.

Eyra doesn't have this limitation. It can statically link without depending on system NSS libraries. It resolves NSS queries by invoking the getent command, so it respects the system NSS config without needing to link to the libraries itself.

polarathene commented 11 months ago

Thanks for the great response and PRs to address feedback! ā¤ļø

No pressure to read what follows and reply, it is mostly additional information to benefit myself and any future readers.


I needed to add #![feature(rustc_private)]

Ah ok, so nightly either way. I had found the same fix last night but for the -musl target to build that example, but forgot to submit the updated edit šŸ˜… Good to know it works for -gnu too!

that example built for me without Eyra, with RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release.

Might have been a typo on your end, but -C link-arg=-nostartfiles (without Eyra) builds a 4.4K binary that segfaults.

It will build a functional binary without that arg present though šŸ‘


eyra and panic handler (resolved: see next section)

Original response before realizing the newly added `no_std` support > Eyra itself doesn't provide a panic handler; Eyra depends on libraries that depend on std, which provides a panic handler. Ok, that was just an observation as I had to provide a panic handler when building without Eyra. I would get this error when adapting to build with Eyra: ``` error[E0152]: found duplicate lang item `panic_impl` --> src/main.rs:18:1 | 18 | fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the lang item is first defined in crate `std` (which `eyra` depends on) = note: first definition in `std` loaded from /home/polarathene/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-b9470aab25f77fbc.so, /home/polarathene/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-b9470aab25f77fbc.rlib = note: second definition in the local crate (`hello_world_no_std_libc_eyra`) ``` I suppose the std dep is why without `-Z build-std` it produces 414K static build? Whereas with the other `no_std` example (_that doesn't use `libc` calls_), that duplicate lang item error doesn't occur when building with Eyra if you try without `-Z build-std` (_using the `cargo add eyra --rename=std` approach_), however it outputs `496 bytes` binary that won't run (_Which is expected since `std` isn't meant to be included_). ```toml [dependencies] eyra = "0.16.4" # Not compatible: #std = { version = "0.16.4", package = "eyra" } # Min size: [profile.release] strip = true panic = "abort" lto = true opt-level = "z" codegen-units = 1 ``` ```rs #![no_std] #![no_main] extern crate eyra; #[no_mangle] pub extern "C" fn main() -> isize { const HELLO: &'static str = "Hello, world!\n"; unsafe { write(1, HELLO.as_ptr() as *const i8, HELLO.len()) }; 0 } #[link(name = "c")] extern "C" { fn write(fd: i32, buf: *const i8, count: usize) -> isize; } // Eyra depends on std which provides this during build: //#[panic_handler] //fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} } ``` ```console # 370K: $ RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release # 13K (with `-Z build-std`): $ RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target x86_64-unknown-linux-gnu --release ``` As mentioned earlier, the `-gnu` target does not successfully produce a static build of that example without Eyra. - The `rustc_private` feature attribute (_with added `extern crate libc;`_) will enable a `-gnu` static build (_without Eyra_), producing a 797K binary. - Compared to the `-musl` target that builds a 13.1K binary(_ regardless of `-Z build-std`, and doesn't need the `libc` workaround_).

eyra with no_std support

Eyra previously didn't support no_std applications.

  • With #29 I've now added support for no_std. In this mode, there is no std, so there's no panic handler, though keep in mind that there's also no global allocator or eh_personality provided either.
  • I've added a no-std example to show how to make these work.

Oh... I should really read the full response before typing the above šŸ˜†

The following doesn't appear needed if using the -Z build-std approach (which still produces same 13 304 bytes / 13K binary as before):

Snippets ```rs #![feature(lang_items)] #![feature(rustc_private)] extern crate libc; #[lang = "eh_personality"] extern "C" fn eh_personality() {} ``` Which leaves only the addition of the global allocator (_in addition to bringing back the panic handler_): ```rs #[global_allocator] static GLOBAL_ALLOCATOR: rustix_dlmalloc::GlobalDlmalloc = rustix_dlmalloc::GlobalDlmalloc; ``` ```console # 13K (with `-Z build-std`): $ RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target x86_64-unknown-linux-gnu --release ```

Global allocator

I guess compared to the prior example, the binary might differ by global allocator now? (that would have been provided from std?)

Eyra build errors when trying mimalloc ``` error: linking with `cc` failed: exit status: 1 ... = note: /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `_mi_fputs': static.c:(.text._mi_fputs+0x1d): undefined reference to `stdout' /usr/bin/ld: static.c:(.text._mi_fputs+0x29): undefined reference to `stderr' /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `mi_vfprintf.part.0': static.c:(.text.mi_vfprintf.part.0+0x48): undefined reference to `__vsnprintf_chk' /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `mi_printf_amount.constprop.0': static.c:(.text.mi_printf_amount.constprop.0+0xa5): undefined reference to `__snprintf_chk' /usr/bin/ld: static.c:(.text.mi_printf_amount.constprop.0+0x142): undefined reference to `__snprintf_chk' /usr/bin/ld: static.c:(.text.mi_printf_amount.constprop.0+0x172): undefined reference to `__snprintf_chk' /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `mi_vfprintf_thread.constprop.0': static.c:(.text.mi_vfprintf_thread.constprop.0+0x6b): undefined reference to `__snprintf_chk' /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `_mi_prim_numa_node_count': static.c:(.text._mi_prim_numa_node_count+0x4d): undefined reference to `__snprintf_chk' /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `_mi_prim_process_info': static.c:(.text._mi_prim_process_info+0x28): undefined reference to `getrusage' /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `_mi_prim_out_stderr': static.c:(.text._mi_prim_out_stderr+0x7): undefined reference to `stderr' /usr/bin/ld: static.c:(.text._mi_prim_out_stderr+0xf): undefined reference to `fputs' /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `mi_option_get': static.c:(.text.mi_option_get+0x193): undefined reference to `strtol' collect2: error: ld returned 1 exit status = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified = note: use the `-l` flag to specify native libraries to link ```

eh_personality vs panic = "abort"

I was under the impression that eh_personality was not necessary when panic = "abort" is configured (even without related -Z build-std args?), but I guess that's something Rust implicitly handled with no_std on -gnu / -musl targets when building without Eyra? (EDIT: Yep, as referenced from Rust unstable book on lang-items)

This resource notes that panic = "abort" in Cargo.toml should disable unwinding and thus not require eh_personality? I can see from cargo tree that rustix is using the unwinding crate which provides a no_std compatible pure rust alternative, so perhaps this opt-out behaviour belongs upstream there?

no_std example (without -Z build-std)

Just adding this as reference to earlier examples shared. Adapted from your no_std example.

[dependencies]
eyra = { version = "0.16.4", default-features = false }
rustix-dlmalloc = { version = "0.1.0", features = ["global"] }

[profile.release]
strip = true
panic = "abort"
lto = true
opt-level = "z"
codegen-units = 1
#![no_std]
#![no_main]
#![feature(lang_items)]
#![feature(rustc_private)]

extern crate libc;
extern crate eyra;

#[no_mangle]
pub extern "C" fn main() -> isize {
    const HELLO: &'static str = "Hello, world!\n";
    unsafe { write(1, HELLO.as_ptr() as *const i8, HELLO.len()) };
    0
}

#[link(name = "c")]
extern "C" {
    fn write(fd: i32, buf: *const i8, count: usize) -> isize;
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }

// Additionally required for building with Eyra:
#[global_allocator]
static GLOBAL_ALLOCATOR: rustix_dlmalloc::GlobalDlmalloc = rustix_dlmalloc::GlobalDlmalloc;

#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
$ RUSTFLAGS="-C link-arg=-nostartfiles -Z location-detail=none -C relocation-model=static -C target-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release

# 17K (17,400 bytes):
$ du -b target/x86_64-unknown-linux-gnu/release/hello_world_eyra_nostd

Very cool! šŸ˜Ž


Targets

There's no fundamental reason "-musl" ABIs couldn't be supported, it just hasn't been implemented yet. It's an interesting question whether that's worth implementing; Eyra doesn't use any of the glibc or musl libraries itself, so the choice of target doesn't matter that much to Eyra.

Not something I can comment much on, way beyond my expertise :) No point in implementing and maintaining support for a target if there is no other benefit. For users like myself just having that cleared up in the README is sufficient šŸ‘

It didn't require the libc extern, so no rustc_private feature needed either, although I don't know if that'd carry over with Eyra support.

I'm only aware of how musl differs with it's allocator performing poorly, and it having a history of issues with DNS / NSS and such. None of which would apply to a build with Eyra.


Eyra doesn't have this limitation. It can statically link without depending on system NSS libraries. It resolves NSS queries by invoking the getent command, so it respects the system NSS config without needing to link to the libraries itself.

Good to know, not having to be aware of such gotchas is another positive for building with Eyra šŸ’Ŗ

Whereas with the -musl target, users may not be aware of drawbacks that can be encountered like the default allocator (which they can change once they're aware of an actual perf issue introduced by the target).

sunfishcode commented 10 months ago

Thanks for the detailed report! The questions here seem answered, so I'll close this now, though feel free to reopen or file new issues if there's anything to add here.