170 comments

[ 3.2 ms ] story [ 245 ms ] thread
I'm a huge fan of the ideas behind this library. Using the stable ABI of "magic numbers" is brilliant. Would love to see this solution gain more usage!
Sadly, the "magic numbers" are much less stable than you'd think.
That's like saying the '9' in kill -9 might change someday. However it really does depend on which numbers. In the APE blog post I mention the importance on focusing on the subset of numbers that systems share in common. In other words, the really old ones. https://github.com/jart/cosmopolitan/blob/master/libc/sysv/c...
Not quite–kill is part of POSIX, and platforms that expose a libc interface must keep signal numbers consistent for applications. Syscall numbers need not be stable if they are not exposed, as is true on pretty much every platform but Linux.
It appears to build a "naked" thompson shell script without a shebang line that works as a PE file for Windows/DOS, and uses `exec` to feed a binary to the shell via a pipe.

Interesting approach for a universal binary.

A downside of this approach is the binary can't be executed with the exec system call because the kernel doesn't recognize scripts without a shebang. It can only be executed from within a shell.
> A downside of this approach is the binary can't be executed with the exec system call because the kernel doesn't recognize scripts without a shebang. It can only be executed from within a shell.

It's a little bit more nuanced than that, depending on the platform. You don't necessarily need to have a shebang. This behaviour is guaranteed in Linux, and traditional on BSD, but not POSIX:

> If the header of a file isn't recognized (the attempted execve(2) failed with the error ENOEXEC), these functions will execute the shell (/bin/sh) with the path of the file as its first argument. (If this attempt fails, no further searching is done.)

Which means that it _should_ work outside a shell on those systems.

This quote is about execlp(), execvp() and execvpe() functions, which are supposed to emulate what shell does.

POSIX has similar wording:

> In the cases where the other members of the exec family of functions would fail and set errno to [ENOEXEC], the execlp() and execvp() functions shall execute a command interpreter and the environment of the executed command shall be as if the process invoked the sh utility using execl() as follows:

  execl(<shell path>, arg0, file, arg1, ..., (char *)0);
> where <shell path> is an unspecified pathname for the sh utility, file is the process image file, and for execvp(), where arg0, arg1, and so on correspond to the values passed to execvp() in argv[0], argv[1], and so on.

https://pubs.opengroup.org/onlinepubs/9699919799/functions/e...

Sorry for being naive, but how can it be possible that nobody had thought of this approach and this approach would have actually worked? What have been preventing people from coming up with something like this?
I think it's been pondered a bunch, but the legwork required to build the headers for each system so that you can execute the same binaries on every platform was tough.
I can think of two reasons: It's arguably a hack and it's not needed.

While this is awesome, it can easily break (zsh already seems to do so) and, as the author states, it is not meant for GUI apps or programs relying deeply on OS APIs, which includes a lot of apps.

Additionaly, it does not solve an important problem. Doing a build for each supported OS is not that hard and easily automated. The actual problem is to be able to reuse code and this is solved for the most part.

Author here. I intend to upstream a patch soon with zsh that restores backwards compatibility with the Thompson Shell.

As for GUIs, while Cosmpolitan isn't intended to write complex GUI apps (since I normally build web apps for that) it still can be used to build WIN32 GUIs. See https://justine.lol/apelife/index.html as an example of Conway's Game of Life built with Cosmopolitan, where it manages to embed a UNIX TUI and a WIN32 GUI in the same binary! Even if you turn the WIN32 off, it'll still run the TUI inside the Windows Command Prompt.

As for breakages, Cosmopolitan only depends on stable kernel ABIs with long term stability promises. The binaries you build will work on all Linux distros dating back to RHEL5, Windows versions dating back to Vista, etc. There's no reason to believe there will be any radical breaking changes to these ABIs. Why is it so stable? Alignment of self-interest. By using canonical kernel interfaces, we ensure platforms can't break us without first breaking themselves.

> Cosmopolitan only depends on stable kernel ABIs with long term stability promises

Unfortunately, on macOS a stable kernel ABI does not exist. macOS has had kernel ABI breakage before, even recently, and they explicitly warn against making your own system calls. On Windows things are more stable but that's only because Windows cares about backwards compatibility to the point of preserving the behavior of even the most badly written software.

Linux is the only one of the three that promises a stable kernel ABI. On macOS and Windows the system libc is the only safe way to talk to the kernel.

Ntdll is the kernel ABI on windows; it is stable, and cosmopolitan uses it.

Not sure what you mean by ‘on Windows thing are more stable’; syscall numbers change all the time. See https://j00ru.vexillium.org/syscalls/nt/64/

Cosmopolitan uses the stable WIN32 API on Windows. The word ABI is used loosely in this context. It's somewhat accurate since Cosmopolitan still configures the assembler to generate the binary linkage by hand. NTDLL APIs are only used on rare occasions, such as sched_yield(). When it is used, it's used in accordance with Microsoft's recommendations: namely having fallback. It'd be great if we were authorized to use the SYSCALL instruction on Windows, however we have to respect Microsoft's recommendations if we want long-term stability promises.

Mac however is a different story. One of the reasons Apple came back is they adopted the UNIX interface. They literally copied the magic numbers right out of the UNIX codebase. They shouldn't have the right to stand on the shoulders of giants, pull an about face, and then declare it their own internal API. Apple actually broke every single Go application a few years ago by doing that, and I'm astounded that Google didn't do more to push back. We need to give Apple as much feedback as possible about keeping the SYSCALL interface stable. It's the right thing to do.

Ntdll is not meant to be used as public API, whatever works does so by pure luck.
Microsoft documents ntdll as subject to change and makes no promises not to break it. When I was at MS it was advised not to use it for things that ship outside Windows. They had automated scripts to flag such a use.

In practice it is pretty stable, and many use it. Many things cannot be changed without causing massive internal refactors and app compat issues. But it is regarded as internal.

Why vista only? Is it 64 bit only? Otherwise for TUI-only this should go back to at least NT4. Or is it using anything clever that makes older versions barf?

Edit: Ah, maybe it's bypassing ntdll and syscalls changed?

Author here. It's Vista+ because Cosmopolitan focuses on x86-64. Cosmopolitan does however support i8086 since these executables boot from BIOS. You can even define a _start16 entrypoint if you want to write real mode apps. No effort has been made to natively target i386, however it's still technically supported since Actually Portable Executables will re-exec themselves under qemu for other architectures.
I think the right way to add GUI support to this is to use the web. Simply embed an HTTP server in your binary and open the user's default web browser on launch. I have a framework for this here: https://github.com/jdarpinian/web-ui-skeleton
Author here. I agree. Check out redbean which is a single-file distributable web server, built with cosmopolitan: https://justine.lol/redbean/index.html redbean is forking web server (works great on windows) that can serve 1 million+ gzip encoded responses per second (only linux and freebsd are capable of that kind of performance) because APE binaries are isomorphic to the zip format which enables kernelspace copies. You just bundle all your assets inside the .com executable by changing the extension to .zip and the web server will deliver them via http.
Yeah, very similar idea. The zip trick is awesome; especially letting the browser do the decompression is brilliant. My version has a couple features you might want: automatically opening the browser when launched and shutting down the server when the last tab is closed, and a debug mode where it serves directly from the filesystem to make editing easier. Man, I can think of so many cool features I'd want to add to this as well:

CGI support. Maybe also an option to embed node or deno, or even tcc for directly executing C source as CGI scripts.

Embedded writable SQLite DBs. Appending would clearly be tricky but probably doable.

PWA support, giving you native notifications, launcher icons, and top-level windows sans browser chrome just like a native GUI app.

Option to morph into various app formats (apk, appx, ipa) and instant install on a connected phone or upload to app stores. (Wait, actually all of these formats are based on zip! So maybe this wouldn't even need that much morphing? Just some extra cruft in the zip file, some code signing BS, and maybe an embedded copy of adb.)

With all these features it might not be tiny anymore but it could make a pretty complete cross platform native app dev environment contained in a single file. Still way smaller than Electron.

Not an answer, but Splunk currently has a large deployment of a C++ application that supports a large variety of platforms. At least as of a few years ago, they deployed C++ indexing and querying nodes on customer machines. They've spent a great deal of time to accommodate the peculiarities of enterprise customers, old compilers, etc. One might imagine that a "build once run anywhere" platform would benefit them, but they've found a business model where they were able to work around the peculiarities and still be profitable.

So while there's probably some interesting historical reasons that explain why there's no "JVM for C++", Splunk is a good example of a modern technical / business model where conquering platform fragmentation was feasible.

Author here. Here's the best explanation I've thought of so far:

> If it's this easy, why has no one done this before? The best answer I can tell is it requires an minor ABI change, where C preprocessor macros relating to system interfaces need to be symbolic. This is barely an issue, except in cases like switch(errno){case EINVAL:...}. https://justine.lol/ape.html

It also took an act of willpower. This is a novel approach to building software that says the conventional wisdom is wrong. When I first stumbled upon the idea, I couldn't believe it was even possible, until I actually did it. Now I'm glad that I did. Actually Portable Executable has been so useful for my work. I used to work on the TensorFlow team, so I've seen the worst c/c++ build toil imaginable, and looking back at those experiences I just can't believe how I managed to get by for so long without having portable build-once binaries without interpreters.

Have you tried to code-sign executables produced by this method?
On Windows, Authenticode signatures rely on and use PE header - I'd also love if the author could answer this one. If it does work, I'd also like to know why it works :)
On macOS, these executables can be signed with a detached signature. Surprisingly, the embedded codesignature also works (but the signature is stored in an extended attribute on the filesystem).
Why not keep constants constant (so your switch example works), but have an extra translation step in front of the syscalls?
Fair enough and inspiring. However I am still wondering how to advocate changes in such a scale.

For example, Linux introduces some fixes for Y2038 problem during the past few years. I suppose the community of this C library should do something for those changes, right?

> For an example of how that works, consider the following common fact about C which that's often overlooked. External function calls such as the following: memcpy(foo, bar, n); Are roughly equivalent to the following assembly, which leads compilers to assume that most cpu state is clobbered:

I think most modern C and C++ compilers know that memcpy is special and optimize accordingly. I believe some C++ compilers recognize when you are using memcpy just to do bit casting to different representations and avoid doing a memory copy at all.

Even if you are trying to avoid the standard library, visual studio will insert memcpy into a loop that is just copying memory, then the linker will error out when it can't find it.
This is true, but it's possible to prevent this behavior by turning off optimizations for just one function (by adding ``#pragma optimize("g", off)'' before the function definition and ``#pragma optimize("g", on)'' after the function definition).
A better way to do with with GCC at least is -fno-builtin.
You can disable them individually too: -fno-builtin-memcpy
Replace modern with clang, and awful with gcc. I have no idea why gcc is still so fast overall, but its memcpy is horrible, as shown in the blog post. My own memcpy in my libc also beats the gcc/glibc one. The compilers are only good for short memcpy's. For the rest the asm hinders modern compiler optimizations.
Not sure what your point is calling gcc both ‘awful’ and ‘so fast overall’. Nor why you're conflating gcc and glibc.

FWIW my experience has been has clang produces a superior development experience for c++ code, but that gcc is better for c. Llvm does have a more modular architecture than gcc, but it's not really fair to say that it's more modern overall. Gcc has gotten several features ahead of llvm, like hot/cold LTO separation and parallel compilation (the former is now in llvm and the latter apparently is impractical due to architectural problems).

> The compilers are only good for short memcpy's. For the rest the asm hinders modern compiler optimizations

Also not sure what you mean by that. TFA isn't performing memcpys with inline assembly. They're using inline assembly to perform a call to the exact same ‘memcpy’ function you would have called anyway, but also to inform the compiler that memcpy clobbers fewer functions. So it's true that this will mainly improve performance for copies of small memory regions; but the performance of large copies will be unaffected.

If anyone's curious, here's the link to memcpy() as it's actually implemented in the Cosmopolitan headers: https://github.com/jart/cosmopolitan/blob/de09bec215675e9b0b... One thing that the web page doesn't mention (for the sake of simplicity) is that the Cosmopolitan headers do call __builtin_memcpy() as well, but only for 2-power constexpr sizes. That's the only time when GCC and Clang both do the optimal thing. In all other cases it's faster to use asm("call MemCpy") which implements something faster than the builtin would otherwise generate. See https://github.com/jart/cosmopolitan/blob/de09bec215675e9b0b...
wow, most likely, this means there would be a virus infective to all operating systems...
It's not really necessary for that case. At the level of taking over control, you're attacking a specific system, so you need to know what it is and can provide specific payload. For the later phases, the dropper knows where it's running so it can request a specific version of the virus as well.

So there's nothing stopping multi-os viruses right now.

Not sure why you're being down voted, perhaps for telling the obvious?

I can foresee Morris Worm v2 coming up sooner rather than later but this time it won't just targeting UNIX systems. It is the obvious killer application for WORA library that use C, whether we like it or not.

On the bright side, for a start this probably can solve the many redundant VC++ compiler versions installation in a single computer just before we get to the 2030 VC++ compiler versions :-)

Anyway kudos to the library's author, when the late Dennis Ritchie invented C for portable OS, he had probably never envision how far it can be made portable.

Doesn’t the “strlcpy” example near the bottom of the page overwrite the buffer by one byte when the source string is as long or longer than the buffer?

Imagine I have a three byte buffer, n is 3 and I copy in “foo”. The result of the MIN is going to be three. And d[3] is the fourth byte of my three byte buffer.

Seems that way to me as well.
Author here. The example and repository has been updated. Many eyes make all bugs shallow. I was hoping to do more testing before posting on Hacker News. Now that the cat's out of the bag, I'd like to explain what's being done to make sure bugs like that never happen.

Cosmopolitan implements a simplified version of the Address Sanitizer (ASAN) runtime. See https://github.com/jart/cosmopolitan/blob/master/libc/log/as... I've been systematically working towards using it to vet the entire codebase. It's helped to address more than a few memory issues so far. However more work needs to be done polishing the ASAN code so it can be bootstrapped for the purposes of having memory safety in the lowest level libraries like LIBC_STR, due to the way that the Makefile requires acyclicity between libraries.

This has been less of an issue, because bugs are normally very evident in functions like strcpy(). The BSD version is an exception though, since it was only used to port programs from the OpenBSD codebase which were used sparingly. But in either case, the real focus here is having continual incremental improvements over time, and I'm very committed to addressing any and all concerns.

Cosmopolitan also has extensive unit tests. This is something that C libraries have traditional done somewhat sparingly, if at all, since C library authors conventionally rely on an ecosystem of ported software that very quickly breaks if everything isn't perfect. If you build the Cosmopolitan repository, you'll notice there's 194 independent test executables in the o/test folder. That's one of the great things about Actually Portable Executable. It makes binaries so tiny that we can have lots of tests, completely isolated from each other!

Have you ever tried C formal verification tools? Check out open source Frama-C https://frama-c.com which uses ACSL (ANSI/ISO C Specification Language). The free documentation and tutorials are pretty good.
This is very clever. I wonder if it could be adopted for Python C extensions (where possible) to make it easier to distribute cross-platform binary distributions. Of course it wouldn’t be suitable for gui toolkits, but I imagine that the majority of Python C-extensions could be handled.
Cosmopolitan would actually be a great fit for Python extensions, since manylinux1 requires RHEL5 support and Cosmopolitan is probably the only modern C library that still supports it (glibc these days needs rhel8+). Maybe someone will figure out a way to compile a single-file library that's simultaneously a .dylib / .dll / .so just like how Actually Portable Executable does for programs.
This approach of using inline asm wrappers around functions which specify exactly the clobbered registers is neat, but...

I don't think the approach of making a call from inline asm is safe. In particular, it clobbers the redzone [1], which the compiler may have used and expect to be intact after the call.

In general, at least some compilers (e.g., gcc) assume there are no calls in an inline asm block.

---

[1] https://godbolt.org/z/e4ecKr

Cosmopolitan doesn't use the "red zone" because these binaries boot on bare metal and operating system code can't assume a red zone. GCC supports this. Otherwise the Linux Kernel wouldn't work, since it's compiled the same way. So invoking call from inline asm is perfectly safe. The only real issue is that the called function can't assume the stack is 16-byte aligned. But that's fine, since Cosmo only using asm(call) to call functions written in assembly.
Well you edited the compile command on the page just now to add -mno-red-zone, but it wasn't there before. So do you mean it is safe as long as -mno-red-zone is used?

Even then, I am not quite sure: redzone is just an example of one of the assumptions that might be violated when calls are made from an asm block, but at least some gcc devs are on record saying that calls from inline asm are not supported: the main problem is that leaf functions can be compiled differently than non-leaf, and a hidden call breaks this (i.e., a non-leaf function may appear as leaf to the compiler).

Agree. This is a great little micro-optimization, but definitely requires some compiler support to do safely (on the other hand, compiler devs should want to support it, because it's a great little micro-optimization, so I don't think that it would be _too_ hard to add).

Note that one way to get the code size wins for the caller in a compiler-blessed way is to use an alternative calling convention, like Clang's preserve_most; this isn't as good as explicitly providing a clobber list, but allows a library to provide a stable interface.

I can confirm we came up with this optimization inside tcmalloc, and our compiler experts nacked it as unsafe, even only calling tightly controlled code. A pity, as it is really beautiful to see the improved clobbering (and even, in some cases, hacked calling conventions.)
In case it helps anyone else, this is a compact libc for an x86_64 binary format that can bootstrap and make syscalls on several kernels but relies on static linking (it doesn’t use any platform’s shared libc).
> Please note this is intended for people who don't care about desktop GUIs, and just want stdio and sockets [...]

Not to diminish the achievement at all, but a significant limitation to be noted (and not a very surprising limitation).

I also wonder about things like the sizeof(long). On 64-bit linux, this is 8, but on windows, it's 4. Well, "on windows"... maybe this throws all the APIs that make such assumptions, uh, out the window, (puts on sunglasses).

Author here. That's not an inherent limitation. See https://justine.lol/apelife/index.html for an example of a GUI + TUI that's built using Cosmopolitan. The reason why I said what you quoted, is I'm simply trying to calibrate expectations. I don't view desktop GUIs as a productive area of focus, because there's such a lack of consensus surrounding the APIs that requires, and web browsers do a great job.

As for sizeof(long) it's 64-bit with Cosmopolitan, which uses an LP64 data model. That hasn't impacted its ability to use the WIN32 API. See https://github.com/jart/cosmopolitan/tree/master/libc/nt where WIN32 API functions are declared using uint32_t, int64_t, etc. It works great. I compile my Windows apps on Linux and it feels wonderful to not have to touch MSVC. Cosmopolitan even takes care of the chore of translating your UTF-8 strings into UTF-16 when polyfilling functions like open() which delegates to CreateFile() on Windows.

Thanks for the informative reply.

> and web browsers do a great job.

Suppose one had a native linux game that uses POSIX, OpenGL and SDL. Is there any hope?

Having given the apelife source a glance, it seems quite possible to me; it already shows you how to create a window on Win32.

And SDL2 has already been ported to Emscripten/WASM after all, and OpenGL is really no trouble, since it's usually accessed through a mostly platform-agnostic C API wrapper already (see for example, https://github.com/Dav1dde/glad ).

And maybe you could use MinGW to support POSIX on Win32...

Now I want to see somebody better at C than me try smashing cosmopolitan and the C version of Dear Imgui together and see what happens.
Regarding consensus desktop APIs, maybe it's time to re-invent/-introduce Win32s ;)

(Although adding that to the library would add considerable bloat, and cross-platform runtime dynamic linking or a "dynamic polyfill" might be a bit beyond the scope of this)

Or raw X11 protocol, of course (WSL made X11 servers on Win10 quite common, and . If that wouldn't be so horrible, it would be interesting how small one could get basic widgets starting from scratch.

It should be possible to compile wxWidgets with Cosmopolitan, one variant per platform (win32, POSIX/X11, MacOS) and initialise the correct one at runtime depending on where you're launched, no?
> it would be interesting how small one could get basic widgets starting from scratch.

Please don't do this for any application with a wide audience. Small, simple widgets are missing many important things such as accessibility and internationalization.

Sure, as do most TUIs and a lot of web apps. I'm merely suggesting a hack that goes along with this hack, not the foundation technology for a mono-binary Office clone.

Re-inventing the UI wheel seems somewhat common within the Go and Rust communities (as is re-inventing everything else), this would just have raw X11 protocol messages via socket as the foundation layer, not Vulkan/GL/SDL. And there just enough to be useful, e.g. text, input, buttons, canvas…

For something more serious, I suggest re-inventing PostScript/NeWs as a service, of course.

> I'm merely suggesting a hack that goes along with this hack, not the foundation technology for a mono-binary Office clone.

Sure. I just worry that one person's clever hack will then become another person's indispensable tool, which they'll then impose on their colleagues, employees, or contractors, blocking a third person from doing their job. I know a blind person who temporarily lost his job because his employer's largest client required an application that didn't work with screen readers. So I just want to make sure developers are aware of accessibility, and in general, the risks of reinventing things that are necessarily complex.

That's one of the biggest issues some developers have: We pride ourselves in the wisdom to see "bloat", but as you say, a lot of complexity arose out of necessity, even if the developer themselves doesn't need it. Privileged minimalism.
I have used XWin32 and Hummingbird since 2000.
> Cosmopolitan even takes care of the chore of translating your UTF-8 strings into UTF-16 when polyfilling functions like open() which delegates to CreateFile() on Windows.

That shortcoming of the MSVC runtime has been the bane of multiple projects I've done on Windows. Even without the portable executable trick, Cosmo might be useful as an alternative Windows CRT that can be used with GCC or clang. (Edit: Except that ARM64 Windows is now a thing, whereas Cosmo seems to assume the continued hegemony of x86-64.)

This is an interesting, and potentially compelling argument that WebAssembly binaries and runtime actually aren’t the future of compile-once-run-anywhere systems software: these support syscalls, sockets, and stdin/stdout —- all of which requires platform-specific glue code to achieve using WebAssembly and standard libc (or JS).
Author here. WebAssembly has played an important role protecting CI on the Internet and it's cool that it enables us to run LLVM in the browser. I was very surprised when I learned that some people were using it offline outside of browsers, since doing so requires a JVM-like runtime such as wasmtime. Cosmopolitan proves that it's possible to just fix C instead, which might fix all the stuff that's built on top of C too. We can in fact have first-class portable native binaries, which will only require JIT compilation if they're launched on a non-x86 machine. This is probably how Java would have been designed, if Intel's instruction set hadn't been encumbered by patents at the time. That's changed. The x86-64 patents just expired this year. So I say why not use it as a canonical software encoding.
Because x86 instruction encoding is a mess. Take a look at this[1] flowchart from the amd manual (vol. 3).

That doesn't even tell the whole story; there's a ton of path dependence, and the operand format is quite complicated and has tons of special cases.

1. https://0x0.st/-rce.png

I thought x86 was pretty straightforward once I understood the octal encoding. Cosmopolitan provides a 3kb x86 instruction length decoder that supports all past, present, and future ISAs: https://github.com/jart/cosmopolitan/blob/master/third_party... The Cosmopolitan codebase also provides a debugger / emulator for i8086 + x86-64 that's similar to GDB TUI and Bochs. See https://justine.lol/blinkenlights/index.html For example, here's the ALU code: https://github.com/jart/cosmopolitan/blob/master/tool/build/...
I wouldn’t call 3kb “pretty straightforward”, certainly not relative to the competition. The instruction length decoder for quite a few CPUs is

  return INSTRUCTION_SIZE;
I don’t know any other that has more than a few cases for immediate constants and jump addresses.
If Sun would have picked a ‘real’ CPU, I doubt it would have been x86 (did they even sell any of them at the time?)

Also, picking a real CPU is only worth it for running on that CPU, and that particular one. If they had picked x86 in 1996, they wouldn’t even have supported MMX.

In a platform-independent design, using n registers in the virtual CPU is only a good idea for n = 0 (i.e. a stack machine, as in the JVM, .NET CLR and Web Assembly) and n = ∞ (as in LLVM).

If you pick any other number your platform-independent code needs to handle both the case where the number of op real registers is smaller (so you need to have a register allocator) and the case where it is larger (there, you could just ignore the additional registers, but that means giving up a lot of performance, so you have to somehow figure out which data would have been in registers if the CPU had more of them).

Why write and optimize both of these complex problems, if you can pick a number of registers at either end of the scale, and spend all your resources on one of them?

And that’s even more true for x86-64, which doesn’t have a fully orthogonal instruction set, has I don’t know how many ways to do vector instructions, none of which allow you to clearly express how long the vectors you’re iterating over are (making it hard to optimally map them to other CPUs or newer vector instructions), has 80-bit floats, etc.

There’s a reason Google stopped work on https://en.wikipedia.org/wiki/Google_Native_Client in favor of Web Assembly.

Also, the high-level nature of Java bytecode enables/simplifies many optimisations in the JIT.

For example, dynamic dispatch is handled by the JIT, it's not encoded into low-level Java bytecode instructions, so if the JIT can see there's only one class loaded that implements a certain interface, it can generate code that directly invokes methods of that implementing class, without going through a vtable. It can do this even across library boundaries. That wouldn't be possible (or at least, would be greatly complicated) if Java bytecode provided pre-baked machine code.

Modern JVMs also have pretty deep integration of the GC and the JIT, if I understand correctly. The Java bytecode format is high level so the JIT is quite free to implement memory-management however it likes. If the JVM took a truly low-level approach to its IR, we'd presumably be stuck with 90's GC technology.

I imagine it would also have implications for the way the JVM handles concurrency. It seems right that it defines its own memory model with its own safety guarantees, rather than defining its model as whatever x86 does.

It's telling that .Net took the same high-level approach that Java bytecode did.

excuse my ignorance on this, but this sounds amazing. game changing. yet it's being "served" here on ice. casually. what am I missing?
WORA's been promised for decades. Even if it's true no one will believe it (including me). And portability is not the only reason people choose Rust or Go over C.

That's before you get into whether you can aesthetically convince everyone that x86-64 is good, actually.

Still very interesting though, especially compared to the usual Show HN crap.

Your comment would be better without the last sentence.
Portability was a long desired attribute of computer software up until it turns out that it doesn't matter at all.

When software was deployed on floppy disks and there were 10k architectures going around, portability was critical to expand the market reach of any distributed software.

But in the modern age when the software runs "in the cloud" and there are dominant platforms out there, turns out that portability doesn't matter - nobody is changing platforms when they don't need to.

So this makes this achievement a pretty cool demo without any industry impact, since people don't distribute C binaries anymore.

The idea behind it and the technology developed is pretty cool :)

> We can in fact have first-class portable native binaries, which will only require JIT compilation if they're launched on a non-x86 machine.

This is nothing new, IBM and Unisys mainframes have been doing it since the 60's.

IBM System/360 and NexGen i586 were big inspirations for the design of Cosmopolitan.
Are you familiar with IBM i (née AS/400)? There's a similar idea there, where programs are compiled to a virtual instruction set called TIMI then statically recompiled to whatever the machine's native instruction set is at the first load on a given machine. As a result, you can just drop a System/36 binary on a modern PowerPC system and not even notice that you've just crossed two changes of instruction set.
"Only require JIT compilation if they're launched on a non-x86 machine" is a heck of a caveat.

The x86-64 instruction set is not a good portable IR. Some well-known problems:

* It's much more irregular to decode than other instruction sets/IRs

* There is a truly vast set of instructions, very many of which are almost never used. So in practice you need to define the subset of x86-64 you're using. E.g. does your subset contain AVX-512? AVX2? AVX? BMI? X87? MMX? XGETBV? etc etc etc. These decisions will impact the performance of your code on actual x86 CPUs as well as non-x86 CPUs.

* x86-64 assumes the TSO memory model which is horribly expensive to support on CPUs whose native model is weaker (e.g. ARM) (which is why Apple added a TSO mode for M1; no other ARM chips have this)

Honestly, declaring x86-64 your portable IR and then claiming that as a technological breakthrough sounds like a trick to me. I'd agree it's a breakthrough if you define your x86-64 subset, show your x86-to-ARM compiler, and show that it produces code competitive with your competitors (e.g. WASM compilers).

> doing so requires a JVM-like runtime such as wasmtime. Cosmopolitan proves that it's possible to just fix C instead

This doesn't sound like a fair comparison. WASM isn't just providing portability, it's also providing security and runtime safety. Cosmopolitan doesn't, it presumably requires you to trust the codebase, and doesn't protect you from C's undefined behaviour. Of course, WASM also imposes a considerable performance penalty, I presume the Cosmopolitan approach easily outperforms WASM.

> This is probably how Java would have been designed, if Intel's instruction set hadn't been encumbered by patents at the time.

To mirror the comment by Someone, I sincerely doubt this. Java bytecode is a very high level stack-based IR, nothing like a CPU ISA. They could easily have made it resemble a CPU ISA if they'd wanted to. The lesser-known GNU Lightning JIT engine takes this approach, for instance.

WASM only provides sandboxing. That is not the same as security nor it means runtime safety nor protection from undefined behavior.
> WASM only provides sandboxing. That is not the same as security

The relevant Wikipedia article is named Sandbox (computer security).

> nor it means runtime safety nor protection from undefined behavior

It puts stronger constraints on what mischief undefined behaviour can lead to, and guarantees that various runtime errors are handled with traps. [0] This isn't the same as a hard guarantee that execution will terminate whenever undefined behaviour is invoked, but it's still a step up.

[0] https://webassembly.org/docs/security/

Speaking as someone who would love to see WebAssembly succeed as a cross-platform, cross-language way for me to write sandboxed plugins and CLI utilities, I do have to point out that, for out-of-browser use, WASM's MVP does regress various things:

* https://00f.net/2018/11/25/webassembly-doesnt-make-unsafe-la...

* https://www.usenix.org/system/files/sec20-lehmann.pdf

(eg. Under WebAssembly's memory model, dereferencing NULL=0 won't lead to a segfault.)

> Under WebAssembly's memory model, dereferencing NULL=0 won't lead to a segfault.

Thanks that's a curious one. It won't always lead to a segfault with a conventional compiler either, undefined behaviour being what it is. [0][1] Fortunately GCC can be asked to add such checks at runtime, [2] this approach could also be taken with WebAssembly.

[0] https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=63...

[1] https://blog.regehr.org/archives/213

[2] https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.h...

I was more referring to how it's standard practice on modern OSes to leave the zero page unmapped in a process's address space so that, if the compiler converts the NULL dereference into a dereference of address 0x0, it'll trigger a segfault.
WebAsm is not performance competitive:

"We find that the mean slowdown of WebAssembly vs. native across SPEC benchmarks is 1.55× for Chrome and 1.45× for Firefox, with peak slowdowns of 2.5× in Chrome and 2.08× in Firefox."

https://arxiv.org/pdf/1901.09056.pdf

x86 isn’t performance competitive either if you’re targeting a CPU that doesn’t run it natively. In fact, it’s much worse than WebAssembly.

Even Apple Rosetta takes roughly a 1.25x-1.50x hit on SPEC compared to native [1], and that’s with ahead-of-time compilation and no software sandboxing, whereas WebAssembly VMs typically use JIT compilation and do provide software sandboxing, both of which come with overhead. In contrast, inNative [2] is a WebAssembly compiler that has ahead-of-time compilation and no software sandboxing, and it can supposedly reach 95% of native performance (no idea if the benchmarks are cherry-picked though). This makes sense: WebAssembly is higher-level, closer to a compiler IR, whereas x86 assembly has already decided on low-level details (like register allocation and calling conventions) that are suboptimal to emulate on another architecture.

And other x86 emulators will do worse than Rosetta. For one thing, Rosetta can take advantage of the Total Store Ordering mode added to M1 chips for the specific purpose of emulating x86 faster. An emulator running on pretty much any other CPU has to emulate a stronger memory model in software on top of a weaker one, which comes with a massive unavoidable penalty for multithreaded workloads. Also, most x86 emulators are just not as efficient as Rosetta, adding an additional penalty.

[1] https://www.anandtech.com/show/16252/mac-mini-apple-m1-teste... [2] https://innative.dev/news/

These binaries wouldn't provide the isolation that wasm provides... Although it's looks like they're essentially unikernels, which means that they could potentially be run per-invocation by a hypervisor.
In theory the linked APE library [1] can also be used for languages like Rust and Go binaries to make fat portable ones as well, right?

[1] https://justine.storage.googleapis.com/ape.html

Yes! Cosmopolitan and APE are now permissively licensed under ISC terms. So they'd be a perfect fit for language authors. One of the biggest obstacles language authors encounter, is overcoming the obstacles posed by portability between operating system apis. Cosmopolitan takes care of that. You can just use the normal textbook unix apis and have it work on Windows/Linux/BSDs. Best of all, Cosmpolitan is tiny, embeddable, and unobtrusive. As in, these "fat binaries" are actually more like 16kb in size. So any language author who chooses to use it is going to have more time to focus on their vision, while reaching a broader audience from day one, with a sexy lightweight form.
can you bundle shared objects in the (blob) executable?
Wow. I am so surprised that this could happen
Wow I am very surprised that this could happen. An executable that can run on every platform? I am curious about it
When you think about it, 99% of the code of a compiled C program is processor specific object code which in theory should not care about the operating system or environment at all. It is the 1% like API/ABI and header magic that makes the same object code incompatible under different OSes.
For the insight, I tip my hat. For the timeline where people actually use this, I put it back on.

Bob Barton is surely turning in his grave.

Bravo, this is really interesting, applicable or not these kinds of projects are really important. Looking forward to seeing this evolve.
I have Wine installed on my Linux system, which uses "binfmt_misc" to automatically run Windows-looking executable files through Wine. This causes an issue with Cosmopolitan, since it uses an output format that triggers binfmt_misc. I had to run this to disable binfmt_misc on my system:

    sudo systemctl mask proc-sys-fs-binfmt_misc.automount
...and this to disable it for just the current boot:

    sudo bash -c 'echo 0 > /proc/sys/fs/binfmt_misc/status'
Give this a try:

    sudo sh -c "echo ':APE:M::MZqFpD::/bin/sh:' >/proc/sys/fs/binfmt_misc/register"
That command should allow Actually Portable Executables to co-exist with WINE, which assumes the MZ prefix, since APE binaries always use the longer MZqFpD prefix.
Thanks! That worked.

I also tried adding the line to a new file /etc/binfmt.d/APE.conf, but it didn't seem to work (after a reboot); after renaming the file to zz_APE.conf, it did work after a reboot. systemd's binfmt.d registers files in alphabetical filename order, so I'm guessing it's important that /usr/lib/binfmt.d/wine.conf is registered before the APE handler, meaning its filename must be alphabetically after "wine.conf".

Can somebody explain the whole thing to me? I know how to programm on windows and linux, but thats basically at the level of calling the compiler and linker, and some parameters to sometimes build a dll. So what does this do, and how does it work? Does it apply to DLLs as well? Also, 'run anywhere' means on the same processor type?
It’s a libc implementation accompanied by a linker script that tricks the linker into generating a polyglot executable file, simultaneously interpretable as an MZ executable, x86 boot sector and a Unix shell script, the latter of which re-writes the executable into ELF or Mach-O format and then executes it again. I haven’t analysed it all that thoroughly, but that’s the gist of it. Here’s more information: https://justine.storage.googleapis.com/ape.html

Polyglot files are not a particularly new invention, but devising a reproducible process to generate those can be quite tricky, so most don’t bother unless there’s a special need for it. (For example, the GRUB4DOS fork of GRUB contains a ‘bootlace’ executable that is simultaneously executable as a DOS .COM and an ELF file.)

The libc itself contains a number of specially-crafted functions and header files that expose the functions’ clobbered register set as part of the functions’ public ABI, which allows the compiler to use that knowledge to better allocate registers and optimise more aggressively. The downside is that if the clobbered register set changes, it requires everything using the function to be recompiled.

How is the libc polyglot? I mean don't system call vary widely between windows and Linux?

    int ftruncate(int fd, int64_t length) {
      if (!IsWindows()) {
        return ftruncate$sysv(fd, length);
      } else {
        return ftruncate$nt(fd, length);
      }
    }
You get the idea. The OS is detected at startup and then checked each time a function is invoked.
Would this not make the implementation inefficient?
IsWindows is likely a macro and therefore you have if(0) or if(1) which the compiler can easily optimize away.
I thought this implementation was meant for compile-once-run-everywhere usage, so I can't see how a compiler would do away with a this if-statement. Could you please say more?
The compiler keeps the branch. The branch doesn't impact performance because it's fully predictable and therefore costs less than 1 nanosecond. Please note however that fadvise() is a system call and the SYSCALL instruction o/s context-switch generally costs 1 microsecond, which is 1000x slower than any overhead incurred by the portability benefits Cosmopolitan offers.

You can however tune the build so the `IsWindows()` branch is dead code eliminated anyway:

    make MODE=tiny CPPFLAGS=-DSUPPORT_VECTOR=0b11111011
Basically unsetting the Windows bit causes the Windows polyfills to not be linked in your binary. It's only useful for making binaries tinier. So rest assured, if you find your 16kb APE .com binary to be too bloated, you can always use SUPPORT_VECTOR to squeeze it down to the order of a 4kb ELF executable. See: https://github.com/jart/cosmopolitan/blob/1df136323be2ae806f...
"Run anywhere" appears several times in the page, but it seems to be a wild exaggeration, especially given the comparison to Java.
Indeed: as far as I understand it, Cosmopolitan works across different OS's (or even without OS), but not across different CPU architectures. Comparing that to Java is a gross exaggeration indeed.
What if you include an emulator in the executable? eg x86-64 on ARM. Then you could slowly run the x86 code on the pi for various values of "slow".
Considering that for Java it really means "run anywhere you have a JVM/JRE already installed", the marketing slogan is an exaggeration in both cases.
Cute trick, but probably fragile. It basically requires either writing functions like memcpy in assembly by hand or parsing the compiler-generated assembly post-codegen to determine the actual set of register clobbers. Otherwise it is bound to be broken as soon as the register allocator decides to clobber a different set of registers when generating the function.

Also, this effectively makes the body of the function a part of its public ABI, which makes it morally equivalent to inlining; it requires users of the function to be recompiled each time the body (and in consequence the clobber set) is changed. But since this seems to be static-linking-only library, this seems not that much of a problem in this case.

Still, I’m expecting improvements to whole-program LTO and inter-procedural optimisations to render this hack obsolete.

There is a typo in this fragment: "the following common fact about C which that's often overlooked". Just trying to help.
Fixed. Thanks!
(comment deleted)