11 ms·
Zig's Incremental Compilation Internals
- remywang 2mo agoDoes this work for release builds or just debug builds now?
- minraws 2mo agoI don't think it will work with llvm/release yet but it might some day maybe. Getting incremental linker to work with llvm is kinda hard atm. Maybe the zig team has a plan dunno.
- mlugg 2mo agoIt only works with our self-hosted code generation backends (the main one being for x86_64), which right now don't have any optimisation passes. It's planned that they will in future, but that's a long-term goal. Also, any optimisations which propagate information between functions (the most obvious and important one is inlining) are more-or-less incompatible with incremental compilation (I touch on this in the post IIRC), so once this does work it'll probably still be limited to a subset of optimisations. TL;DR: only debug builds for now, could extend to release builds once we have our own optimisation passes one day, but some optimisations will still be inapplicable.
- hoppp 2mo agoThe zig compiler can compile C so will it work with C also?
- bpavuk 2mo agonot quite! if your project is mixed C/Zig, then editing Zig would work but editing C would not. the Zig compiler caches Zig AIR but no information from C. plus, the C compilation can only really be done with LLVM and writing a self-hosted backend would be PITA for C
- dundarious 2mo agoNot quite true, there is already a capable C compiler written in Zig (Aro/arocc), and a plan to transition to it for C compilation: https://codeberg.org/ziglang/translate-c https://codeberg.org/ziglang/translate-c Using this native (written in Zig) C compiler to translate C source into Zig source as a part of the build, would presumably lend itself trivially to all the incremental logic in TFA, as updating C would update the generated Zig, and the incremental logic would detect differences just like it detects differences made by a human in an editor. Maybe there are aspects of the generated Zig that would complicate that somewhat, but I don't know -- just a warning about my ignorance. This is part of plans to remove the hard LLVM dependency. AFAIK, the LLVM dependency will still be a variant many will use for the convenience of Zig as a much better clang, but removing the hard dependency is part of enabling all these great features like incremental compilation.
- squeek502 2mo ago> a plan to transition to it for C compilation That's not planned AFAIK (see https://github.com/ziglang/zig/issues/16269 https://github.com/ziglang/zig/issues/16269). `translate-c` is really only intended for header translation, not C source code. See https://github.com/ziglang/zig/issues/20875 https://github.com/ziglang/zig/issues/20875 for the (not fully fleshed out yet) plans around C compilation.
- hibra1 2mo agoZig already uses arocc! https://codeberg.org/ziglang/zig/src/branch/master/lib/compiler https://codeberg.org/ziglang/zig/src/branch/master/lib/compi...
- mlugg 2mo agoThat's there for translate-c, it's not used for compiling C code (To be clear, squeek502 is a part of the Zig core team [0], so he knows what he's talking about :D) [0]: https://ziglang.org/news/welcoming-new-team-members/ https://ziglang.org/news/welcoming-new-team-members/
- applfanboysbgon 2mo agoIt disappoints me how unseriously the industry has taken compilation speed for so long. I'm glad to see Zig doing incredibly valuable, high-impact work here.
- logicchains 2mo agoGolang's reason for existence is pretty much compilation speed.
- pjmlp 2mo agoYes, we get people amazed to rediscover Modula-2, Object Pascal compiler execution speed.
- deepsun 2mo agoWell, I remember back in the day Java was winning a lot of love from C++ devs over its much-much faster compilation than C++. It was important, even 15+ years ago. People praised faster iteration time while coding.
- khanhnguyen8386 2mo ago[flagged]
- steveklabnik 2mo agoZig's toolchain work is continually impressive. While I still don't plan to write software in it, given that I believe memory safety is table stakes, all of this stuff is very, very good. Before the incremental work, it was the toolchain and cross-compiler work. The toolchain stuff has continually been fantastic. I'm very curious to see what they come up with next! > Semantic analysis is the most difficult part of the compiler to handle incrementally. Perhaps unsurprisingly then, this is where language design starts to matter a lot: while I am pretty confident that most modern languages could support incremental compilation similar to how we do, certain design decisions can make that much more difficult. Zig has had its design tweaked over the years (sometimes controversially) specifically so that it is easier to support fast incremental compilation. This is something I wish that we had done with Rust. It is impossible to do all of the things at once, though, and we already had a tremendous amount of things to do. This is also part of the "when do you ship 1.0" tradeoff; for our goals with the language, 2015 was the right moment to launch, but if had a few more years to bake things, maybe we could have made compile times way faster. Software engineering is hard.
- hoppp 2mo agoThe thing with rust is that you get safety with slow compilation, it's a tradeoff. Zig doesn't have the same safety guarantees, it's on the dev to use safe coding patterns, so the tradeoff for safety is discipline or experience.
- steveklabnik 2mo agoRust's safety checks have basically nothing to do with its slow compile times. This is something that sounds intuitive but is just completely incorrect. In particular, Rust made several good design decisions around this stuff that keeps those checks fast, like keeping checks local rather than being global.
- jamiejquinn 2mo agoThat's interesting. Coming from C++ and Zig, the massive time "wasters" are metaprogramming features, i.e. Templates and comptime. Are Rust's macros the compile-time culprits?
- thefaux 2mo agoThere is something that I don't fully understand about this design: why are they insisting on building a giant binary for debug builds that contains all of the code? From my perspective, a simpler approach is to generate many smaller shared libraries (perhaps at the file level) and link them in to the final binary. With this approach, the program binary would have a tiny text section and a (potentially long) list of shared libraries to load. But even with thousands of shared libraries to load, the resulting program binary would not be all that long and there would be no need for binary patching. I understand that for a release mode a single giant binary may be desirable, but I am struggling to understand this design for debug builds. Moreover, while reading this article, I found myself wondering what happens if the main binary becomes corrupted. Maybe the user cancels compilation with ctrl+c while it is patching the binary. Even if they have a story for avoiding and/or detecting corruption, it is simpler to not patch in the first place and always generate a new main binary. Again, this is reasonable because the new binary is mainly just a list of shared libraries to link which will not take up much space and can be written to disk quickly. Moreover, this process can be done recursively, e.g. at the subdirectory level, so that during incremental linking a few quite small shared libraries may be produced rather than patching in the new code and writing cascading relocations.
- chaz72 2mo agoIf you choose to build static or dynamic libraries, I expect you’d still get to do that, and I expect as long as those don’t change and only the main binary needs patching it’d work the same. (Though I’m waiting for the next tagged release to really try it out myself, so that’s not like a guarantee or anything.)
- dzaima 2mo agoA quick test (C, clang) gives me that a binary depending on 1000 shared libraries, each containing a single function returning an integer, with a main function summing up the results of all those functions, takes ~270ms to run from the dynamic linker overhead. So you'd definitely want a good chunk below thousands. (a process with 100 shared libraries takes 6ms to run, which is a lot better (0.9ms for 1 library, for reference), but, especially with in-place patching skipping work on unchanged values, static linking still has a good shot at beating dynamic linking, especially if you run the binary multiple times) Incremental compilation generally already depends on its stored intermediate data not getting corrupted, and the final binary need not be any differently handled in that aspect.
- dlcarrier 2mo agoI just looked up a Hello World program from the Zig Wikipedia article: const std = @import("std"); const File = std.Io.File; pub fn main(init: std.process.Init) !void { _ = try File.stdout().writeStreamingAll(init.io, "Hello, World!\n"); } That's a lot to follow, just to output a plan-text message, especially after this line: "The primary goal of Zig is to be a better solution to the sorts of tasks that are currently solved with C. A primary concern in that respect is readability…"
- defen 2mo agoThe issue is that most "hello world" programs are not correct.
- dwattttt 2mo agoWhile most hello worlds do not check that the message was printed (which I assume writeStreamingAll does for you), dismissing the rest of the differences as "the others aren't correct" isn't really accurate. Explicitly passing IO in is a fine design choice, but it's not a correctness issue to say others are wrong to not do so.
- tester756 2mo ago>While most hello worlds do not check that the message was printed Should they?
- deleted 2mo ago[deleted]
- phire 2mo agoReally depends on what a hello world is meant to be a simplification of. If hello world is meant to be a simplification of printing large amounts of text to a buffered standard out? Then yes, it probably should be checking errors. If hello world is meant to be a simplification of low-volume debug logging to prove that code was reached (aka, printf debugging), then the simple alternative hello world using std.debug.print is what you want. For such debug prints, you don't want any buffering, you don't want it mixed in with stdout (despite the name, stderr is not just for error messages), and you don't really need to check for errors. And std.debug.print does not return errors.
- sigbottle 2mo agoI've always thought this was fascinating, but the only incremental compilation I knew was obscure programming languages and Rust. Oh yeah, I guess Roslyn? Really fun and fascinating problem to work on.
- tester756 2mo agoWasnt Roslyn 1st at implementing this on such scale?
- sigbottle 2mo agoYeah I remember being in sophomore year of college, watching Anders Hejlsberg's video on "the new way to build compilers" or something and having my mind blown. But I only ever looked at the source code for Rust when it came to something actually implementing this, so that came to mind first. Roslyn also has an extra constraint of integrating with live editing on the fly; I think you can get simpler and/or have different constraints if your requirement is only incremental compilation.
- patrec 2mo ago> Dependencies on the body of a runtime function are impossible (at least in the simplified view I’m presenting here) How does this work given that e.g. a constant can be computed by a comptime function?
- mlugg 2mo agoIt works through the fact that I specified "runtime" function ;) A bit after that quote I have a note about `inline` functions in Zig, where I mention that they perform semantic inlining, which means dependencies triggered by the function actually get associated with the call site. Well, `comptime` function calls work just the same way---in fact, to the compiler, `comptime` calls are almost exactly identical to `inline` calls. So when we encounter a comptime function call, we start analyzing the ZIR for that function's body, but we don't switch our analysis unit, so comptime stuff doesn't really complicate the dependency graph at all (aside from the fact that it means you can depend on any number of source code hashes, instead of everything depending on exactly one). With all that being said, there actually is a (completely unrelated) way in Zig to can depend on the body of a runtime function (hence why the quote includes "at least in the simplified view I'm presenting here"). It's to do with "inferred error sets" (IESes for short). If a function's return type is written `!T`, that means it can return an error, but we're asking the compiler to figure out exactly which errors are possible. So if at some point we need to know that set of errors (e.g. because the user has done some reflection to try and access the list of errors), that's where we get a dependency on a runtime function body, because we need to analyze the function body to learn about all the places it might return an error.
- muth02446 2mo agoThe incremental linking part sounds pretty hackish to me and I wonder what the price is in increased code complexity and maintenance effort. It also does not mention how it deals with the patching of debug information.
- afdbcreid 2mo agoThis post is really interesting. As a member of the rust-analyzer team, I cannot avoid comparing it to the situation in Rust land. Rust famously has not less (or even more) sophisticated system for incremental compilation, yet its compilation is way slower. I attribute that to two main things: - Language design. Zig was designed for fast and incremental compilation, Rust is just not. For instance, the post states that Zig has four properties (layout, type, value, body) that the compiler has to track for changes. Rust has much more, to the point that tracking them statically is just impossible, so the compiler uses a query system that tracks them dynamically, which adds overhead. - Compiler implementation. Rust is much more complicated to compile than Zig, and rustc is both older and bigger (10x-20x LOC) than the Zig compiler, making changing it way harder.
- lsuresh 2mo agoIsn't most of Rust's compilation overhead from the llvm backend?
- afdbcreid 2mo agoThat is common wisdom but reality is more complicated. It's true in some projects, but not all.
- simonask 2mo agoEvery analysis of the problem I've seen has concluded that the main problem is that rustc generates a lot of input to LLVM. Efforts to reduce compilation times are currently focused on doing more to the IR before converting it to LLVM IR. Rust as a language is even more reliant on monomorphization and inlining than C++, due to core language traits such as Deref, AsRef, From/Into, and so on. That said, in practice my personal experience has been that Rust compares pretty favorably on compilation speed, even to some high-level languages like C#. On many developer machines, the actual slow part is linking.
- afdbcreid 2mo ago
- anitil 2mo agoI'm becoming a big fan of Zig every since learning about `zig cc` as a way of dipping my toes in it. I was already impressed by the build caching so I'm keen to play with this
- rustynailer 2mo ago[flagged]
- ycombiburger 2mo ago[flagged]
- upboatsy 2mo ago[flagged]
- dang 2mo agoWe detached this comment from https://news.ycombinator.com/item?id=49090676 https://news.ycombinator.com/item?id=49090676.
- toxicrust 2mo ago[flagged]
- deleted 2mo ago[deleted]
- KingMob 2mo agoNeither of those links involves Klabnik. And the second one (about swatting a linux dev) says "Others think someone from the Rust (programming language, not video game) development community was responsible due to how critical René has been of that project, but those claims are entirely unsubstantiated." -- Also wild to complain about botting when I've seen half a dozen accounts that didn't exist an hour ago all pop up to criticize Klabnik.
- peterfirefly 2mo agoAnd the first one is about Christoph Hellwig who has a long history of being awful to communicate with. He should have been kicked out of the Linux community two decades ago for that reason alone. For some reason, he has decided that he doesn't like Rust and he does what he can to keep it far, far away from any code he has anything to do with. If that requires 10x more work for others, that is a price he is willing to pay.
- rusttrash 2mo ago[flagged]
- deleted 2mo ago[deleted]
- justrust 2mo ago[flagged]
- myshapeprotocol 2mo ago[flagged]