Polyglink is a Rust rewrite of
apelink
from the Cosmopolitan project. It uses
Clap for its command-line interface and turns architecture-specific ELF
executables into one Actually Portable Executable (APE).
One source file. Three CPU builds. One final file. Copy that file to another supported operating system and run it directly—there is no archive to unpack, VM to start, or language runtime to install.
+--> x86-64 ELF --+
hello.c --+--> AArch64 ELF ---+--> hello-polyglink-riscv.com
+--> RISC-V ELF --+ +--> Linux / BSD / macOS / Windows
+--> including RISC-V 64 Linux
Polyglink requires a Rust toolchain with Rust 2024 edition support. Run these
commands from the Polyglink repository root; the demo also needs curl and
unzip for the one-time compiler download.
command -v cargo
command -v curl
command -v unzip
cargo build --release
export POLYGLINK="$PWD/target/release/polyglink"
"$POLYGLINK" --versionThe commands below assume a POSIX shell on Linux, macOS, or WSL. Building in
WSL and then copying the resulting .com file to native Windows is the least
surprising Windows workflow. The RISC-V extension uses a Debian or Ubuntu
x86-64 builder with a RISC-V cross compiler.
Polyglink performs the final link. The C compilers, libc, and APE loaders come
from the official Cosmopolitan toolchain. If it is already installed, skip the
download and set COSMOCC to its absolute directory.
export COSMOCC="$HOME/.local/opt/cosmocc"
mkdir -p "$COSMOCC"
curl --fail --location https://cosmo.zip/pub/cosmocc/cosmocc.zip --output "$COSMOCC/cosmocc.zip"
(cd "$COSMOCC" && unzip -q -o cosmocc.zip)
test -x "$COSMOCC/bin/x86_64-unknown-cosmo-cc"
test -x "$COSMOCC/bin/aarch64-unknown-cosmo-cc"
test -x "$COSMOCC/bin/apelink"
test -f "$COSMOCC/bin/ape-x86_64.elf"
test -f "$COSMOCC/bin/ape-aarch64.elf"
test -f "$COSMOCC/bin/ape-m1.c"This one-time download is large. The unversioned cosmocc.zip URL is the
stable installation URL documented in the
Cosmopolitan README.
The demo uses a temporary directory, so it does not dirty the repository and can be run again safely.
export DEMO="${TMPDIR:-/tmp}/polyglink-demo"
mkdir -p "$DEMO"
cd "$DEMO"
cat > hello.c <<'EOF'
#include <stdio.h>
int main(void) {
puts("Hello from one polyglot binary!");
return 0;
}
EOFUse the public *-unknown-cosmo-cc drivers. The similarly named
*-linux-cosmo-gcc programs are low-level implementation details and do not
configure the Cosmopolitan headers when called directly.
"$COSMOCC/bin/x86_64-unknown-cosmo-cc" -g -o hello-x86_64.elf hello.c
"$COSMOCC/bin/aarch64-unknown-cosmo-cc" -g -o hello-aarch64.elf hello.cAt this point there are two normal ELF executables containing the same program for different CPUs.
"$POLYGLINK" \
-G \
-l "$COSMOCC/bin/ape-x86_64.elf" \
-l "$COSMOCC/bin/ape-aarch64.elf" \
-M "$COSMOCC/bin/ape-m1.c" \
-o hello-polyglink.com \
hello-x86_64.elf hello-aarch64.elf
chmod +x hello-polyglink.com
./hello-polyglink.comThe output should be:
Hello from one polyglot binary!
-G makes the demonstration self-contained: the generated file uses its
embedded APE loaders instead of searching PATH for a system-wide ape
command. -M embeds the Apple Silicon loader source so macOS can compile it on
the first run.
This is a useful compatibility check. Only the linker executable and output name change.
"$COSMOCC/bin/apelink" \
-G \
-l "$COSMOCC/bin/ape-x86_64.elf" \
-l "$COSMOCC/bin/ape-aarch64.elf" \
-M "$COSMOCC/bin/ape-m1.c" \
-o hello-apelink.com \
hello-x86_64.elf hello-aarch64.elf
chmod +x hello-apelink.com
./hello-apelink.comIt prints the same line. Loader-compressed files are not required to be byte-for-byte identical because Polyglink uses a pure-Rust DEFLATE backend; their uncompressed contents and executable behavior are equivalent.
The stable cosmocc.zip currently supplies x86-64 and AArch64 tools. For the
third payload, use a Linux RISC-V cross compiler and the RISC-V APE
loader from the rustsbi/cosmopolitan
fork. The loader work is kept on its feat/riscv branch.
On Debian or Ubuntu, install the additional tools once:
sudo apt-get update
sudo apt-get install -y git make gcc-riscv64-linux-gnu libc6-dev-riscv64-cross
command -v git
command -v make
command -v riscv64-linux-gnu-gccFetch the loader source and build both the RISC-V-aware reference apelink
and the RISC-V loader:
export COSMOPOLITAN_RISCV="$HOME/.local/src/rustsbi-cosmopolitan"
mkdir -p "$(dirname "$COSMOPOLITAN_RISCV")"
test -d "$COSMOPOLITAN_RISCV/.git" || git clone --depth 1 --branch feat/riscv https://github.com/rustsbi/cosmopolitan.git "$COSMOPOLITAN_RISCV"
git -C "$COSMOPOLITAN_RISCV" switch feat/riscv
git -C "$COSMOPOLITAN_RISCV" pull --ff-only
make -C "$COSMOPOLITAN_RISCV" -j2 o//tool/build/apelink o/riscv64/ape/ape.elf
test -x "$COSMOPOLITAN_RISCV/o//tool/build/apelink"
test -x "$COSMOPOLITAN_RISCV/o/riscv64/ape/ape.elf"Compile the same hello.c as a static RISC-V Linux ELF. The explicit
0x400000 load address is required: userspace ELF segments below 2 MiB are
rejected by apelink and Polyglink.
riscv64-linux-gnu-gcc \
-static -no-pie -g \
-Wl,-Ttext-segment=0x400000 \
-o hello-riscv64.elf \
hello.cNow link all three CPU architectures with Polyglink:
"$POLYGLINK" \
-G \
-l "$COSMOCC/bin/ape-x86_64.elf" \
-l "$COSMOCC/bin/ape-aarch64.elf" \
-l "$COSMOPOLITAN_RISCV/o/riscv64/ape/ape.elf" \
-M "$COSMOCC/bin/ape-m1.c" \
-o hello-polyglink-riscv.com \
hello-x86_64.elf hello-aarch64.elf hello-riscv64.elf
chmod +x hello-polyglink-riscv.com
./hello-polyglink-riscv.comIt prints the same line on the x86-64 build machine. Copy that exact file to a RISC-V 64 Linux machine and run it again:
export RISCV_HOST="user@your-riscv64-linux-host"
scp hello-polyglink-riscv.com "$RISCV_HOST:/tmp/"
ssh "$RISCV_HOST" 'chmod +x /tmp/hello-polyglink-riscv.com && /tmp/hello-polyglink-riscv.com'Both executions should print:
Hello from one polyglot binary!
The RISC-V-aware reference apelink can link the exact same inputs. This is
the three-architecture compatibility check:
"$COSMOPOLITAN_RISCV/o//tool/build/apelink" \
-G \
-l "$COSMOCC/bin/ape-x86_64.elf" \
-l "$COSMOCC/bin/ape-aarch64.elf" \
-l "$COSMOPOLITAN_RISCV/o/riscv64/ape/ape.elf" \
-M "$COSMOCC/bin/ape-m1.c" \
-o hello-apelink-riscv.com \
hello-x86_64.elf hello-aarch64.elf hello-riscv64.elf
chmod +x hello-apelink-riscv.com
./hello-apelink-riscv.comCopy hello-polyglink-riscv.com unchanged to another supported machine. Do
not recompile it and do not extract it. If you skipped the RISC-V extension,
use hello-polyglink.com from step 4 instead.
| Platform | Run command |
|---|---|
| Linux x86-64 or AArch64 | chmod +x hello-polyglink-riscv.com then ./hello-polyglink-riscv.com |
| Linux RISC-V 64 | chmod +x hello-polyglink-riscv.com then ./hello-polyglink-riscv.com |
| FreeBSD, OpenBSD, NetBSD | chmod +x hello-polyglink-riscv.com then ./hello-polyglink-riscv.com |
| macOS on Intel | chmod +x hello-polyglink-riscv.com then ./hello-polyglink-riscv.com |
| macOS on Apple Silicon | chmod +x hello-polyglink-riscv.com then ./hello-polyglink-riscv.com |
| Windows x86-64, PowerShell | .\hello-polyglink-riscv.com |
| Windows x86-64, Command Prompt | hello-polyglink-riscv.com |
Apple Silicon needs the Xcode command-line compiler on the first run because
the example deliberately embeds ape-m1.c. Install it with
xcode-select --install if macOS asks for it.
Now inspect the same executable as a ZIP archive:
unzip -l hello-polyglink-riscv.comThe file is simultaneously a shell program, native executable container, and ZIP-compatible asset container. That is the polyglot: different operating systems read different, mutually compatible parts of the same byte sequence.
- Run the build commands in a POSIX shell. On native Windows, use WSL for the build and PowerShell or Command Prompt only for the final execution.
- If Linux has a broken
binfmt_miscor Wine association, runsh ./hello-polyglink-riscv.com. The-Gexample still uses the embedded loader. - Zsh should be version 5.9 or newer. Running the APE with
shis also a reliable fallback for older shells. - Do not strip the intermediate ELF files before linking. Polyglink uses their symbols and sections to construct the PE, Mach-O, and embedded symbol data.
- The stable Cosmopolitan download does not yet contain a RISC-V compiler or
loader. Use the
feat/riscvfork andriscv64-linux-gnu-gcccommands above;riscv64-unknown-cosmo-ccdoes not currently exist. - Keep the RISC-V payload static, non-PIE, and linked at
0x400000. Removing any of those options can produce an ELF that the linker rejects or the APE loader cannot start. - The RISC-V payload in this demonstration targets Linux. It does not claim native RISC-V support for BSD, macOS, or Windows.
- The target program and supported platforms ultimately depend on the
Cosmopolitan toolchain used to build the input ELF files. When diagnosing a
runtime issue, compare the Polyglink and
apelinkoutputs made from the exact same inputs.
polyglink -o OUTPUT [OPTIONS] INPUT...
polyglink --helpThe short options are compatible with apelink, including repeatable
-l PATH loader inputs, -k KERNEL associations, and repeatable -V BITS
support vectors.
Many thanks to Justine Tunney and all Cosmopolitan contributors for designing
the APE format and publishing apelink as open-source software. Polyglink's
file-format behavior and compatibility target would not exist without their
work.
Polyglink is released under the ISC License. See LICENSE.
Because this project is a rewrite derived from Cosmopolitan's ISC-licensed
apelink, the original Cosmopolitan and apelink.c copyright and license
notices are retained in THIRD_PARTY_NOTICES.md.