Theory of "target-triple" and how to run Zig on "POSIX-compliant" system

At work, I can cross-compile my Zig program to aarch64-linux-gnu.2.31+ and be happy having it run, also linking it with existing libs (written in C++ but exposing C headers along with some object files). Today, I was asked whether Zig can compile to non-classic targets (eg. QNX Neutrino), which are “POSIX-compliant”. To be honest, I am not exactly sure what that means (of course, I’ve read wiki pages about it). I feel like I don’t understand how “POSIX-compliant” maps to the classic target triples <arch>-<os>-<abi>. To be honest, twice, I’m not even sure what exactly the <os>-<abi> part means now. Usually, terms like OS ABI, libc API, C-ABI, system call ABI, and runtime ABI often get mixed up. Understanding the theory of what this triple maps to at different levels would be of great help (llms haven’t helped me :)).

I also found out that Zig can be transpiled to C using zig build-obj/exe -ofmt=c file.zig, which can then be further compiled by some C compiler. I thought maybe this is the way to extend the current (limited) list of targets to ones for which there are separate C-compatible SDKs. However, I do not know, for example, which C standard the transpiled code follows, which libc API (version) it uses, and there is hardly any information about it.

2 Likes

The ‘platform tier table’ in the release notes is a good starting point I think:

…but in reality it’s a bit more complicated. For instance (all AFAIK), it’s possible to cross-compile a DirectX Windows application with just the Zig toolchain, because the mingw2 ‘Windows SDK’ headers which are embedded in the Zig toolchain come with DirectX headers, but for compiling a macOS/iOS Cocoa/Metal application you need a native macOS/iOS SDK.

When it comes to libc vs POSIX my knowledge is also blurry though. AFAIK if your code only depends on MUSL as libc, cross-compiling will be a lot easier than depending on a platform-specific C library like glibc or MSVC’s ‘Universal C Runtime’ …e.g. the promise of POSIX as a portability standard is in reality more like a ‘loose guideline’ outside the UNIX world :wink:

I also found out that Zig can be transpiled to C using zig build-obj/exe -ofmt=c file.zig

AFAIK this feature is currently broken. nvm, seems like it’s “unbroken” again

PS: this might also be useful Cross-compilation using Clang — Clang 22.0.0git documentation (e.g. Zig’s usage of target triples should be mostly the same as Clang’s)

3 Likes

It seems relatively straightforward to add support for another open-source POSIX OS to Zig. For instance, SerenityOS was among the most recent additions. Here’s the finalizing PR for it, referencing the main patches that came before:

It basically requires a little legwork in terms of creating a new OS tag value and handling the new case wherever the std logic switches on that tag, which comes down to ensuring Zig knows what the new target’s POSIX C ABI is.

The trickier case is compiling to a target that has a designated SDK, which might not even be open-source – basically every AAA game console. For example, see the following efforts:

Btw, not an expert on this, but I believe I’m at least not wrong in pointing out the sources above.

4 Likes

This was something i was recently thinking about as well. Jeremy the creator of redox just did a 10 years of Redox at rust conf. They achieve posix compatibility through relibc.

I wonder what would be required to get zig to work on redox beyond updating llvm on redox.

Posix is a standard for interacting with the operating system. It applies to both users interacting with a terminal and to programs using operating system services.
For users, an OS being posix-compliant means that it will offer a shell and certain programs will be available, like grep.
For programs, an OS being posix-compliant means that certain services will be available through a standard API. For example, in order to open a file, you can call fopen, with the expected signature. Such functions are the typical libc functions. Note that there isn’t a standard “libc”. Libc used to be whatever the C compiler offered. Now we have some major libc implementations, but they can disagree in certain things. So you can think of the posix API as a standard for libc.
Posix restricts itself to the API. It explicitly does not touch on ABI.
The parts of the OS triple are: OS - instruction set - ABI.
The OS part is mostly needed to know the file format of the executable. For instance, on Windows we use Portable Executable. Posix does not define this, so it doesn’t enter here.
The instruction set is the most important part, as it literally defines how you will encode each instruction. Posix does not enter here.
The ABI part, I believe is redundant and should be removed. ABI defines how functions should be called. But in Zig, for internal functions (that are only called from within Zig), Zig can use whatever ABI it wants. It doesn’t even have to be consistent, it can mix and match ABIs as long as it doesn’t lose track of how each function should be called. For external functions, the ABI is defined in the callconv part of the signature. So, I believe the ABI part of the triplet simply means which ABI whould be used when the user specifies callconv(.C). Once again, Posix does not touch on this.
So the answer is, Posix has nothing to do with the OS triplet.

Hmm, AFAIK the C stdlib is clearly defined in the C standard (e.g. what headers and functions must be provided by a standard compliant C compiler toolchain).

This C stdlib is (mostly?) a tiny subset of POSIX. That’s why MSVC can call itself a standard C compiler even though you can’t compile typical Linux C code which expects a full POSIX API (like glibc).

2 Likes

Roght, sorry. I was a bit anachronic. Posix came before the C standard, so it defined the things that it needed, instead of relying on a C standard.

1 Like

The abi in the target triple also defines the which libc, when linking it

afaik you can leave it off if you’re not linking libc or there is only one option for the target, might even be a default but i doubt it.

1 Like

Also technically there is no such thing as a “C ABI”, usually it’s the OS/CPU combo which define the ABI, C compilers need to implement those system-specific ABIs when they want to talk to operating system ABIs, and often that same ABI is used as standard for static and dynamic libraries on a system (but not technically required).

1 Like

Pretty much, but it often delegates to the C standard these days.

There are many manpages these days which just state:

The functionality described on this reference page is aligned with the ISO C standard. Any conflict between the requirements described here and the ISO C standard is unintentional.

1 Like

Yep, and it’s also not necessarily possible to implement POSIX on top of the base API of an operating systems.

For example Windows doesn’t have something like fork or exec. It has CreateProcess (which kinda does both), but that’s it. Or while Windows does have the concept of a parent process, there is no reparenting possible (including for orphans).

Another thing: In POSIX handle inheriting is done implicitly and can only be done that way. You don’t have an API like fork(handles) or the like. But there are operating systems which only support inheriting handles by explicitly enumerating them like Fuchsia (Windows supports implicit and explicit (see PROC_THREAD_ATTRIBUTE_HANDLE_LIST)) which also seems to be where OS research seems to go from what I can see.
Actually thinking about it, I don’t know how such an OS could be supported by Zig’s std.process.Child API.

1 Like

You may find that this issue clarifies things a bit: RFC/Proposal: Turning Zig target triples into quadruples · Issue #20690 · ziglang/zig · GitHub

The conflation of ABI and libc in the triple is indeed quite odd if you think about it.

5 Likes

The ideal thing we’d need from Redox is a commitment to a stable ABI either for kernel syscalls or relibc (or both). We could do what we plan to do do for Serenity, which is to just ship a libc ABI snapshot of the master branch, but the reason we’ll do it that way for Serenity is that it doesn’t really do releases or stability or anything like that due to its project culture. I would assume that Redox does want a stable ABI at some point.

2 Likes

In this recent talk from Jeremy starting at 14:11 the goal is to have a stable ABI through relibc.