In the near term, the machine code generated by Zig will become less competitive. Long-term, it may catch up or even surpass LLVM and GCC.
Technically true but realistically how confident is the author they can surpass a project backed by the biggest corporations in the world and a project underpinning a few (all?) largest FLOSS efforts?
In the near term it would also reduce the number of targets Zig supports, since LLVM has a nice laundry list. However, LLVM supports fewer targets than you think… we are constantly running into bugs and embarrassing O(N^2) code for pretty much every target except x86, aarch64, and webassembly.
Coincidentally, those are the targets that are are listed a little earlier in the OP as being worked on for Zig. I assume, those are going to be the only targets for a while. LLVM might be not optimal for other platforms but it does support those other platforms. Is going from suboptimal support to no support an upgrade?
We can attract direct contributions from Intel, ARM, RISC-V chip manufacturers, etc., who have a vested interest in making our machine code better on their CPUs.
Again, technically true but how realistic it is? I’m sure all those vested interests already invest in LLVM. Would they want to also invest in Zig? Let’s face it, Zig is pretty small at the moment. Zig would get all the results of LLVM’s improvement by those vested interests but can it effectively divert effort from LLVM or can it effectively make those vested interests make an additional effort to make parallel contributions to Zig?
The benefit list is tempting, true. It’s full of potential. I’m just a little sceptical whether it can be realised to the extent it’s pitched.
I don’t have a horse in this race. I don’t know the language or the community so can’t give an accurate judgement on the announcement. If authors wants to work on the compiler (as opposed to on the language) all power to them. I just don’t see it as a good way to advance the project in the “product” sense. To me it seems like a lot of effort’s going to be spent on reimplementing what’s already been done by someone else.
Given the enormous effort that goes into LLVM I’m a little skeptical about Zig code generation eventually being able to surpass either, too. But I can sort of see where this is coming from.
A few months ago I drew the short stick writing some LLVM-related build and glue code (I was the only contractor who’d ever touched it, and that was in like 2007 – I’m only moderately interested in compiler design and completely uninterested in language design). 15 years is of course a lot of time, so I wasn’t expecting LLVM to be anything like what I remembered it, but holy crap. Bootstrapping is quite a goat carnage, and you have to do it, because it moves pretty quickly and regressions are pretty abundant, so your distro’s/Homebrew’s/whatever packages tend to be useless if you want to develop against it, you start getting into weird bugs as soon as you do something non-trivial.
It’s not bad, I’m admitedly entirely uneducated when it comes to language tooling but as far as I can tell it’s an extraordinarily good piece of software, and the bug churn is inherent to its massive scope and the extremely difficult problem that it solves. However, like most corporate-backed projects, it seems to have evolved to the point where it’s also difficult to use if you don’t have corporate-level resources to throw at it.
Looking through the bug reports in Zig’s bug tracker it looks like they spend an uncanny amount of time fighting packaging problems or upstream bugs. Maybe they think they’re just too small for a tool this big.
skeptical about Zig code generation eventually being able to surpass either
Maybe it doesn’t have to?
There is increasing evidence that optimising compilers hit diminishing returns quite a while ago. See Proebsting’s Law, which states that compiler advances double computing power every 18 years, compared to the 18 months for hardware advances. That might seem depressing, but it was actually wildly optimistic, the current rate seems to be a doubling for every 50 years, with the interval increasing. And at a cost of a much larger increase in compile times.
The Oberon optimising compiler is 6 source files, LLVM is what, a million lines of code?
For my own Objective-S, I made three attempts with LLVM, and each time gave up, because the cost of using it was just too high. Last summer I broke down and decided to just do my own native compiler. Are there times when I am struggling with some aspect of code-generation or object-file layout that I regret this choice? Absolutely! But overall, both progress and developer happiness are much better.
The efficacy of compiler optimisations follows a pareto distribution (see e.g.). Llvm’s investors really need (or, at least, think they need) those extra few single-digit %; if you don’t, you may be able to get away with something much smaller that still works pretty well.
Llvm is not the be-all and end-all of compilers—it is very much a product of its time, and taking advantage of developments since its creation allow the creation of a compiler generating code of a given degree of quality for appreciably less effort and code. Redundancies in its ir mean that optimisations may, to a great extent, be brute-forced where a more unified mechanism would serve better. The example I always use is this. It is not per se a problem that the code for g is suboptimal—getting that right involves annoying cost models that may not generalise from a three-line function. But it is a problem that the compiler is able to tell the difference between f and g (and note llvm and gcc both fall down in exactly the same way, and for the same reason).
Are you just referring to the fact that it ends up emitting two function bodies instead of one?
No—that part is trivial. The problem is not the relation of different functions, only the analysis of a single function.
In general, it’s desirable for a compiler intermediate representation to canonicalise, in order to eliminate redundancies—it’s undesirable to have two ways of expressing the same thing. One trivial example is addition and negation vs subtraction; if the ir includes addition, subtraction, and negation, then there is a redundancy: do you write x-y or x+(-y)? The problem with this is that a lot of optimisations have to be written twice—once, to apply to x-y, and once again to apply to x+(-y). The solution is to not have subtraction in the intermediate representation; a source-level x-y is always lowered to x+(-y) for optimisation purposes. (We can say that x+(-y) is the canonical form of a subtraction.) Hence, the optimiser is utterly unable to tell whether the user wrote x-y or x+(-y), and so it does not need to care.
Needing to express an optimisation twice—once for addition, and once for subtraction—is what I mean by ‘brute forcing’; you’re spending less time looking for patterns and underlying structure, and more time just throwing rules at the wall.
Llvm ir doesn’t have subtraction—that is, again, a fairly trivial example, and is not hard to fix retroactively. But its intermediate representation (like gcc’s) carries around redundant information about sequencing, instead of including only as much sequencing information as is necessary to preserve the program semantics, and this is much harder to fix ex post facto. The result of this is that f and g have distinct ir. And this comes through in the final generated code, because the compiler did not go to extra effort to account for it. (Even if it had done so, it would not change the fact that the redundancy in the ir is there, and forces additional redundancy and complexity into the compiler; but the fact that it has not makes it an easy way to demonstrate the deficiency.)
One trivial example is addition and negation vs subtraction
In practice this can be handled by having a pass looking for the x+(-y) pattern and replacing occurrences with x-y (or the other way around), effectively giving you a canonical form. I’m not sure that I buy the idea that not having subtraction in the IR is really a win, when it’s so simple to remove it anyway, but sure, this is just another way of achieving the same goal.
However I’m dubious about the (potential) ability for semantically equivalent code to be canonicalised to an identical IR. In your simple example the sequencing isn’t important and so it’s true that the IR need not carry the sequencing of the call vs the arithmetic operation, but of course there are plenty of cases where A really must happen before B, and some where the compiler can’t easily (or can’t at all) determine that sequence must be preserved (consider the simple case of storing through two different pointer variables: if the pointers can alias, the order of the stores must be preserved, otherwise, in the absence of volatile, it’s not necessary. But determining whether pointers can alias can be undecidable).
Of course you can’t canonicalise all semantically equivalent functions to the same IR—that would violate rice’s theorem. I’m not arguing that you should be able to. Only that the irs of llvm and gcc carry around a shitload of redundant sequencing information (among other things) and it holds them back considerably.
E:
this can be handled by having a pass looking for the x+(-y) pattern and replacing occurrences with x-y (or the other way around)
I guess. I don’t think it makes much difference (only be able to represent one form vs only have one form that you ever use), except that the former seems much nicer.
The forms of redundancy found in llvm and gcc’s irs are more interesting, and you will likely find them harder to treat that way. Compare block-oriented cfg vs sea of nodes.
Bootstrapping is quite a goat carnage, and you have to do it, because it moves pretty quickly and regressions are pretty abundant, so your distro’s/Homebrew’s/whatever packages tend to be useless if you want to develop against it, you start getting into weird bugs as soon as you do something non-trivial.
That’s not been my experience in recent releases (after about LLVM 8). I’ve maintained out of tree things that had a small abstraction layer to paper over differences and were able to build against 3-4 LLVM releases. The opaque pointer work has taken a long time to and and was a big change, but it was also supported with a very long (multi-year) deprecation period where you could build code that was conditional on whether the current version was built with opaque pointers or not.
I don’t doubt it, LLVM is large and covers a lot of architectures – it’s very likely that I’ve only touched some weird part. Every good project has one of these weird swamps. Thing is, if you have to deal with the one or two things it does… not even badly, just not quite the way you need it, it can seem like it’s not worth it, especially if you rely on indie developers working in their spare time a lot.
My post wasn’t intended as a jab at LLVM – it’s a big project and whatever unwieldiness it has is likely unevenly spread, and not just inherent to its size and the difficulty it solves, but further compounded by my being an amateur in its field. I rather wanted to point out that, if you’re not specifically interested in writing toolchains with features LLVM is particularly good at, or with features that are particularly important for LLVM (or, in the extreme case, you’re not that interested in writing toolchains, like me), this may seem like a far more reasonable trade-off than it looks at first.
I think the biggest reason to be optimistic is the Zig C backend, so we’re not really losing any platform support. Granted, I don’t know how well it works in practice and what the performance is like compared to the LLVM backend.
I don’t agree. LLVM is a huge value add for something like Rust or C++ where having a hyper optimizing backend helps make some of the much higher level abstractions zero cost. But Zig is a relatively simple language, like Go, which also has its own code generation backend. Go’s code generation is much less sophisticated than LLVM but Go is still considered a high performance language.
Go is considered high performance by people comparing it to Python, it is much slower than C++ for a lot of things. The go front end for GCC was much faster for single threaded execution but used one pthread per goroutine, so ended up being slower overall. I don’t know if this was ever fixed.
Zig author and the small team that he built around the language seems quite capable.
I would like to see them go into the direction of having their own IRL and an optimizing backend.
The approach that they have been taking with everything (at least in my undestanading)
is
a) give better language and better tooling for multi-OS and multi-CPU targets
b) consider developer experience as a whole (programming, debugging, managing build systems, using external dependencies)
c) Recognize that operating systems and their user-land revolves around C
d) Carefully consider build-times and incremental build times as part of developer experience (smaller is better)
I think they are seeing also that ( d ) may not achievable with LLVM backend – because LLVMs goals would drive it to tradeoff small optimization wins for a larger compile/link time and less exhaustive testing for non-tier1-platforms.
So all in all, for the new CPUs, new or less serviced OSes, a Zig-supported backend may be a win.
Which in turn result in more competition with the established vendors (OS and CPU wise). yes there will be more fragmentation, but I hope it will be good overall outcome.
Again, technically true but how realistic it is? I’m sure all those vested interests already invest in LLVM.
Quite common for microcontrollers to have a vendor supplied c compiler. If the license suits, it’s not inconceivable they might base it on the zig c compiler.
My, the entire GitHub issue is full of people using the C++ toolchain. It feels like Andrew made a language, and is now discovering that most of his users (or at least his most vocal users) actually came for the toolchain.
I genuinely thought this was a well understood thing. AFAIK, the most significant “corporate” adoption Zig has found so far has been Uber who exclusively use the toolchain and not the language. Most news I hear about Zig from outside of that ecosystem is about their linker zld or some big improvement to zig cc, rarely the language.
Perhaps a clarification is needed then. To me it looks like the respective goals of the project and its users aren’t quite aligned.
I personally was a bit surprised C++ compilation is so important. Especially since interfacing to it is such a pain. The Zig FFI is only to C, right? So those C++ users have to make a C API anyway. From the outside, it makes sense to me that C++ compilation is just an ancillary benefit of the Zig toolchain.
Also, users who consider quitting Zig over this change may be users the project doesn’t even want at this relatively early stage (where “early” <= 1.0): those who value the toolchain more than the language are using short term considerations, and building a language (I think it’s obvious to anyone that’s Zig’s main goal) is a long term thing. It’s not for me to judge, but perhaps the best course of action is to go through with that plan, bite the short term drawbacks, and accept the loss of users and donations.
What’s really disheartening though is that Andrew built a language, and so many people cared more about the toolchain. It’s like remembering Leslie Lamport for LaTeX instead of his work on concurrency and formal methods.
To me it looks like the respective goals of the project and its users aren’t quite aligned.
Yes, this reminds me of the situation with ksmbd, which Samsung developed to support SMB Direct, but approximately all users simply want to avoid Samba’s GPLv3 license.
The language is still unstable, it’s expected that corpos don’t invest into using it yet. Maybe they won’t even once it gets stable, but the current situation tells us nothing in that regard.
I agree the situation is expected, but it could have been the case that Zig is so attractive companies start to use Zig even if it is unstable. That didn’t happen, so it tells us something. A small something, but not nothing.
(Unexpectedly, Dropbox started to use Rust even if it is unstable, because Rust was so attractive to their goals.)
but it could have been the case that Zig is so attractive companies start to use Zig even if it is unstable. That didn’t happen, so it tells us something.
Zig cc doesn’t do anything particularly magic. It uses support that’s already in clang, it just provides the contents of the sysroot directory and sets the –sysroot and -target flags for you. Companies that care about this typically already provide configured versions for the targets that they care about. For example, XCode can do this for multiple macOS / iOS versions.
As with other similar efforts, zig cc is limited by licenses. If you want to cross-compile for Windows, I believe the visual studio redistributables allow you to install their headers and libraries on any system for the purpose of cross-compiling (I believe the person doing the compilation is required to accept the license, not sure how Zig does this), so it’s ‘just’ a matter of getting the right files. Apple explicitly prohibits this. Zig works around that by building their own sysroot from Apple open source bits, which hits a brick wall as soon as you want to use anything not in the open source repos (e.g. any of the GUI frameworks). Building a sysroot for most *NIX systems is fairly easy and most companies that want to cross compile for multiple *NIX variants build their own.
now discovering that most of his users (or at least his most vocal users) actually came for the toolchain
I’ve heard people say the same thing about Go, that they aren’t there for the “language,” but are there for the toolchain and the experience it creates.
zig cc basically completely takes care of cross-compilation for any target. It’s pretty much a clang distribution that can cross-compile to whatever target you want without having to do any special trickery. It’s a drop-in replacement for gcc/clang, too, and is marked as stable, which is why you’ll see e.g. Uber using it.
It’s really quite nice and am honestly surprised something like this was never made officially by the clang/LLVM project.
It’s really quite nice and am honestly surprised something like this was never made officially by the clang/LLVM project.
It did. That’s what zig cc uses. Clang supports a number of flags that make this trivial:
--sysroot specifies the root to look for headers when compiling, libraries when linking.
-B lets you specify an alternate path to search for tools, so you can drop in an alternative linker, assembler, or whatever if your target needs one.
-target lets you specify the target triple.
The only thing that you need to do is provide a sysroot.
For an open-source *NIX system, that’s trivial. If you have an install you can just copy the relevant files out of it. If you want to target FreeBSD, for example, if you just extract base.txz from any release, you have a sysroot that works for targeting that release and architecture. It’s a bit more complex if you want libraries from packages, but they’re usually tarballs + metadata and you can just extract them into your sysroot.
It’s harder for proprietary platforms because you don’t have unlimited redistribution rights to their include files and libraries (or library stubs for dynamic linking). A lot of the Windows things can be extracted from the Visual Studio redistributables (though you must accept the license before doing this). On macOS / iOS, the T&Cs explicitly prohibit using any of their parts to compile from non-Apple platforms. Zig builds a macOS sysroot out of the open source Darwin bits, but that means you can’t use any headers or link any libraries that are part of the proprietary components, which includes all of the GUI frameworks and most system services.
That’s true, clang is a lot better in this regard than gcc. I still find it completely insane that gcc requires a fully separately-built toolchain to do any kind of cross-compilation (at least it did when I looked into it last year)…
Still, zig cc definitely hits the spot when it comes to the convenience - no need to deal with sysroots or alternative toolchains. Just specify the target and it figures it out.
I still find it completely insane that gcc requires a fully separately-built toolchain to do any kind of cross-compilation (at least it did when I looked into it last year)…
I think they’re working on it. At least gdb can now be built with support for multiple targets. It makes sense when you consider the history. GCC started at a time when memory was measured in single-digit megabytes and disks were typically double-digit megabytes. Cross compilation was rare and so there was absolutely no desire to make compile times longer (the first time I compiled GCC, it was an overnight job, and that was quite a few years later) and add disk space requirements for a feature that very few people needed. Target selection was done with #ifdef, which meant that you weren’t paying any indirection cost at run time (which would have slowed things down).
In contrast, when clang came along, computers were already pretty fast. I started doing LLVM dev on 1 GHz machines with >1 GiB of RAM (I did a lot on a 1.2 GHz Celeron M, which took about an hour and a half for a clean build). The overhead from indirection in the per-platform or per-CPU bits was tiny and compiling all targets didn’t add a huge amount of time (10-20%), and being able to test all targets from a single was considered a benefit that outweighed all of the costs. In particular, few people are expected to build LLVM from source (the GNU project expected everyone to build GCC from source, early on, because that was how you distributed it), only developers and distributors. Developers benefit from taking 20% longer to build a single binary that can compile for anything rather than having to build 10 different binaries.
One of the problems with big software projects in general is that they must be built with the constraints that are present when they are started and typically grow to be useful and widely supported by the time that those constraints have changed. For example, LLVM began very early in the multicore era and so none of the core data structures were designed for concurrency. If you modify any function that refers to a global, you must modify that global’s use-def chain, which means that you can’t do that concurrently with anything else in the same module. There’s been a lot of work on ThinLTO to try to bring parallelism back, but building this from the start would give you something very different. And, by the time you’d finished, people would complain that it didn’t use your quantum AI coprocessor, which could do compilation much faster.
Still, zig cc definitely hits the spot when it comes to the convenience - no need to deal with sysroots or alternative toolchains. Just specify the target and it figures it out.
This works only because zig cc provides packaged toolchains for some platforms. If you want to target anything else (e.g. macOS with Cocoa), you’re in exactly the same place you are with clang.
Wait, cross compilation is still hard? What year is it?
I would like a meme for this; you wonder what the problem is and it turns out there is a much bigger problem. As if, you wonder why the car doesn’t start and then realize it doesn’t have an engine.
In addition, zig packages stand out for being really easy to compile, because they take advantage of the built-in C++/Objective-C support in the compiler. Rather than shelling out to any subprocess, the zig community has been porting projects over to use build.zig instead, so cmake doesn’t need to be installed.
There are also may PRs that members of the zig community have made to (such as this one) to add build.zig as an alternative build system for open source projects. All of that work goes away for non-C projects.
Reading the thread here and and the GH issue, I think it’d be kind to point out how hard developing both a language and the surrounding tooling is, how much effort it takes supporting other ecosystems with many flavors of rabid braindamage is, and how much value the Zig team has created.
I don’t know the specifics, but I’m willing to wager they aren’t getting paid super well for this compared to what they could make sitting on a team and attending meetings for 30 hours a week and pulling a fat GOOG/MSFT/AAPL paycheck (perhaps I’m wrong in this, maybe that’s what @andrewrk already does, but who knows?).
Open source has a huge free-rider problem, based on my experience as a contributor and maintainer on other projects, it can be incredibly depressing to get endless reports from people who are making a buck off of your work threatening to leave because you are pushing a project you care about in the direction you intend and suddenly they feel they are no longer being well served–especially if they haven’t exactly been footing the bill.
Anyways, just wanted to flag this as an opportunity to maybe treat these Zig core folks with a little grace and maybe be excited to see what the next iteration of the project is. If dropping support for C++ and associated tooling frees them up to speed compilation, improve docs, and foster a community doing actual rewrites of things from C/C++ instead of wrapping them, I think that’s a winning outcome too.
Ha! I “know” him and had lunch with him a few months ago, so knew he still wasn’t in big tech. But aside from that, he’s posted about his finances on his blog if you’re interested.
This is excellent news! I like to be able to build my toolchains in a reasonable amount of time and that’s a no-go with anything LLVM. One of my favorite things about Go is that the toolchain has minimal external dependencies and still builds well under 2 minutes. It used to be under 1 minute but the LOC has more than doubled since and I will take 90 seconds over 5 hours.
I am hopeful that the eventual drop of llvm means the OpenBSD port can track the current language easier. As of now, I’m not a zig hobbyist user for this reason, but I’d like to be!
I have never used Zig and yet even I know that a lot of people use it for its very convenient cross-compiling facilities.
I admire ambitious people but at this point in the software engineering history I believe it should be obvious for everyone that improving compilation times and quality is one of the hardest tasks ever and it might easily take the creator and the team their entire careers to even make a dent… and actually succeeding and gaining wide adoption is not at all guaranteed.
I fear this will just doom the project to irrelevance. And it’s not like there are more than two people actually paid to work on Zig.
To me, this is all the more reason to hope that they go forward with it. The team has already proven that they have the expertise and perseverance to solve hard problems. This feels very aligned with their overall goals.
For the divorce to be a proper metaphor in this situation the other party (LLVM) should also be able to initiate the process. Which would have been interesting: Zig changed its mind but LLVM decided to proceed anyway because it felt betrayed.
Divorce metaphor seems appropriate if you think of this as not Zig divorcing LLVM but Zig language developers divorcing Zig toolchain users who use its C++ support. Even if Zig doesn’t decide to do this, Zig toolchain users may feel betrayed and decide to fork.
This seems counterproductive.
Technically true but realistically how confident is the author they can surpass a project backed by the biggest corporations in the world and a project underpinning a few (all?) largest FLOSS efforts?
Coincidentally, those are the targets that are are listed a little earlier in the OP as being worked on for Zig. I assume, those are going to be the only targets for a while. LLVM might be not optimal for other platforms but it does support those other platforms. Is going from suboptimal support to no support an upgrade?
Again, technically true but how realistic it is? I’m sure all those vested interests already invest in LLVM. Would they want to also invest in Zig? Let’s face it, Zig is pretty small at the moment. Zig would get all the results of LLVM’s improvement by those vested interests but can it effectively divert effort from LLVM or can it effectively make those vested interests make an additional effort to make parallel contributions to Zig?
The benefit list is tempting, true. It’s full of potential. I’m just a little sceptical whether it can be realised to the extent it’s pitched.
I don’t have a horse in this race. I don’t know the language or the community so can’t give an accurate judgement on the announcement. If authors wants to work on the compiler (as opposed to on the language) all power to them. I just don’t see it as a good way to advance the project in the “product” sense. To me it seems like a lot of effort’s going to be spent on reimplementing what’s already been done by someone else.
Given the enormous effort that goes into LLVM I’m a little skeptical about Zig code generation eventually being able to surpass either, too. But I can sort of see where this is coming from.
A few months ago I drew the short stick writing some LLVM-related build and glue code (I was the only contractor who’d ever touched it, and that was in like 2007 – I’m only moderately interested in compiler design and completely uninterested in language design). 15 years is of course a lot of time, so I wasn’t expecting LLVM to be anything like what I remembered it, but holy crap. Bootstrapping is quite a goat carnage, and you have to do it, because it moves pretty quickly and regressions are pretty abundant, so your distro’s/Homebrew’s/whatever packages tend to be useless if you want to develop against it, you start getting into weird bugs as soon as you do something non-trivial.
It’s not bad, I’m admitedly entirely uneducated when it comes to language tooling but as far as I can tell it’s an extraordinarily good piece of software, and the bug churn is inherent to its massive scope and the extremely difficult problem that it solves. However, like most corporate-backed projects, it seems to have evolved to the point where it’s also difficult to use if you don’t have corporate-level resources to throw at it.
Looking through the bug reports in Zig’s bug tracker it looks like they spend an uncanny amount of time fighting packaging problems or upstream bugs. Maybe they think they’re just too small for a tool this big.
Maybe it doesn’t have to?
There is increasing evidence that optimising compilers hit diminishing returns quite a while ago. See Proebsting’s Law, which states that compiler advances double computing power every 18 years, compared to the 18 months for hardware advances. That might seem depressing, but it was actually wildly optimistic, the current rate seems to be a doubling for every 50 years, with the interval increasing. And at a cost of a much larger increase in compile times.
See also The Death of Optimising Compilers by Daniel Bernstein of cryptography and Qmail fame. Frances Allen got all the good ones.
The Oberon optimising compiler is 6 source files, LLVM is what, a million lines of code?
For my own Objective-S, I made three attempts with LLVM, and each time gave up, because the cost of using it was just too high. Last summer I broke down and decided to just do my own native compiler. Are there times when I am struggling with some aspect of code-generation or object-file layout that I regret this choice? Absolutely! But overall, both progress and developer happiness are much better.
Maybe not. I was merely commenting of what is in the OP. I assume that is their ambition based on what they said in the OP alone.
For your project, did you try QBE? It’s supposed to be simpler but performant. Here’s a short intro (pdf).
A couple of points:
The efficacy of compiler optimisations follows a pareto distribution (see e.g.). Llvm’s investors really need (or, at least, think they need) those extra few single-digit %; if you don’t, you may be able to get away with something much smaller that still works pretty well.
Llvm is not the be-all and end-all of compilers—it is very much a product of its time, and taking advantage of developments since its creation allow the creation of a compiler generating code of a given degree of quality for appreciably less effort and code. Redundancies in its ir mean that optimisations may, to a great extent, be brute-forced where a more unified mechanism would serve better. The example I always use is this. It is not per se a problem that the code for g is suboptimal—getting that right involves annoying cost models that may not generalise from a three-line function. But it is a problem that the compiler is able to tell the difference between f and g (and note llvm and gcc both fall down in exactly the same way, and for the same reason).
That is an interesting example, but I’m having a little trouble grokking this:
Did you mean that it is a problem that the compiler is not able to tell that they are semantically the same?
Why is it a problem? Are you just referring to the fact that it ends up emitting two function bodies instead of one?
No—that part is trivial. The problem is not the relation of different functions, only the analysis of a single function.
In general, it’s desirable for a compiler intermediate representation to canonicalise, in order to eliminate redundancies—it’s undesirable to have two ways of expressing the same thing. One trivial example is addition and negation vs subtraction; if the ir includes addition, subtraction, and negation, then there is a redundancy: do you write x-y or x+(-y)? The problem with this is that a lot of optimisations have to be written twice—once, to apply to x-y, and once again to apply to x+(-y). The solution is to not have subtraction in the intermediate representation; a source-level x-y is always lowered to x+(-y) for optimisation purposes. (We can say that x+(-y) is the canonical form of a subtraction.) Hence, the optimiser is utterly unable to tell whether the user wrote x-y or x+(-y), and so it does not need to care.
Needing to express an optimisation twice—once for addition, and once for subtraction—is what I mean by ‘brute forcing’; you’re spending less time looking for patterns and underlying structure, and more time just throwing rules at the wall.
Llvm ir doesn’t have subtraction—that is, again, a fairly trivial example, and is not hard to fix retroactively. But its intermediate representation (like gcc’s) carries around redundant information about sequencing, instead of including only as much sequencing information as is necessary to preserve the program semantics, and this is much harder to fix ex post facto. The result of this is that f and g have distinct ir. And this comes through in the final generated code, because the compiler did not go to extra effort to account for it. (Even if it had done so, it would not change the fact that the redundancy in the ir is there, and forces additional redundancy and complexity into the compiler; but the fact that it has not makes it an easy way to demonstrate the deficiency.)
In practice this can be handled by having a pass looking for the x+(-y) pattern and replacing occurrences with x-y (or the other way around), effectively giving you a canonical form. I’m not sure that I buy the idea that not having subtraction in the IR is really a win, when it’s so simple to remove it anyway, but sure, this is just another way of achieving the same goal.
However I’m dubious about the (potential) ability for semantically equivalent code to be canonicalised to an identical IR. In your simple example the sequencing isn’t important and so it’s true that the IR need not carry the sequencing of the call vs the arithmetic operation, but of course there are plenty of cases where A really must happen before B, and some where the compiler can’t easily (or can’t at all) determine that sequence must be preserved (consider the simple case of storing through two different pointer variables: if the pointers can alias, the order of the stores must be preserved, otherwise, in the absence of volatile, it’s not necessary. But determining whether pointers can alias can be undecidable).
Still, it’s an interesting example.
Of course you can’t canonicalise all semantically equivalent functions to the same IR—that would violate rice’s theorem. I’m not arguing that you should be able to. Only that the irs of llvm and gcc carry around a shitload of redundant sequencing information (among other things) and it holds them back considerably.
E:
I guess. I don’t think it makes much difference (only be able to represent one form vs only have one form that you ever use), except that the former seems much nicer.
The forms of redundancy found in llvm and gcc’s irs are more interesting, and you will likely find them harder to treat that way. Compare block-oriented cfg vs sea of nodes.
[Comment removed by author]
That’s not been my experience in recent releases (after about LLVM 8). I’ve maintained out of tree things that had a small abstraction layer to paper over differences and were able to build against 3-4 LLVM releases. The opaque pointer work has taken a long time to and and was a big change, but it was also supported with a very long (multi-year) deprecation period where you could build code that was conditional on whether the current version was built with opaque pointers or not.
I don’t doubt it, LLVM is large and covers a lot of architectures – it’s very likely that I’ve only touched some weird part. Every good project has one of these weird swamps. Thing is, if you have to deal with the one or two things it does… not even badly, just not quite the way you need it, it can seem like it’s not worth it, especially if you rely on indie developers working in their spare time a lot.
My post wasn’t intended as a jab at LLVM – it’s a big project and whatever unwieldiness it has is likely unevenly spread, and not just inherent to its size and the difficulty it solves, but further compounded by my being an amateur in its field. I rather wanted to point out that, if you’re not specifically interested in writing toolchains with features LLVM is particularly good at, or with features that are particularly important for LLVM (or, in the extreme case, you’re not that interested in writing toolchains, like me), this may seem like a far more reasonable trade-off than it looks at first.
Fun fact, Crystal currently supports all major LLVM releases from 8 to 16 (!)
I think the biggest reason to be optimistic is the Zig C backend, so we’re not really losing any platform support. Granted, I don’t know how well it works in practice and what the performance is like compared to the LLVM backend.
We use it to bootstrap Zig, so it works at least that well.
The C backend compiles Zig code to C code, so performance is up to the C compiler that you end up using.
I don’t agree. LLVM is a huge value add for something like Rust or C++ where having a hyper optimizing backend helps make some of the much higher level abstractions zero cost. But Zig is a relatively simple language, like Go, which also has its own code generation backend. Go’s code generation is much less sophisticated than LLVM but Go is still considered a high performance language.
Go is considered high performance by people comparing it to Python, it is much slower than C++ for a lot of things. The go front end for GCC was much faster for single threaded execution but used one pthread per goroutine, so ended up being slower overall. I don’t know if this was ever fixed.
I know of exactly 0 people that have ever used this. Everyone I know of uses the default, written in Go, compiler and toolchain.
My favourite Go performance story was it always passing arguments on the stack, even if it could use registers.
Zig author and the small team that he built around the language seems quite capable. I would like to see them go into the direction of having their own IRL and an optimizing backend.
The approach that they have been taking with everything (at least in my undestanading) is
a) give better language and better tooling for multi-OS and multi-CPU targets
b) consider developer experience as a whole (programming, debugging, managing build systems, using external dependencies)
c) Recognize that operating systems and their user-land revolves around C
d) Carefully consider build-times and incremental build times as part of developer experience (smaller is better)
I think they are seeing also that ( d ) may not achievable with LLVM backend – because LLVMs goals would drive it to tradeoff small optimization wins for a larger compile/link time and less exhaustive testing for non-tier1-platforms.
So all in all, for the new CPUs, new or less serviced OSes, a Zig-supported backend may be a win. Which in turn result in more competition with the established vendors (OS and CPU wise). yes there will be more fragmentation, but I hope it will be good overall outcome.
Quite common for microcontrollers to have a vendor supplied c compiler. If the license suits, it’s not inconceivable they might base it on the zig c compiler.
Zig is MIT licensed, so nobody should have any issue with it.
My, the entire GitHub issue is full of people using the C++ toolchain. It feels like Andrew made a language, and is now discovering that most of his users (or at least his most vocal users) actually came for the toolchain.
Huh, not just any users, but some pretty high profile ones like Mitchell Hashimoto, of Hashicorp fame: https://github.com/ziglang/zig/issues/16270#issuecomment-1614062991
I genuinely thought this was a well understood thing. AFAIK, the most significant “corporate” adoption Zig has found so far has been Uber who exclusively use the toolchain and not the language. Most news I hear about Zig from outside of that ecosystem is about their linker zld or some big improvement to
zig cc
, rarely the language.Perhaps a clarification is needed then. To me it looks like the respective goals of the project and its users aren’t quite aligned.
I personally was a bit surprised C++ compilation is so important. Especially since interfacing to it is such a pain. The Zig FFI is only to C, right? So those C++ users have to make a C API anyway. From the outside, it makes sense to me that C++ compilation is just an ancillary benefit of the Zig toolchain.
Also, users who consider quitting Zig over this change may be users the project doesn’t even want at this relatively early stage (where “early” <= 1.0): those who value the toolchain more than the language are using short term considerations, and building a language (I think it’s obvious to anyone that’s Zig’s main goal) is a long term thing. It’s not for me to judge, but perhaps the best course of action is to go through with that plan, bite the short term drawbacks, and accept the loss of users and donations.
What’s really disheartening though is that Andrew built a language, and so many people cared more about the toolchain. It’s like remembering Leslie Lamport for LaTeX instead of his work on concurrency and formal methods.
Yes, this reminds me of the situation with ksmbd, which Samsung developed to support SMB Direct, but approximately all users simply want to avoid Samba’s GPLv3 license.
The language is still unstable, it’s expected that corpos don’t invest into using it yet. Maybe they won’t even once it gets stable, but the current situation tells us nothing in that regard.
I agree the situation is expected, but it could have been the case that Zig is so attractive companies start to use Zig even if it is unstable. That didn’t happen, so it tells us something. A small something, but not nothing.
(Unexpectedly, Dropbox started to use Rust even if it is unstable, because Rust was so attractive to their goals.)
Not while we’re still breaking for loop syntax :^)
TBF, Go is planning to break its loop semantics in version 1.22 next year. :-)
And TBF to Go, it’s mostly fixing it (if what you’re talking about is the
i := i
loopvar hack).Yes.
It is kind of ridiculous zig cc was produced by Zig community and not C++ community. Isn’t C++ community much much bigger?
One possible explanation is that while C++ has much much more compiler consumers than Zig, gap is smaller for compiler producers.
Zig cc doesn’t do anything particularly magic. It uses support that’s already in clang, it just provides the contents of the sysroot directory and sets the –sysroot and -target flags for you. Companies that care about this typically already provide configured versions for the targets that they care about. For example, XCode can do this for multiple macOS / iOS versions.
As with other similar efforts, zig cc is limited by licenses. If you want to cross-compile for Windows, I believe the visual studio redistributables allow you to install their headers and libraries on any system for the purpose of cross-compiling (I believe the person doing the compilation is required to accept the license, not sure how Zig does this), so it’s ‘just’ a matter of getting the right files. Apple explicitly prohibits this. Zig works around that by building their own sysroot from Apple open source bits, which hits a brick wall as soon as you want to use anything not in the open source repos (e.g. any of the GUI frameworks). Building a sysroot for most *NIX systems is fairly easy and most companies that want to cross compile for multiple *NIX variants build their own.
It’s not as simple as you make it sound.
Example 1: https://github.com/ziglang/zig/blob/master/tools/gen_stubs.zig Example 2: https://github.com/ziglang/glibc-abi-tool/
There is a bunch more stuff like this that goes into it.
I’ve heard people say the same thing about Go, that they aren’t there for the “language,” but are there for the toolchain and the experience it creates.
Our experience of technology is meaningful.
What are people using this toolchain for? I would have expected it to be pretty useless unless one also uses the language.
zig cc
basically completely takes care of cross-compilation for any target. It’s pretty much a clang distribution that can cross-compile to whatever target you want without having to do any special trickery. It’s a drop-in replacement for gcc/clang, too, and is marked as stable, which is why you’ll see e.g. Uber using it.It’s really quite nice and am honestly surprised something like this was never made officially by the clang/LLVM project.
It did. That’s what zig cc uses. Clang supports a number of flags that make this trivial:
--sysroot
specifies the root to look for headers when compiling, libraries when linking.-B
lets you specify an alternate path to search for tools, so you can drop in an alternative linker, assembler, or whatever if your target needs one.-target
lets you specify the target triple.The only thing that you need to do is provide a sysroot.
For an open-source *NIX system, that’s trivial. If you have an install you can just copy the relevant files out of it. If you want to target FreeBSD, for example, if you just extract base.txz from any release, you have a sysroot that works for targeting that release and architecture. It’s a bit more complex if you want libraries from packages, but they’re usually tarballs + metadata and you can just extract them into your sysroot.
It’s harder for proprietary platforms because you don’t have unlimited redistribution rights to their include files and libraries (or library stubs for dynamic linking). A lot of the Windows things can be extracted from the Visual Studio redistributables (though you must accept the license before doing this). On macOS / iOS, the T&Cs explicitly prohibit using any of their parts to compile from non-Apple platforms. Zig builds a macOS sysroot out of the open source Darwin bits, but that means you can’t use any headers or link any libraries that are part of the proprietary components, which includes all of the GUI frameworks and most system services.
That’s true, clang is a lot better in this regard than gcc. I still find it completely insane that gcc requires a fully separately-built toolchain to do any kind of cross-compilation (at least it did when I looked into it last year)…
Still,
zig cc
definitely hits the spot when it comes to the convenience - no need to deal with sysroots or alternative toolchains. Just specify the target and it figures it out.I think they’re working on it. At least gdb can now be built with support for multiple targets. It makes sense when you consider the history. GCC started at a time when memory was measured in single-digit megabytes and disks were typically double-digit megabytes. Cross compilation was rare and so there was absolutely no desire to make compile times longer (the first time I compiled GCC, it was an overnight job, and that was quite a few years later) and add disk space requirements for a feature that very few people needed. Target selection was done with
#ifdef
, which meant that you weren’t paying any indirection cost at run time (which would have slowed things down).In contrast, when clang came along, computers were already pretty fast. I started doing LLVM dev on 1 GHz machines with >1 GiB of RAM (I did a lot on a 1.2 GHz Celeron M, which took about an hour and a half for a clean build). The overhead from indirection in the per-platform or per-CPU bits was tiny and compiling all targets didn’t add a huge amount of time (10-20%), and being able to test all targets from a single was considered a benefit that outweighed all of the costs. In particular, few people are expected to build LLVM from source (the GNU project expected everyone to build GCC from source, early on, because that was how you distributed it), only developers and distributors. Developers benefit from taking 20% longer to build a single binary that can compile for anything rather than having to build 10 different binaries.
One of the problems with big software projects in general is that they must be built with the constraints that are present when they are started and typically grow to be useful and widely supported by the time that those constraints have changed. For example, LLVM began very early in the multicore era and so none of the core data structures were designed for concurrency. If you modify any function that refers to a global, you must modify that global’s use-def chain, which means that you can’t do that concurrently with anything else in the same module. There’s been a lot of work on ThinLTO to try to bring parallelism back, but building this from the start would give you something very different. And, by the time you’d finished, people would complain that it didn’t use your quantum AI coprocessor, which could do compilation much faster.
This works only because zig cc provides packaged toolchains for some platforms. If you want to target anything else (e.g. macOS with Cocoa), you’re in exactly the same place you are with clang.
Wait, cross compilation is still hard? What year is it?
I would like a meme for this; you wonder what the problem is and it turns out there is a much bigger problem. As if, you wonder why the car doesn’t start and then realize it doesn’t have an engine.
I burnt several weekends trying to cross-compile my Rust project from macOS ARM to Linux x86 with Nix so yeah… it’s still hard.
Cross compilation and cross linking for C and C++. Build executables for any platform. No other toolchain can do this.
In addition, zig packages stand out for being really easy to compile, because they take advantage of the built-in C++/Objective-C support in the compiler. Rather than shelling out to any subprocess, the zig community has been porting projects over to use
build.zig
instead, socmake
doesn’t need to be installed.There are also may PRs that members of the zig community have made to (such as this one) to add
build.zig
as an alternative build system for open source projects. All of that work goes away for non-C projects.On top of that, Uber has been paying the zig foundation to develop the cross-compiler for C++. Removing the cross-compilation support feels like zig is shooting itself in the foot in terms of adoption.
It probably would make sense to split off the toolchain into its own project then (that calls the Zig compiler on demand)?
Reading the thread here and and the GH issue, I think it’d be kind to point out how hard developing both a language and the surrounding tooling is, how much effort it takes supporting other ecosystems with many flavors of rabid braindamage is, and how much value the Zig team has created.
I don’t know the specifics, but I’m willing to wager they aren’t getting paid super well for this compared to what they could make sitting on a team and attending meetings for 30 hours a week and pulling a fat GOOG/MSFT/AAPL paycheck (perhaps I’m wrong in this, maybe that’s what @andrewrk already does, but who knows?).
Open source has a huge free-rider problem, based on my experience as a contributor and maintainer on other projects, it can be incredibly depressing to get endless reports from people who are making a buck off of your work threatening to leave because you are pushing a project you care about in the direction you intend and suddenly they feel they are no longer being well served–especially if they haven’t exactly been footing the bill.
Anyways, just wanted to flag this as an opportunity to maybe treat these Zig core folks with a little grace and maybe be excited to see what the next iteration of the project is. If dropping support for C++ and associated tooling frees them up to speed compilation, improve docs, and foster a community doing actual rewrites of things from C/C++ instead of wrapping them, I think that’s a winning outcome too.
He does not work for big tech, for a fat paycheck.
Well then there you go.
Ha! I “know” him and had lunch with him a few months ago, so knew he still wasn’t in big tech. But aside from that, he’s posted about his finances on his blog if you’re interested.
Whether or not it’s the right decision, I’ve got to love #6502
As usual this was all a misunderstanding: https://github.com/ziglang/zig/issues/16270#issuecomment-1615388680
and a/the conclusion: https://github.com/ziglang/zig/issues/16270#issuecomment-1616115039
People who use zig cc may enjoy https://github.com/rsms/llvmbox
This is excellent news! I like to be able to build my toolchains in a reasonable amount of time and that’s a no-go with anything LLVM. One of my favorite things about Go is that the toolchain has minimal external dependencies and still builds well under 2 minutes. It used to be under 1 minute but the LOC has more than doubled since and I will take 90 seconds over 5 hours.
I am hopeful that the eventual drop of llvm means the OpenBSD port can track the current language easier. As of now, I’m not a zig hobbyist user for this reason, but I’d like to be!
I have mixed expectations. I’m certainly curious to see how it goes.
I have never used Zig and yet even I know that a lot of people use it for its very convenient cross-compiling facilities.
I admire ambitious people but at this point in the software engineering history I believe it should be obvious for everyone that improving compilation times and quality is one of the hardest tasks ever and it might easily take the creator and the team their entire careers to even make a dent… and actually succeeding and gaining wide adoption is not at all guaranteed.
I fear this will just doom the project to irrelevance. And it’s not like there are more than two people actually paid to work on Zig.
To me, this is all the more reason to hope that they go forward with it. The team has already proven that they have the expertise and perseverance to solve hard problems. This feels very aligned with their overall goals.
For the divorce to be a proper metaphor in this situation the other party (LLVM) should also be able to initiate the process. Which would have been interesting: Zig changed its mind but LLVM decided to proceed anyway because it felt betrayed.
Divorce metaphor seems appropriate if you think of this as not Zig divorcing LLVM but Zig language developers divorcing Zig toolchain users who use its C++ support. Even if Zig doesn’t decide to do this, Zig toolchain users may feel betrayed and decide to fork.
I love LLVM, but this is a very relatable decision.