-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Invalid assumptions in type resolution logic cause incorrect resolution success, crashing compiler #23344
Comments
I've managed to reduce this. Unfortunately, it's a very deep bug which demonstrates fundamental flaws with our type resolution strategy. The idea here is to abuse the fact that while resolving recursive types, we treat a type as successfully resolved if its resolution is WIP (below us on the call stack). That assumption isn't valid, because a later field of that WIP type might cause resolution to fail! This means we can end up in a situation where a type Here's the smallest I managed to get the repro, heavily commented: /// `Invalid` is a type which fails layout/full resolution.
/// The `x` field is there to make sure the type has runtime bits to avoid triggering #14903 instead.
const Invalid = struct { x: u8, y: Invalid };
/// This type, `A`, is what we're going to want to fully resolve.
/// The type resolution process looks like this:
/// * We mark `A` as WIP
/// * We recurse into `B`
/// * `B` sees that `A` is WIP so does not recurse; it marks itself as fully resolved since all fields have been checked
/// * Back in `A`, we recurse into `Invalid`
/// * We see an error, so `A` is *not* marked as fully resolved
/// * Ultimately, `A` is correctly marked as failed, but `B` is marked as fully resolved
const A = struct {
b: *B,
x: *Invalid,
};
/// This is the type whose resolution wrongly succeeds.
const B = struct {
a: A,
};
comptime {
// First, we queue full resolution of `A`.
// Per the steps above, when this happens, `B` will incorrectly succeed full resolution.
_ = A;
// We want to analyze `bar` *after* fully resolving `A`, but usually, the compiler will
// queue function analysis before type resolution. To get the ordering we want, we use
// a wrapper type, whose nested `comptime` decl will be analyzed after the resolution we
// queued above.
_ = struct {
comptime {
_ = &bar;
}
};
}
/// This is the function whose analysis triggers the compiler crash.
/// It fails because `Air.types_resolved` determines that every type here is fully resolved,
/// but `codegen.llvm` later hits some unresolved state (the runtime field order of `Invalid`).
fn bar(x: *B) void {
_ = x;
} Stack Trace
Between this issue, #23400, #23362, #20134, #19920, #14903, and other related issues, it seems clear to me that there are fundamental flaws in how the Zig compiler handles type resolution. We need to figure out a new approach. |
InternPool.LoadedStructType.RuntimeOrderIterator
Zig Version
0.14.0
Steps to Reproduce and Observed Behavior
Found this crash while setting up Zig tests in Bun. You can reproduce it by checking out this PR.
When building with a ReleaseSafe build of Zig, I get this stack trace:
Expected Behavior
Build succeeds and tests run.
The text was updated successfully, but these errors were encountered: