Actually Portable Executable (APE) with Zig

Zig provides a standard library which works on different platforms. I figure it may be possible to make “actually portable exectuable” (APE) with zig. Here is a short summary. It follows this example in rust.

First, you need to have apelink executable. You can download the pre-compiled cosmocc which includes apelink program (cosmocc.zip).
But for some reasons, I also need to have ape installed in WSL. Therefore I download the whole cosmopolitan source code and run

ape/apeinstall.sh

so that WSL can run APE.

Then, compile the exectuable to x86_64 and aarch64 targets:

$ zig build -Dtarget=x86_64-linux
$ cp ./zig-out/bin/hello_world hello_world.x86_64
$ zig build -Dtarget=aarch64-linux
$ cp ./zig-out/bin/hello_world hello_world.aarch64

Combine executables into one APE. {$cosmo} is where cosmocc.zip is decompressed.

{$cosmo}/bin/apelink -l {$cosmo}/bin/ape-x86_64.elf -l {$cosmo}/bin/ape-aarch64.elf -M {$cosmo}/bin/ape-m1.c -V 0b1110011 -o hello_world ./hello_world.x86_64 ./hello_world.aarch64

It generates a single hello_world executable which can run on x86_64 and aarch64 linux.

-V 0b1110011 selects which platforms to run. I tested it under x86_64 WSL and aarch64 WSL. Not sure whether it will work on different OS, say x86_64-freebsd.

4 Likes

Thanks for that write up, it goes in my saved posts!

And although I love this from a technical standpoint, practically I still fail to see what is the use case of APEs.

Some meandering rant Providing two different executable for two different arch is ok I think. What's much more important (to me at least) is that zig is strongly biased toward statically link executables. Delivering executable to systems with a lot of variability in libc is suddenly not a problem anymore. And you improve the preservation of your binary.

And what I would like to see is the problem solved for libX.

For example, factorio depends on 44 different dynamic libraries on linux. Will I be able to pull my factorio tarball in 15 years and play it on linux from the future? Nope.

But happy to be schooled on this issue!

To my taste, the cool factor is the most interesting part of the APE/CosmoCC project.

I think Justine jumped the gun on one thing: the “architectural consensus” around x86 and variants. On the contrary, I suspect that the moment of greatest dominance of that architecture is several years behind us. It’s noteworthy that the project was released just before the Mac switched to the M series of processors, and now Microsoft is going to do another push towards ARM. Then there’s RISC-V, who knows what the actual prospects are there but it’s in the running.

Still, if she can make a cross-OS universal binary for one architecture, I bet she can figure out how to make a universal fat binary as well. I believe there’s been some progress on that actually, although I’m not going to jump back down the Cosmo rabbit hole right this instant.

Zig tackles the problem in a more comprehensive and durable way imho, by emphasizing static compilation, and providing a world-class cross compiling experience. But still, the idea of being able to make one (1) binary, which can run on whatever OS and whatever chip you want, is pretty compelling. You can tuck that away in your backups and break it out whenever for whatever. I like it when software keeps working, y’know?

And where Zig is concerned, the language is still in flux, so it’s nice that @rguiscard worked out how to APE a Zig binary. Sometimes you dash off a project, it’s done, you just want to keep using it, and maybe you don’t want to maintain an entire frozen-in-time Zig build system for that project, or keep updating the project as Zig continues to change. So making one binary you can use in a lot of places buys some optionality for such a project.

1 Like