Home

Awesome

Cosmopolitan Libc and Rust

Note: the executables built using Cosmo and Rust do not work across the operating systems supported by Cosmopolitan Libc, because Rust decides system constants like EINVAL at compile-time (see https://github.com/ahgamut/rust-ape-example/issues/3), based on the operating system provided in the compilation target. Thus, I expect the fat binaries built here will only work on x86-64-linux and aarch64-linux. Very well, perhaps in the future we can find a way to have system constants as extern values in Rust, like how I did it for C.

This repository contains a simple Hello world! example in the Rust programming language, that builds with Cosmopolitan Libc. Now it also includes all the example snippets I could scrape from Rust By Example, and it builds around 175 example programs, including those that use Rust's std::thread and std::sync::Arc.

ripgrep builds with Cosmopolitan Libc -- check it out here.

To build this repo you need a recent version of the Cosmopolitan Libc monorepo, and bash because I wrote a simple filter script.

I created a custom compilation target for Rust, called x86_64-unknown-linux-cosmo, to provide a build process that uses the Cosmopolitan Libc amalgamation and cargo. I followed the documentation in the Rust Embedonomicon to create the target.

An alternative method to build APEs with Rust would be to avoid cargo, just use rustc or equivalent compiler to generate .o files, and then write a shell script that does the linking with the expected flags. I have not tried this method.

Building a Rust APE with the std crate

  1. Download the Cosmopolitan Libc repo and build the toolchain:
git clone https://github.com/jart/cosmopolitan
cd cosmopolitan
make -j MODE= toolchain
make -j MODE=aarch64 toolchain
export COSMO=$(realpath ./)
cd ..
mkdir cosmos
export COSMOS=$(realpath ./cosmos)
$COSMO/bin/cosmocc --update

Then clone this repo

git clone https://github.com/ahgamut/rust-ape-example
cd rust-ape-example
  1. Download the necessary host toolchain and source code for Rust:
# I was on Debian 11, so I did this
rustup toolchain install nightly-x86_64-unknown-linux-gnu
rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
# on Alpine Linux, you may need to do
rustup toolchain install nightly-x86_64-unknown-linux-musl
rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-musl

For reference, this worked when I tried it for nightly-x86_64-linux-gnu and:

  1. run cargo build to get the debug executables. This uses a bash script that removes unnecessary linker arguments.
export ARCH=x86_64
cargo +nightly build --target=./x86_64-unknown-linux-cosmo.json
export ARCH=aarch64
cargo +nightly build --target=./aarch64-unknown-linux-cosmo.json
  1. run apelink on the debug binaries to obtain the fat APE:
# look at the built debug binaries
ls ./target/x86_64-unknown-linux-cosmo/debug/*.com.dbg
ls ./target/aarch64-unknown-linux-cosmo/debug/*.com.dbg

# apelink
MODE=
MODE_AARCH64=aarch64
APELINK=$COSMO/o/tool/build/apelink.com
apelinkpls () {
    OUTPUT="$1"
    OUTPUT_X86_64="$2"
    OUTPUT_AARCH64="$3"
    "$APELINK" -l "$COSMO/o/$MODE/ape/ape.elf" \
        -l "$COSMO/o/$MODE_AARCH64/ape/ape.elf" \
        -M "$COSMO/ape/ape-m1.c" \
        -o "$OUTPUT" \
        "$OUTPUT_X86_64" \
        "$OUTPUT_AARCH64"
}

apelinkpls ./hello.com\
    ./target/x86_64-unknown-linux-cosmo/debug/hello.com.dbg\
    ./target/aarch64-unknown-linux-cosmo/debug/hello.com.dbg

# run the APE
./hello.com

Now we have Actually Portable Executables built with Rust! I also built a few more executables using the code from Rust By Example, and an APE that doesn't use the std crate. There might some edge cases that I haven't noticed, so clone/fork the repo and try it out!

TODOs

The std crate relies on backtrace, which depends on libunwind in the default builds for unix. To work around this, cosmopolitan.a currently has stubs for the functions that backtrace relies on. However, it might be easier to provide a build flag in Cargo.toml to use the noop module of backtrace.

A small change needs to be submitted to the source code of backtrace (in the cfg_if! here) to allow choosing noop when building as part of the std crate. This conditional compilation flag should be accessible when building the std crate either via Cargo.toml or something like -Z use-std-backtrace-noop in the build command.