1. 129

  2. 23

    This is fucking crazy good! Congrats on that deep integration with C/C++. I see the future here.

    1. 7

      this is remarkable!

      for the sake of my understanding, what are the other popular options for installing a drop-in c/c++ cross compiler? A long time ago, I used Sourcery Codebench, but I think that was a paid product

      1. 7

        Clang is a cross-compiler out of the box, you just need headers and libraries for the target. Assembling a sysroot for a Linux or BSD system is pretty trivial, just copy /usr/{local}/include and /usr/{local}/lib and point clang at it. Just pass a --sysroot={path-to-the-sysroot} and -target {target triple of the target} and you’ve got cross compilation. Of course, if you want any other libraries then you’ll also need to install them. Fortunately, most *NIX packaging systems are just tar or cpio archives, so you can just extract the ones you want in your sysroot.

        It’s much harder for the Mac. The license for the Apple headers, linker files, and everything else that you need, explicitly prohibit this kind of use. I couldn’t see anything in the Zig documentation that explains how they get around this. Hopefully they’re not just violating Apple’s license agreement…

        1. 3

          Zig bundles Darwin’s libc, which is licensed under APSL 2.0 (see: https://opensource.apple.com/source/Libc/Libc-1044.1.2/APPLE_LICENSE.auto.html, for example).

          APSL 2.0 is both FSF and OSI approved (see https://en.wikipedia.org/wiki/Apple_Public_Source_License), which makes me doubt that this statement is correct:

          The license for the Apple headers, linker files, and everything else that you need, explicitly prohibit this kind of use.

          That said, if you have more insight, I’m definitely interested in learning more.

          1. 1

            I remember some discussion about these topics on Guix mailing lists, arguing convincingly why Guix/Darwin isn’t feasible for licensing issues. Might have been this: https://lists.nongnu.org/archive/html/guix-devel/2017-10/msg00216.html

          2. 1

            The license for the Apple headers, linker files, and everything else that you need, explicitly prohibit this kind of use.

            Can’t we doubt the legal validity of such prohibition? Copyright often doesn’t apply where it would otherwise prevent interoperability. That’s why we have third party printer cartridges, for instance.

            1. 2

              No, interoperability is an affirmative defence against copyright infringement but it’s up to a court to decide whether it applies.

          3. 4

            When writing the blog post I googled a bit about cgo specifically and the only seemingly general solution for Go I found was xgo (https://github.com/karalabe/xgo).

            1. 2

              This version of xgo does not seem to be maintained anymore, I think most xgo users now use https://github.com/techknowlogick/xgo

              I use it myself and albeit very the tool is very heavy, it works pretty reliable and does what is advertised.

              1. 2

                Thanks for mentioning this @m90. I’ve been maintaining my fork for a while, and just last night automated creating PRs for new versions of golang when detected to reduce time to creation even more.

            2. 3

              https://github.com/pololu/nixcrpkgs will let you write nix expressions that will be reproducibly cross-compiled, but you also need to learn nix to use it. The initial setup and the learning curve are a lot more demanding that zig cc and zig c++.

              1. 3

                Clang IIRC comes with all triplets (that specify the target, like powerpc-gnu-linux or whatever) enabled OOTB. You can then just specify what triplet you want to build for.

                1. 2

                  But it does not include the typical build environment of the target platform. You still need to provide that. Zig seems to bundle a libc for each target.

                  1. 2

                    I have to wonder how viable this will be when your targets become more broad than Windows/Linux/Mac…

                    1. 6

                      I think the tier system provides some answers.

                      1. 3

                        One of the points there is that libc is available when cross-compiling.

                        On *NIX platforms, there are a bunch of things that are statically linked into every executable that provide the things that you need for things like getting to main. These used to be problematic for anything other than GCC to use because the GCC exemption to GPLv2 only allowed you to ignore the GPL if the thing that inserted them into your program was GCC. In GCC 4.3 and later, the GPLv3 exemption extended this to any ‘eligible compilation process’, which allows them to be used by other compilers / linkers. I believe most *BSD systems now use code from NetBSD (which rewrote a lot of the CSU stuff) and LLVM’s compiler-rt. All of these are permissively licensed.

                        If you’re dynamically linking, you don’t actually need the libc binary, you just need something that has the same symbols. Apple’s ld64 supports a text file format here so that Apple doesn’t have to ship all of the .dylib files for every version of macOS and iOS in their SDKs. On ELF platforms, you can do a trick where you strip everything except the dynamic symbol tables from the .so files: the linker will still consume them and produce a binary that works if you put it on a filesystem with the original .so.

                        As far as I am aware, macOS does not support static linking for libc. They don’t ship a libc.a and their libc.dylib links against libSystem.dylib, which is the public system call interface (and does change between minor revisions, which broke very single Go program, because Go ignored the rules). As I understand correctly, a bunch of the files that you need to link a macOS or iOS program have a license that says that you may only use them on a Mac. This is why the Visual Studio Mac target needs a Mac connected on the network to remotely access and compile on, rather than cross-compiling on a Windows host.

                        I understand technically how to build a cross-compile C/C++ toolchain: I’ve done it many times before. The thing I struggle with on Zig is how they do so without violating a particularly litigious company’s license terms.

                        1. 2

                          This elucidates a lot of my concerns better than I could have. I have a lot of reservations about the static linking mindset people get themselves into with newer languages.

                          To be specific on the issue you bring up: Most systems that aren’t Linux either heavily discourage static libc or ban it - and their libcs are consistent unlike Linux’s, so there’s not much point in static libc. libc as an import library that links to the real one makes a lot of sense there.

              2. 4

                Not directly related to the article, but whenever I see these crazy good UX posts I get a little jealous. But it seems whenever I inquire about why it’s not done in “language X” there is a huge list of reasons and pitfalls. So the pessimist in me always wonder with these types posts…what’s the catch? Did they solve the pitfalls, did they ignore them due to different goals (which I think is a perfectly valid answer), or did they just not know about some of the pitfalls? I prefer when known design decisions lead to future pitfalls or areas that can’t be implemented they are stated up front so that you know what the answer to the previous question is.

                1. 9

                  What zig cc does differently from everyone else is that it ships libc itself, vastly improving out-of-the-box experience. Why doesn’t anyone else do so? The reason is that libc is big, and libc for N targets is N times big. How does zig cc solve that? The trick is Zig ships libc source, building binaries for N targets as needed, achieving N times compression for N targets. That’s it.

                  1. 4

                    I believe they also have to maintain some patching/shims (at least I thought I remembered watching a video of Andrew doing just that since there was some issue with the target libc he was demoing…but I could be misremembering). Do I understand correctly; all the other options like sysroot, target specific compile options, etc. are all handled by LLVM though Zig is doing the coordination/management (and caching)? The question begs though, why don’t other projects also ship compressed libc source? This is a genuine question, I’m not in the weeds with these problem sets.

                    1. 2

                      I really don’t know. IMO other projects should ship compressed libc sources too. It’s hard to believe, but I think it’s just a new idea which is yet to be adopted widely.

                      1. 8

                        There is some preprocessing and manual labor that goes into preparing libc sources to be used in this way. See this wiki page: How to update zig’s bundled libcs to a new release

                        It’s very similar to the work that package maintainers do for various operating system distributions and package managers, but in this case it’s specific to the zig compiler.

                        For glibc and musl it involves an algorithm to de-duplicate header files. It would be nice if the upstream projects would migrate to universal headers themselves, but I think there is zero chance of that happening. On the other hand, mingw-w64 has switched to universal headers (the same set of .h files work for all 4 targets: arm32, arm64, x86, x86_64) and it is therefore much simpler on zig’s end.

                2. 1

                  I just tried it and it didn’t work here. I got:

                  error: unknown command: -E

                  I ran CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC="zig cc -target x86_64-linux" CXX="zig c++ -target x86_64-linux" using zig 0.7.1 and go 1.15.7 on Arch Linux. This is the contents of main.go:

                  package main
                  // #include <stdio.h> // for printf
                  // #include <stdlib.h> // for free
                  // static void myprint(char *fmt, char* s) {
                  //   printf(fmt, s);
                  // }
                  import "C"
                  import (
                  func main() {
                  	fmt.Print("Hello ")
                  	cstrFormat := C.CString("%s\n")
                  	defer C.free(unsafe.Pointer(cstrFormat))
                  	cstrArg := C.CString("Crustaceans")
                  	defer C.free(unsafe.Pointer(cstrArg))
                  	C.myprint(cstrFormat, cstrArg)

                  It works when building with go build.

                  1. 2

                    You need to follow the steps as described in the blog post. Until Go 1.17 is out you will need to wrap the Zig invocation in a bash script.

                    1. 1

                      I tried that as well, and it did not work either.

                      When creating the two wrapper scripts zcc and zxx and running CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC="zcc" CXX="zxx" go build --tags extended I get:

                      cgo: exec /missing-cc: fork/exec /missing-cc: no such file or directory

                      When adding $PWD and running:

                      CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC="$PWD/zcc" CXX="$PWD/zxx" go build --tags extended

                      it just freezes and nothing happens.

                      Edit: The second command with $PWD works if I wait long enough! The only output is:

                      warning: unsupported linker arg: --compress-debug-sections=zlib-gnu

                      But it produces a working executable. Excellent!

                      1. 3

                        Good job! LLVM is not the fastest compiler out there :) In the blog post I mentioned that compiling Hugo with C extensions takes 4 mins. Without the extensions, it takes like 20 seconds.

                        We’re working on adding support for more flags as people use the feature and report issues, in any case those are just warnings that don’t impact the final executable.

                        1. 1

                          Thanks for the excellent blog post! I dream of one day combining Go and Zig seamlessly, so that Zig can do the performance-critical parts and Go can handle the network-related parts.

                  2. [Comment from banned user removed]

                    1. 33

                      There are more details on why and how this works here: zig cc: a Powerful Drop-In Replacement for GCC/Clang

                      The other full time C/C++ compiler engineers are not idiots; they just have different goals since they work for companies trying to turn a profit by exploiting open source software rather than working for a non-profit just trying to make things nice for everyone.

                      1. 7

                        The other full time C/C++ compiler engineers are not idiots; they just have different goals since they work for companies trying to turn a profit by exploiting open source software rather than working for a non-profit just trying to make things nice for everyone.

                        This feels like a big statement, and that’s fine, but would you mind elaborating? Which companies do you mean? What goals do they have that are incompatible with something like zig cc?

                        1. 6

                          I think the point there was just that e.g. Apple has no particular interest in making using clang to cross-compile Windows binaries easy. They wouldn’t necessarily be against it, but it’s not something that aligns with their business interests whatsoever, so they’re very unlikely to spend any money on it. (Microsoft actually does value cross-compilation very highly, and has been doing some stuff in that area with clang, and so is almost a counterexample. But even there, they focus on cross-compilation in the context of Visual Studio, in which case, improving the CLI UI of clang again does not actually do anything for them.)

                      2. 40

                        Am I the only one who is concerned by the complexity these party-trick features seem to involve?

                        (The other option is that this stuff is really that easy and all the hundreds of full-time C/C++ compiler engineers are just idiots for not doing it.)

                        This mindset is one of the major causes why modern software sucks so much. The amount of tools that can be improved is humongous and this learned helplessness is why we keep having +N layer solutions to problems that would require re-thinking the existing toolchains.

                        I encourage you to read the Handmade Manifesto and to dive deeper into how Zig works. Maybe you’re right, maybe this is a party trick, but the reality is that you don’t know (otherwise you would take issue with specific approaches Zig employs) and you’re just choosing the safe approach of reinforcing your understanding of the status quo.

                        Yes, there are a lot of snake oil sellers out there, but software is not a solved problem and blanket statements like this one are frankly not helping anybody.

                        1. 1

                          I think you are wrong and the exact opposite is the case:

                          We can’t have nice things because people don’t learn from their predecessors.

                          Instead they go out to reinvent flashy new stuff and make grandiose claims until it turns out they ignored the inconvenient last 20% of work that would make their new code reliable and complete – oh, and their stuff takes 200% more resources for no good reason.

                          So yeah, if people don’t want to get suspected of selling snake oil, then they need to be straight-forward and transparent, instead of having these self-congratulatory blog articles.

                          Build trust by telling me what doesn’t work, and what will never work.

                          1. 17

                            Here’s what doesn’t work https://github.com/ziglang/zig/labels/zig%20cc.

                        2. 7

                          Clang could provide the same trivial cross compilation if it were a priority. Zig is mostly just using existing clang/llvm features and packaging them up in a way that is easier for the end user.

                          1. 21


                            1. 4

                              Perhaps not obvious, but I meant the “just” to be restricted to “mostly just using existing clang/llvm features”. I’m in no way denegrating Andrew’s efforts or the value of good UX.

                          2. 5

                            Another option is that it’s easy if you build it in at the start and much more difficult to add it later. It’s like the python 2 to 3 migration. It wasn’t worth it for some projects, but creating a new python 3 project is easy. Path dependence is a thing.

                            1. 2

                              I think the hard part is adding these kinds of features after the fact. But assuming it’s already in place, I feel like this is actually not a very hard thing to maintain?

                              I think a lot of complexity with existing tools is around “oh we’re going to have this be global/implicit” and that permeating everywhere, so then when you want to parametrize it you have to play a bunch of tricks or rewrite everything in the stack to get it to work.

                              But if you get it right out of the door, so to speak, or do the legwork with some of the dependencies… then it might just become a parameter passed around at the top level (and the lower levels already had logic to handle this, so they don’t actually change that much).

                              case in point: if you have some translation framework relying on a global, your low-level will read that value and do a lookup, and the high level will not handle it. If you parameterize it, now your high-level stuff has to pass around a bunch of translation state, but the low-level (I guess the hard part, so to speak?) will stay basically the same. At least in theory

                              I do kinda share your skepticism with the whole “let’s rewrite LLVM” thing… but cross compilation? Having a build system that is “just zig code” instead of some separate config lang? These seem good and almost simpler to maintain. I don’t think C compiler engineers are idiots for not doing X, just like… less incentivised to do that, since CMake isn’t a problem for someone who has spent years doing it.

                              1. 2

                                I agree with you. This doesn’t make any sense for Zig to take on. Andrew shared it with me as he was working on it and I thought the same thing then: what? Why does a compiler for one language go to this much trouble to integrate a toolchain for another language? Besides being severely out of scope, the problem space is fraught with pitfalls, for example with managing sysroots and dependencies, maintaining patched forks of libcs, etc. What a huge time sink for a group who should ostensibly have their hands full with, you know, inventing an entire new programming language.

                                The idea of making cross-compilation easier in C and C++ is quite meritous. See Plan 9 for how this was done well back in the naughts. The idea that it should live in the zig build tool, however, is preposterous, and speaks rather ill of the language and its maintainers priorities. To invoke big corporate compiler engineers killing open source as the motivation is… what the fuck?

                                Sorry Andrew. We don’t always see eye to eye, but this one is particularly egregious.

                                1. 7

                                  No, this makes a lot of sense. Going back to the article, Go’s toolchain (like Plan 9’s) is good at cross-compilation, but “I recommend, if you need cgo, to compile natively”. This sort-of works for Go because cgo use is low. But Zig wants to encourage C interoperability. Then, Zig’s toolchain being good at cross-compilation is useless without solving C’s cross-compilation, because most of Zig will fail to cross-compile because of C dependency somewhere. By the way, most of Rust fails to cross-compile because of C dependency somewhere. This is a real problem.

                                  Once you solved the problem, it is just a good etiquette to expose it as CLI, aka zig cc, so that others can use it. The article gives an example of Go using it, and mentions Rust using it in passing.

                                  I mean, yes, zig cc should be a separate project collaboratively maintained by Go, Rust, and Zig developers. Humanity is bad at coordination. Big companies are part of that problem. Do you disagree?

                                  1. 2

                                    The best way, in my opinion, to achieve good C interop is by leveraging the tools of the C ecosystem correctly. Use the system linker, identify dependencies with pkg-config, link to system libraries, and so on. Be prepared to use sysroots for cross-compiling, and unafraid to meet the system where it’s at to do so. Pulling the concerns of the system into zig - libc, the C toolchain, statically building and linking to dependencies - is pulling a lot of scope into zig which really has no right to be there. Is the state of the art for cross-compiling C programs any good? Well, no, not really. But that doesn’t mean that those problems can jump domains into Zig’s scope.

                                    I am a believer that your dependency’s problems are your problems. But that definitely doesn’t mean that the solution should be implemented in your domain. If you don’t like the C ecosystem’s approach to cross compiling, and you want to interoperate with the C ecosystem, the correct solution involves going to the C ecosystem and improve it there, not to pull the responsibilities of the C ecosystem into your domain.

                                    Yes, other languages - Go, Rust, etc - should also be interested in this effort, and should work together. And yes, humanity is bad at cooperation, and yes, companies are part of that problem - but applying it here doesn’t make sense. It’s as if I were talking about poaching as contributing to mass extinction, and climate change for also contributing to mass extinction, and large corporations for contributing to climate change, and then conclude that large corporations are responsible for poaching.

                                    1. 3

                                      There’s another path to achieving C interop, which is by using whatever feels more convenient but staying true to whatever ABI boundaries. In terms of Zig, this is achieved in a few ways: It uses its own linker (currently LLD) which is useful when you don’t have a local system linker (pure linux/windows install) and still works with existing C code out there. It uses paths for dependencies, leaving it up to the user to specify how they’re found (e.g. pkg-config). It links to system libraries only if told explicitly but still works without them - this is also useful when building statically linked binaries which still work with existing C code.

                                      For cross-compiling, sysroot is a GCC concept. This doesn’t apply to other environments like clang (the C compiler Zig uses), or the defaults of Mac/Windows. Zig instead uses LLVM to emit any supported machine code (something which requires having multiple compilers for in GCC), bundled the build environment needed (lib files on windows, static libc on linux if specified, nothing if dynamically linking), and finally links them together to the appropriate output using LLD’s cross-linking ability.

                                      Having this all work seamlessly from whatever supported system is what makes it appealing. For example, andrew (creator of Zig) has showcased in the past cross-compiling the compiler on an x86 machine to aarch64, then using qemu to cross-compile the compiler again from the aarch64 vm back to x86, and it works. This applies also to other operating systems, which is a feature that isn’t present in current cross compiling tools, even clang.

                                      For the issue of problem domains, this is not something you could address by trying to fix existing C tools. Those already have a defined structure as andrew noted above given they have different goals and are unlikely to change it. This could be why Zig takes upon solving these problems locally, and pulls the responsibility of what it wishes to provide, not the entire C ecosystem. I believe its partially of similar sub-reasons why Go has its own build system but also claims to compile to different environments.

                                      I also agree that different ecosystems could pitch in for what seems to be a universally helpful tool, but as its been going on today, maybe they have different design goals. Where another path such as using the existing C ecosystem (for various situational reasons) makes more sense than the idealistic one Zig has chose to burden.

                                      1. 1

                                        It links to system libraries only if told explicitly but still works without them - this is also useful when building statically linked binaries which still work with existing C code.

                                        System libraries can also be static libraries, and there’s lots of reasons to link to them instead. We do build statically linked programs without the Zig tools, you know!

                                        For cross-compiling, sysroot is a GCC concept. This doesn’t apply to other environments like clang

                                        Clang definitely uses sysroots. Where does it find the static libs you were referring to? Or their headers? The answer is in a sysroot. Zig may manage the sysroot, but it’s a sysroot all the same.

                                        There’s more to take apart here, but on the whole this is a pretty bad take which seems to come from a lack of understanding about how Linux distributions (and other Unicies, save for macOS perhaps) work. That ignorance also, I think, drove the design of this tool in the first place, and imbued it with frustrating limitations which are nigh-unsolvable as a consequence of its design.

                                        1. 3

                                          The explicitly provided system libraries is not about dynamic vs static linking, its about linking them at all. Even if you have the option to statically link libc, you may not want to given you can do its job sometimes better for your use case on platforms that don’t require it (e.g. linux). The closest alternative for C land seems to be -ffreestanding (correct me if i’m wrong)? This is also an option in zig, but it also gives the option to compile for platforms without having to link to any normal platform libraries.

                                          Clang has the option to use sysroots, but it doesn’t seem to be required. In zig’s case, it uses whatever static libs you need by you explicitly linking to them rather than assuming they exist upon a given folder structure in the same directory. Zig does at least provide some methods of finding where they are on the system if you don’t know there they reside given the different configurations out there. I’d say this differs from a sysroot as its more modular than “system library directory”.

                                          Without a proper explanation, the idea that this approach “stems from lack of understanding” or has “frustrating limitations which are nigh-unsolvable” don’t seem make such sense. As we’re both guilty of prejudice here, i’d relate your response to one of willfully ignorant to unknown systems and gate-keeping.

                                          1. 1

                                            Clang has the option to use sysroots, but it doesn’t seem to be required.

                                            Your link does not support your statement. I don’t think you understand how cross-compiling or sysroots actually work.

                                            Again, it’s the same with the rest of your comments. There are basic errors throughout. You have a deep ignorance or misunderstanding of how the C toolchain, linking, and Unix distributions work in practice.

                                            1. 4

                                              Given you haven’t actually rebutted any of my claims yet, nor looked into how clang supports using sysroots, we probably won’t be getting anywhere with this. Hope you’re able to do more than troll in the future.

                                              1. 1

                                                Clang totally uses sysroot, see here. (Long time ago, I wrote the beginning of Clang’s driver code.) I don’t know where to begin, but in fact, ddevault is correct about all technical points and you really are demonstrating your ignorance. Please think about it.

                                                1. 3

                                                  Please re-read by post above which literally says “clang supports using sysroots”, a claim that agrees with yours. My original point a few messages back was about how clang doesn’t need sysroot in order to cross-compile, which still stands to be disproved, as its just short for a bunch of includes.

                                                  Once again, just as ddevault, you enjoy making claims about others without specifying why in an attempt to prove some point or boost your ego. Either ways, if this is your mindset, there’s no point in further discussion with you as well.

                                2. 1

                                  What features in this case?

                                  1. 1