Actually Portable Executables27 Feb 2021
I came across Cosmopolitan on Hacker News, and I was initially confused, due to a few memories of cross-compilation nightmares: while it should be possible to compile for the same architecture regardless of operating system, wouldn’t the OS get confused by the leading bytes of the executable? I read the article explaining how it works, but most of it went over my head.
The example on the Github README used the following script for compilation:
gcc -g -O -static -nostdlib -nostdinc -fno-pie -no-pie -mno-red-zone \ -o hello.com.dbg hello.c -fuse-ld=bfd -Wl,-T,ape.lds \ -include cosmopolitan.h crt.o ape.o cosmopolitan.a objcopy -S -O binary hello.com.dbg hello.com
I converted it into a simple Makefile to run the compilation commands. I tried a bunch of simple C programs (basic arithmetic, reading and writing to files) on Linux+Windows (compiled on Linux), and all of them worked.
Compiling Lua with Cosmopolitan
I decided to try compiling a high-level language built on C. I originally picked Python, but the Makefile for Python seemed too complicated to mess with, so I then picked Lua, which looked much simpler in comparison.
I started out by blindly copy-pasting the flags and includes used in the sample compilation on Github. Ah, it would have been wonderful for my laziness if it compiled out of the box. Following is a play-by-play commentary of trying to compile Lua.
First problem I ran into was header clashes: if I didn’t put
-nostdlib -nostdinc while compiling each object file,
-include cosmopolitan.h would clash with the system headers. But blocking the system headers meant I would have to change every
#include of a system header. I created a bunch of dummy headers with the same names as those in the C stdlib and and included to those instead.
Naming clashes: some of the macros in
cosmopolitan.h clashed with macro/function names in Lua:
isempty. I changed the Lua source to avoid this.
FIRST_RESERVED was broken because
UCHAR_MAX was missing. I thought
UCHAR_MAX was supposed to be in
limits.h – the
limits.h part of
cosmopolitan.h did not have
UCHAR_MAX (It had
SCHAR_MAX, though.) I added in a
__UINT8_MAX__ (ie 255).
The default Lua Makefile attempts to use
ldo.c when on Linux. I disabled the
LUA_USE_LINUX flag for compiling the object files, but this caused an issue with
mkstemp is available in Cosmopolitan). I changed the Lua source to use
longjmp. A similar issue showed in
sys/wait.h (which is a no-op in non-POSIX systems, as per the Lua source code), and in
sys/types.h so disabled
LUA_USE_POSIX over there as well.
localeconv() function (part of
locale.h) was not implemented in
cosmopolitan.h, and this caused an error while compiling
lua_getlocaledecpoint() depended on
localeconv()). Changed the macro to just return
panic function in Lua
static int panic (lua_state*) clashed with that in Cosmopolitan
void panic(void). Renamed the lua function to
lua_panic. This triggered an error where the
panic function was being called in
luaL_newstate, so I changed the name there as well.
luaL_loadfilex caused a frame size error – I have never seen this before. A quick internet search shows that this is because a large buffer is allocated on stack when entering the function, and yes,
luaL_loadfilex allocates a
loadF object containing a
char buffer of
BUFSIZ. I reduced the size of the buffer to
BUFSIZ - 64.
loslib.c requires the
locale.h, which is defined as an extern value in
cosmopolitan.h, but that definition is somehow not enough.. screw it, I just disabled
loslib.c, and then it compiles.
Linking the object files
Ok, time for linking …
gcc -std=gnu99 -o lua lua.o liblua.a -lm -Wl,-E -ldl /usr/bin/ld: errno: TLS definition in //lib/x86_64-linux-gnu/libc.so.6 section .tbss mismatches non-TLS reference in liblua.a(lauxlib.o) /usr/bin/ld: //lib/x86_64-linux-gnu/libc.so.6: error adding symbols: bad value collect2: error: ld returned 1 exit status
I forgot, I shouldn’t
-ldl. Ok, let’s try with all the object files instead of
/usr/bin/ld.bfd: lvm.o: in function `l_strcmp': lvm.c:(.text+0x59): undefined reference to `strcoll' /usr/bin/ld.bfd: lmathlib.o: in function `math_tanh': lmathlib.c:(.text+0x21f): undefined reference to `tanh' /usr/bin/ld.bfd: lmathlib.o: in function `math_sinh': lmathlib.c:(.text+0x24f): undefined reference to `sinh' /usr/bin/ld.bfd: lmathlib.o: in function `math_cosh': lmathlib.c:(.text+0x27f): undefined reference to `cosh' collect2: error: ld returned 1 exit status
Umm… okay, it looks like some of the functions defined in the cosmopolitan header are yet to be implemented in the static library. That’s okay, I can just quickly fill in the math functions, and I’ll comment out
strcoll for now, just because I want to see it compile…. and it successfully compiles!! Let’s run
objcopy before trying it out on a system though.
$ objcopy -S -O binary lua lua.exe $ ls -al -rwxr-xr-x 1 1953720 Feb 27 01:33 lua -rwxr-xr-x 1 344064 Feb 27 01:39 lua.exe
That size reduction seems a little too drastic, but let’s see if it runs on Linux:
Awesome. How about Windows?
Summary: it is actually portable
This is pretty incredible: I just had to modify a few lines in a Makefile and some C source files, and I got a Lua executable that works both on Linux and Windows (and possibly others as well). Granted, there are still some details to be filled out (floating point calculation above prints a
g), but Cosmopolitan is currently at release 0.2, so there is a lot of time. (Update: Lua is vendored as part of the Cosmopolitan repo since March 8 2021, and is used for dynamic pages.)
Hopefully this means that other languages that have source code completely in C can also be compiled once and run anywhere. Actually Portable Python next, maybe?