-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Incremental fixes, refactor Zcu.File
#23836
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
In one of my project I currently use non existant modules to have optional depencencies. Simplified slightly below: // build.zig
const use_jemalloc = b.option(bool, "use_jemalloc", "test jemalloc") orelse false;
const exe_opts = b.addOptions();
exe_opts.addOption(bool, "use_jemalloc", use_jemalloc);
const exe_mod = b.createModule(.{
// ...
});
exe_mod.addOptions("build_opts", exe_opts);
// the jemalloc source code may not be downloaded, or maybe exe_mod wants to stay libc free
if (use_jemalloc) {
const jemalloc_mod = b.createModule(.{
// ...
});
exe_mod.addImport("use_jemalloc", jemalloc_mod);
} // main.zig
const functions = [_]Contructor{} ++
if (build_opts.use_jemalloc) @import("jemalloc").constructor else .{};
const build_opts = @import("build_opts");
// ... If I understand correctly, after this PR this code will no longer compile when |
In that case, I'd suggest just adding the module in the build script regardless of There could be other cases I've not thought of where this is more of a problem, though. If anyone has any, feel free to ask here, and we can see if this language change is problematic. |
It may be useful to label this PR as breaking, so more people are inclined to check it out. |
7069906
to
834693c
Compare
Whats about bigger dependencies like |
Does the compiler actually analyse that |
Optional lazy dependencies seems like a compelling argument in favor of status quo to me. @andrewrk, what do you think? The question is about something like this: const foo = @import("foo");
const build_options = @import("build_options");
pub fn main() void {
if (build_options.use_foo) {
foo.doStuff();
}
} ...where the module I think this clearly needs to be a supported use case, so I see two options:
Personally I'm in favour of option 1 (making this PR non-breaking); while it's unfortunate that you could end up with unused imports of non-existent modules, option 2 feels like quite a bit of friction to make this work well. It also makes the error for referencing |
I've reverted the breaking change discussed above; I think it did more harm than good. At the very least, it can go through the proposal process. There is still one very minor breaking change here (I honestly don't think anyone will hit it), which is that the contract of |
c462eee
to
c1dc22d
Compare
Allow specifying modules which the root module depends on. More complex graphs cannot currently be specified.
This function was broken, because it took ownership of the buffer on error *sometimes*, in a way which the caller could not tell. Rather than trying to be clever, it's easier to just follow the same interface as all other `addFilePost` methods, and not take ownership of the path. This is a breaking change. The next commits will apply it to the compiler, which is the only user of this function in the ziglang/zig repository.
This commit makes some big changes to how we track state for Zig source files. In particular, it changes: * How `File` tracks its path on-disk * How AstGen discovers files * How file-level errors are tracked * How `builtin.zig` files and modules are created The original motivation here was to address incremental compilation bugs with the handling of files, such as ziglang#22696. To fix this, a few changes are necessary. Just like declarations may become unreferenced on an incremental update, meaning we suppress analysis errors associated with them, it is also possible for all imports of a file to be removed on an incremental update, in which case file-level errors for that file should be suppressed. As such, after AstGen, the compiler must traverse files (starting from analysis roots) and discover the set of "live files" for this update. Additionally, the compiler's previous handling of retryable file errors was not very good; the source location the error was reported as was based only on the first discovered import of that file. This source location also disappeared on future incremental updates. So, as a part of the file traversal above, we also need to figure out the source locations of imports which errors should be reported against. Another observation I made is that the "file exists in multiple modules" error was not implemented in a particularly good way (I get to say that because I wrote it!). It was subject to races, where the order in which different imports of a file were discovered affects both how errors are printed, and which module the file is arbitrarily assigned, with the latter in turn affecting which other files are considered for import. The thing I realised here is that while the AstGen worker pool is running, we cannot know for sure which module(s) a file is in; we could always discover an import later which changes the answer. So, here's how the AstGen workers have changed. We initially ensure that `zcu.import_table` contains the root files for all modules in this Zcu, even if we don't know any imports for them yet. Then, the AstGen workers do not need to be aware of modules. Instead, they simply ignore module imports, and only spin off more workers when they see a by-path import. During AstGen, we can't use module-root-relative paths, since we don't know which modules files are in; but we don't want to unnecessarily use absolute files either, because those are non-portable and can make `error.NameTooLong` more likely. As such, I have introduced a new abstraction, `Compilation.Path`. This type is a way of representing a filesystem path which has a *canonical form*. The path is represented relative to one of a few special directories: the lib directory, the global cache directory, or the local cache directory. As a fallback, we use absolute (or cwd-relative on WASI) paths. This is kind of similar to `std.Build.Cache.Path` with a pre-defined list of possible `std.Build.Cache.Directory`, but has stricter canonicalization rules based on path resolution to make sure deduplicating files works properly. A `Compilation.Path` can be trivially converted to a `std.Build.Cache.Path` from a `Compilation`, but is smaller, has a canonical form, and has a digest which will be consistent across different compiler processes with the same lib and cache directories (important when we serialize incremental compilation state in the future). `Zcu.File` and `Zcu.EmbedFile` both contain a `Compilation.Path`, which is used to access the file on-disk; module-relative sub paths are used quite rarely (`EmbedFile` doesn't even have one now for simplicity). After the AstGen workers all complete, we know that any file which might be imported is definitely in `import_table` and up-to-date. So, we perform a single-threaded graph traversal; similar to what `resolveReferences` plays for `AnalUnit`s, but for files instead. We figure out which files are alive, and which module each file is in. If a file turns out to be in multiple modules, we set a field on `Zcu` to indicate this error. If a file is in a different module to a prior update, we set a flag instructing `updateZirRefs` to invalidate all dependencies on the file. This traversal also discovers "import errors"; these are errors associated with a specific `@import`. With Zig's current design, there is only one possible error here: "import outside of module root". This must be identified during this traversal instead of during AstGen, because it depends on which module the file is in. I tried also representing "module not found" errors in this same way, but it turns out to be much more useful to report those in Sema, because of use cases like optional dependencies where a module import is behind a comptime-known build option. For simplicity, `failed_files` now just maps to `?[]u8`, since the source location is always the whole file. In fact, this allows removing `LazySrcLoc.Offset.entire_file` completely, slightly simplifying some error reporting logic. File-level errors are now directly built in the `std.zig.ErrorBundle.Wip`. If the payload is not `null`, it is the message for a retryable error (i.e. an error loading the source file), and will be reported with a "file imported here" note pointing to the import site discovered during the single-threaded file traversal. The last piece of fallout here is how `Builtin` works. Rather than constructing "builtin" modules when creating `Package.Module`s, they are now constructed on-the-fly by `Zcu`. The map `Zcu.builtin_modules` maps from digests to `*Package.Module`s. These digests are abstract hashes of the `Builtin` value; i.e. all of the options which are placed into "builtin.zig". During the file traversal, we populate `builtin_modules` as needed, so that when we see this imports in Sema, we just grab the relevant entry from this map. This eliminates a bunch of awkward state tracking during construction of the module graph. It's also now clearer exactly what options the builtin module has, since previously it inherited some options arbitrarily from the first-created module with that "builtin" module! The user-visible effects of this commit are: * retryable file errors are now consistently reported against the whole file, with a note pointing to a live import of that file * some theoretical bugs where imports are wrongly considered distinct (when the import path moves out of the cwd and then back in) are fixed * some consistency issues with how file-level errors are reported are fixed; these errors will now always be printed in the same order regardless of how the AstGen pass assigns file indices * incremental updates do not print retryable file errors differently between updates or depending on file structure/contents * incremental updates support files changing modules * incremental updates support files becoming unreferenced Resolves: ziglang#22696
This can be reverted once ziglang#23902 is fixed.
See commit messages.
There is one breaking change here, which is that@import("non_existent_module")
is now a compile error even if the import is never analyzed. This is going to fail CI at the moment, because Aro currently relies on the old behavior of accepting these imports.I've reverted that breaking change, at least for now. There is still one very minor breaking change, which is that the contract of
std.Build.Cache.Manifest.addFilePostContents
has changed.