Actually Portable Executables

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.c -fuse-ld=bfd -Wl,-T, \
  -include cosmopolitan.h crt.o ape.o cosmopolitan.a
objcopy -S -O binary

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: reverse and isempty. I changed the Lua source to avoid this.

A macro 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 #define stating UCHAR_MAX as __UINT8_MAX__ (ie 255).

The default Lua Makefile attempts to use _setjmp/_longjmp in ldo.c when on Linux. I disabled the LUA_USE_LINUX flag for compiling the object files, but this caused an issue with tmpnam in loslib.c (mkstemp is available in Cosmopolitan). I changed the Lua source to use setjmp/longjmp. A similar issue showed in lauxlib.c for sys/wait.h (which is a no-op in non-POSIX systems, as per the Lua source code), and in liolib for sys/types.h so disabled LUA_USE_POSIX over there as well.

The localeconv() function (part of locale.h) was not implemented in cosmopolitan.h, and this caused an error while compiling lobject.c (macro lua_getlocaledecpoint() depended on localeconv()). Changed the macro to just return '.'.

The 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 setlocale() and LC_* from locale.h, which is defined as an extern value in cosmopolitan.h, but that definition is somehow not enough.. screw it, I just disabled os_setlocale in 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/ section
.tbss mismatches non-TLS reference in liblua.a(lauxlib.o)
/usr/bin/ld: //lib/x86_64-linux-gnu/ error adding symbols: bad value
collect2: error: ld returned 1 exit status

I forgot, I shouldn’t -lm or -ldl. Ok, let’s try with all the object files instead of liblua.a:

/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?