mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-05-21 08:32:02 +03:00
Merge pull request 'incremental: fix tracking of nested container declarations (and of opaque types)' (#31889) from dont-track-children-if-lost-parent into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31889 Reviewed-by: Andrew Kelley <andrew@ziglang.org>
This commit is contained in:
+50
-47
@@ -4021,30 +4021,30 @@ pub const DeclContents = struct {
|
||||
/// This is a simple optional because ZIR guarantees that a `func`/`func_inferred`/`func_fancy` instruction
|
||||
/// can only occur once per `declaration`.
|
||||
func_decl: ?Inst.Index,
|
||||
explicit_types: std.ArrayList(Inst.Index),
|
||||
type_decls: std.ArrayList(Inst.Index),
|
||||
other: std.ArrayList(Inst.Index),
|
||||
|
||||
pub const init: DeclContents = .{
|
||||
.func_decl = null,
|
||||
.explicit_types = .empty,
|
||||
.type_decls = .empty,
|
||||
.other = .empty,
|
||||
};
|
||||
|
||||
pub fn clear(contents: *DeclContents) void {
|
||||
contents.func_decl = null;
|
||||
contents.explicit_types.clearRetainingCapacity();
|
||||
contents.type_decls.clearRetainingCapacity();
|
||||
contents.other.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
pub fn deinit(contents: *DeclContents, gpa: Allocator) void {
|
||||
contents.explicit_types.deinit(gpa);
|
||||
contents.type_decls.deinit(gpa);
|
||||
contents.other.deinit(gpa);
|
||||
}
|
||||
};
|
||||
|
||||
/// Find all tracked ZIR instructions, recursively, within a `declaration` instruction. Does not recurse through
|
||||
/// nested declarations; to find all declarations, call this function recursively on the type declarations discovered
|
||||
/// in `contents.explicit_types`.
|
||||
/// in `contents.type_decls`.
|
||||
///
|
||||
/// This populates an `ArrayList` because an iterator would need to allocate memory anyway.
|
||||
pub fn findTrackable(zir: Zir, gpa: Allocator, contents: *DeclContents, decl_inst: Zir.Inst.Index) !void {
|
||||
@@ -4064,15 +4064,49 @@ pub fn findTrackable(zir: Zir, gpa: Allocator, contents: *DeclContents, decl_ins
|
||||
if (decl.value_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
|
||||
}
|
||||
|
||||
/// Like `findTrackable`, but only considers the `main_struct_inst` instruction. This may return more than
|
||||
/// just that instruction because it will also traverse fields.
|
||||
pub fn findTrackableRoot(zir: Zir, gpa: Allocator, contents: *DeclContents) !void {
|
||||
/// `findTrackable` does not recurse into field expressions in a type. Instead, this function will
|
||||
/// scan specifically field expressions in a given type declaration for trackable ZIR instructions.
|
||||
pub fn findTrackableFields(
|
||||
zir: *const Zir,
|
||||
gpa: Allocator,
|
||||
contents: *DeclContents,
|
||||
type_decl_inst: Zir.Inst.Index,
|
||||
) Allocator.Error!void {
|
||||
contents.clear();
|
||||
|
||||
var found_defers: std.AutoHashMapUnmanaged(u32, void) = .empty;
|
||||
defer found_defers.deinit(gpa);
|
||||
|
||||
try zir.findTrackableInner(gpa, contents, &found_defers, .main_struct_inst);
|
||||
assert(zir.instructions.items(.tag)[@intFromEnum(type_decl_inst)] == .extended);
|
||||
switch (zir.instructions.items(.data)[@intFromEnum(type_decl_inst)].extended.opcode) {
|
||||
.struct_decl => {
|
||||
const struct_decl = zir.getStructDecl(type_decl_inst);
|
||||
var it = struct_decl.iterateFields();
|
||||
while (it.next()) |field| {
|
||||
try zir.findTrackableBody(gpa, contents, &found_defers, field.type_body);
|
||||
if (field.align_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
|
||||
if (field.default_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
|
||||
}
|
||||
},
|
||||
.union_decl => {
|
||||
const union_decl = zir.getUnionDecl(type_decl_inst);
|
||||
var it = union_decl.iterateFields();
|
||||
while (it.next()) |field| {
|
||||
if (field.type_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
|
||||
if (field.align_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
|
||||
if (field.value_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
|
||||
}
|
||||
},
|
||||
.enum_decl => {
|
||||
const enum_decl = zir.getEnumDecl(type_decl_inst);
|
||||
var it = enum_decl.iterateFields();
|
||||
while (it.next()) |field| {
|
||||
if (field.value_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
|
||||
}
|
||||
},
|
||||
.opaque_decl => {},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn findTrackableInner(
|
||||
@@ -4396,49 +4430,18 @@ fn findTrackableInner(
|
||||
try zir.findTrackableBody(gpa, contents, defers, body);
|
||||
},
|
||||
|
||||
// Reifications and opaque declarations need tracking, but have no bodies.
|
||||
// Reifications need tracking.
|
||||
.reify_enum,
|
||||
.reify_struct,
|
||||
.reify_union,
|
||||
.opaque_decl,
|
||||
=> return contents.other.append(gpa, inst),
|
||||
|
||||
// Struct declarations need tracking and have bodies.
|
||||
.struct_decl => {
|
||||
try contents.explicit_types.append(gpa, inst);
|
||||
|
||||
const struct_decl = zir.getStructDecl(inst);
|
||||
var it = struct_decl.iterateFields();
|
||||
while (it.next()) |field| {
|
||||
try zir.findTrackableBody(gpa, contents, defers, field.type_body);
|
||||
if (field.align_body) |b| try zir.findTrackableBody(gpa, contents, defers, b);
|
||||
if (field.default_body) |b| try zir.findTrackableBody(gpa, contents, defers, b);
|
||||
}
|
||||
},
|
||||
|
||||
// Union declarations need tracking and have bodies.
|
||||
.union_decl => {
|
||||
try contents.explicit_types.append(gpa, inst);
|
||||
|
||||
const union_decl = zir.getUnionDecl(inst);
|
||||
var it = union_decl.iterateFields();
|
||||
while (it.next()) |field| {
|
||||
if (field.type_body) |b| try zir.findTrackableBody(gpa, contents, defers, b);
|
||||
if (field.align_body) |b| try zir.findTrackableBody(gpa, contents, defers, b);
|
||||
if (field.value_body) |b| try zir.findTrackableBody(gpa, contents, defers, b);
|
||||
}
|
||||
},
|
||||
|
||||
// Enum declarations need tracking and have bodies.
|
||||
.enum_decl => {
|
||||
try contents.explicit_types.append(gpa, inst);
|
||||
|
||||
const enum_decl = zir.getEnumDecl(inst);
|
||||
var it = enum_decl.iterateFields();
|
||||
while (it.next()) |field| {
|
||||
if (field.value_body) |b| try zir.findTrackableBody(gpa, contents, defers, b);
|
||||
}
|
||||
},
|
||||
// Type declarations need tracking.
|
||||
.struct_decl,
|
||||
.union_decl,
|
||||
.enum_decl,
|
||||
.opaque_decl,
|
||||
=> return contents.type_decls.append(gpa, inst),
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+46
-44
@@ -3361,8 +3361,8 @@ pub fn mapOldZirToNew(
|
||||
old_inst: Zir.Inst.Index,
|
||||
new_inst: Zir.Inst.Index,
|
||||
};
|
||||
var match_stack: std.ArrayList(MatchedZirDecl) = .empty;
|
||||
defer match_stack.deinit(gpa);
|
||||
var pending_matched_type_decls: std.ArrayList(MatchedZirDecl) = .empty;
|
||||
defer pending_matched_type_decls.deinit(gpa);
|
||||
|
||||
// Used as temporary buffers for namespace declaration instructions
|
||||
var old_contents: Zir.DeclContents = .init;
|
||||
@@ -3370,42 +3370,13 @@ pub fn mapOldZirToNew(
|
||||
var new_contents: Zir.DeclContents = .init;
|
||||
defer new_contents.deinit(gpa);
|
||||
|
||||
// Map the main struct inst (and anything in its fields)
|
||||
{
|
||||
try old_zir.findTrackableRoot(gpa, &old_contents);
|
||||
try new_zir.findTrackableRoot(gpa, &new_contents);
|
||||
// Map the main struct inst to start off with.
|
||||
try pending_matched_type_decls.append(gpa, .{
|
||||
.old_inst = .main_struct_inst,
|
||||
.new_inst = .main_struct_inst,
|
||||
});
|
||||
|
||||
assert(old_contents.explicit_types.items[0] == .main_struct_inst);
|
||||
assert(new_contents.explicit_types.items[0] == .main_struct_inst);
|
||||
|
||||
assert(old_contents.func_decl == null);
|
||||
assert(new_contents.func_decl == null);
|
||||
|
||||
// We don't have any smart way of matching up these instructions, so we correlate them based on source order
|
||||
// in their respective arrays.
|
||||
|
||||
const num_explicit_types = @min(old_contents.explicit_types.items.len, new_contents.explicit_types.items.len);
|
||||
try match_stack.ensureUnusedCapacity(gpa, @intCast(num_explicit_types));
|
||||
for (
|
||||
old_contents.explicit_types.items[0..num_explicit_types],
|
||||
new_contents.explicit_types.items[0..num_explicit_types],
|
||||
) |old_inst, new_inst| {
|
||||
// Here we use `match_stack`, so that we will recursively consider declarations on these types.
|
||||
match_stack.appendAssumeCapacity(.{ .old_inst = old_inst, .new_inst = new_inst });
|
||||
}
|
||||
|
||||
const num_other = @min(old_contents.other.items.len, new_contents.other.items.len);
|
||||
try inst_map.ensureUnusedCapacity(gpa, @intCast(num_other));
|
||||
for (
|
||||
old_contents.other.items[0..num_other],
|
||||
new_contents.other.items[0..num_other],
|
||||
) |old_inst, new_inst| {
|
||||
// These instructions don't have declarations, so we just modify `inst_map` directly.
|
||||
inst_map.putAssumeCapacity(old_inst, new_inst);
|
||||
}
|
||||
}
|
||||
|
||||
while (match_stack.pop()) |match_item| {
|
||||
while (pending_matched_type_decls.pop()) |match_item| {
|
||||
// There are some properties of type declarations which cannot change across incremental
|
||||
// updates. If they have, we need to ignore this mapping. These properties are essentially
|
||||
// everything passed into `InternPool.getDeclaredStructType` (likewise for unions, enums,
|
||||
@@ -3461,9 +3432,41 @@ pub fn mapOldZirToNew(
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
// Match the namespace declaration itself
|
||||
// Match the container declaration itself
|
||||
try inst_map.put(gpa, match_item.old_inst, match_item.new_inst);
|
||||
|
||||
{
|
||||
// First, map the fields...
|
||||
try old_zir.findTrackableFields(gpa, &old_contents, match_item.old_inst);
|
||||
try new_zir.findTrackableFields(gpa, &new_contents, match_item.new_inst);
|
||||
|
||||
// This isn't a `.declaration`, so we shouldn't see a function declaration.
|
||||
assert(old_contents.func_decl == null);
|
||||
assert(new_contents.func_decl == null);
|
||||
|
||||
// We don't have any smart way of matching up these instructions, so we correlate them based on source order
|
||||
// in their respective arrays.
|
||||
|
||||
const num_type_decls = @min(old_contents.type_decls.items.len, new_contents.type_decls.items.len);
|
||||
try pending_matched_type_decls.ensureUnusedCapacity(gpa, @intCast(num_type_decls));
|
||||
for (
|
||||
old_contents.type_decls.items[0..num_type_decls],
|
||||
new_contents.type_decls.items[0..num_type_decls],
|
||||
) |old_inst, new_inst| {
|
||||
pending_matched_type_decls.appendAssumeCapacity(.{ .old_inst = old_inst, .new_inst = new_inst });
|
||||
}
|
||||
|
||||
const num_other = @min(old_contents.other.items.len, new_contents.other.items.len);
|
||||
try inst_map.ensureUnusedCapacity(gpa, @intCast(num_other));
|
||||
for (
|
||||
old_contents.other.items[0..num_other],
|
||||
new_contents.other.items[0..num_other],
|
||||
) |old_inst, new_inst| {
|
||||
// These instructions don't have declarations, so we just modify `inst_map` directly.
|
||||
inst_map.putAssumeCapacity(old_inst, new_inst);
|
||||
}
|
||||
}
|
||||
|
||||
// Maps decl name to `declaration` instruction.
|
||||
var named_decls: std.StringHashMapUnmanaged(Zir.Inst.Index) = .empty;
|
||||
defer named_decls.deinit(gpa);
|
||||
@@ -3537,14 +3540,13 @@ pub fn mapOldZirToNew(
|
||||
// We don't have any smart way of matching up these instructions, so we correlate them based on source order
|
||||
// in their respective arrays.
|
||||
|
||||
const num_explicit_types = @min(old_contents.explicit_types.items.len, new_contents.explicit_types.items.len);
|
||||
try match_stack.ensureUnusedCapacity(gpa, @intCast(num_explicit_types));
|
||||
const num_type_decls = @min(old_contents.type_decls.items.len, new_contents.type_decls.items.len);
|
||||
try pending_matched_type_decls.ensureUnusedCapacity(gpa, @intCast(num_type_decls));
|
||||
for (
|
||||
old_contents.explicit_types.items[0..num_explicit_types],
|
||||
new_contents.explicit_types.items[0..num_explicit_types],
|
||||
old_contents.type_decls.items[0..num_type_decls],
|
||||
new_contents.type_decls.items[0..num_type_decls],
|
||||
) |old_inst, new_inst| {
|
||||
// Here we use `match_stack`, so that we will recursively consider declarations on these types.
|
||||
match_stack.appendAssumeCapacity(.{ .old_inst = old_inst, .new_inst = new_inst });
|
||||
pending_matched_type_decls.appendAssumeCapacity(.{ .old_inst = old_inst, .new_inst = new_inst });
|
||||
}
|
||||
|
||||
const num_other = @min(old_contents.other.items.len, new_contents.other.items.len);
|
||||
|
||||
+7
-15
@@ -1073,8 +1073,6 @@ pub fn ensureMemoizedStateUpToDate(
|
||||
|
||||
const unit: AnalUnit = .wrap(.{ .memoized_state = stage });
|
||||
|
||||
log.debug("ensureMemoizedStateUpToDate", .{});
|
||||
|
||||
assert(!zcu.analysis_in_progress.contains(unit));
|
||||
|
||||
const was_outdated = zcu.clearOutdatedState(unit);
|
||||
@@ -1142,6 +1140,8 @@ fn analyzeMemoizedState(
|
||||
const comp = zcu.comp;
|
||||
const gpa = comp.gpa;
|
||||
|
||||
log.debug("analyzeMemoizedState({t})", .{stage});
|
||||
|
||||
const unit: AnalUnit = .wrap(.{ .memoized_state = stage });
|
||||
|
||||
try zcu.analysis_in_progress.putNoClobber(gpa, unit, reason);
|
||||
@@ -1182,8 +1182,6 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU
|
||||
|
||||
const anal_unit: AnalUnit = .wrap(.{ .@"comptime" = cu_id });
|
||||
|
||||
log.debug("ensureComptimeUnitUpToDate {f}", .{zcu.fmtAnalUnit(anal_unit)});
|
||||
|
||||
assert(!zcu.analysis_in_progress.contains(anal_unit));
|
||||
|
||||
// Determine whether or not this `ComptimeUnit` is outdated. For this kind of `AnalUnit`, that's
|
||||
@@ -1345,8 +1343,6 @@ pub fn ensureTypeLayoutUpToDate(
|
||||
|
||||
const anal_unit: AnalUnit = .wrap(.{ .type_layout = ty.toIntern() });
|
||||
|
||||
log.debug("ensureTypeLayoutUpToDate {f}", .{zcu.fmtAnalUnit(anal_unit)});
|
||||
|
||||
assert(!zcu.analysis_in_progress.contains(anal_unit));
|
||||
|
||||
const was_outdated: bool = outdated: {
|
||||
@@ -1413,6 +1409,8 @@ pub fn ensureTypeLayoutUpToDate(
|
||||
};
|
||||
defer sema.deinit();
|
||||
|
||||
log.debug("ensureTypeLayoutUpToDate {f} (out of date, resolving)", .{zcu.fmtAnalUnit(anal_unit)});
|
||||
|
||||
const result = switch (ty.zigTypeTag(zcu)) {
|
||||
.@"enum" => Sema.type_resolution.resolveEnumLayout(&sema, ty),
|
||||
.@"struct" => Sema.type_resolution.resolveStructLayout(&sema, ty),
|
||||
@@ -1478,8 +1476,6 @@ pub fn ensureStructDefaultsUpToDate(
|
||||
|
||||
const anal_unit: AnalUnit = .wrap(.{ .struct_defaults = ty.toIntern() });
|
||||
|
||||
log.debug("ensureStructDefaultsUpToDate {f}", .{zcu.fmtAnalUnit(anal_unit)});
|
||||
|
||||
assert(!zcu.analysis_in_progress.contains(anal_unit));
|
||||
|
||||
const was_outdated: bool = outdated: {
|
||||
@@ -1536,6 +1532,8 @@ pub fn ensureStructDefaultsUpToDate(
|
||||
};
|
||||
defer sema.deinit();
|
||||
|
||||
log.debug("ensureStructDefaultsUpToDate {f} (out of date, resolving)", .{zcu.fmtAnalUnit(anal_unit)});
|
||||
|
||||
const new_failed: bool = if (Sema.type_resolution.resolveStructDefaults(&sema, ty)) failed: {
|
||||
break :failed false;
|
||||
} else |err| switch (err) {
|
||||
@@ -1584,8 +1582,6 @@ pub fn ensureNavValUpToDate(
|
||||
const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id });
|
||||
const nav = ip.getNav(nav_id);
|
||||
|
||||
log.debug("ensureNavValUpToDate {f}", .{zcu.fmtAnalUnit(anal_unit)});
|
||||
|
||||
assert(!zcu.analysis_in_progress.contains(anal_unit));
|
||||
|
||||
try zcu.ensureNavValAnalysisQueued(nav_id);
|
||||
@@ -1946,8 +1942,6 @@ pub fn ensureNavTypeUpToDate(
|
||||
const anal_unit: AnalUnit = .wrap(.{ .nav_ty = nav_id });
|
||||
const nav = ip.getNav(nav_id);
|
||||
|
||||
log.debug("ensureNavTypeUpToDate {f}", .{zcu.fmtAnalUnit(anal_unit)});
|
||||
|
||||
assert(!zcu.analysis_in_progress.contains(anal_unit));
|
||||
|
||||
try zcu.ensureNavValAnalysisQueued(nav_id);
|
||||
@@ -2191,8 +2185,6 @@ pub fn ensureFuncBodyUpToDate(
|
||||
|
||||
const anal_unit: AnalUnit = .wrap(.{ .func = func_index });
|
||||
|
||||
log.debug("ensureFuncBodyUpToDate {f}", .{zcu.fmtAnalUnit(anal_unit)});
|
||||
|
||||
assert(!zcu.analysis_in_progress.contains(anal_unit));
|
||||
|
||||
const func = zcu.funcInfo(func_index);
|
||||
@@ -2282,7 +2274,7 @@ fn analyzeFuncBody(
|
||||
else
|
||||
.none;
|
||||
|
||||
log.debug("analyze and generate fn body {f}", .{zcu.fmtAnalUnit(anal_unit)});
|
||||
log.debug("analyzeFuncBody {f}", .{zcu.fmtAnalUnit(anal_unit)});
|
||||
|
||||
var air = try pt.analyzeFuncBodyInner(func_index, reason);
|
||||
var air_owned = true;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#update=initial version
|
||||
#file=main.zig
|
||||
pub fn main() void {
|
||||
_ = @as(S, undefined);
|
||||
}
|
||||
// To reproduce the original bug, the inner struct must perform a namespace lookup
|
||||
// or a scope lookup when resolving its field type.
|
||||
const SomeType = u8;
|
||||
const S = struct {
|
||||
foo: struct { inner: SomeType },
|
||||
};
|
||||
#expect_stdout=""
|
||||
#update=add field to outer struct, change decl used by inner struct
|
||||
#file=main.zig
|
||||
pub fn main() void {
|
||||
_ = @as(S, undefined);
|
||||
}
|
||||
// To reproduce the original bug, the inner struct must perform a namespace lookup
|
||||
// or a scope lookup when resolving its field type.
|
||||
const SomeType = u16;
|
||||
const S = struct {
|
||||
foo: struct { inner: SomeType },
|
||||
bar: u32,
|
||||
};
|
||||
#expect_stdout=""
|
||||
@@ -0,0 +1,41 @@
|
||||
// TODO: it'd be great if we could actually check that no analysis happened!
|
||||
#update=initial version
|
||||
#file=main.zig
|
||||
pub fn main() void {
|
||||
const ptr: *const O = @ptrFromInt(0x1000);
|
||||
_ = ptr;
|
||||
}
|
||||
const S = struct { foo: u32, nested: struct { x: u16 } };
|
||||
const U = union(enum) { a, b, c: S };
|
||||
const E = enum(u8) { a = @typeInfo(U).@"union".fields.len, b = 0, c };
|
||||
const O = opaque {
|
||||
comptime {
|
||||
_ = @as(S, undefined);
|
||||
_ = @as(U, undefined);
|
||||
_ = @as(E, undefined);
|
||||
const Wrapper = struct { val: S };
|
||||
const wrapper: Wrapper = .{ .val = .{ .foo = 123, .nested = .{ .x = 456 } } };
|
||||
_ = wrapper;
|
||||
}
|
||||
};
|
||||
#expect_stdout=""
|
||||
#update=do literally nothing
|
||||
#file=main.zig
|
||||
pub fn main() void {
|
||||
const ptr: *const O = @ptrFromInt(0x1000);
|
||||
_ = ptr;
|
||||
}
|
||||
const S = struct { foo: u32, nested: struct { x: u16 } };
|
||||
const U = union(enum) { a, b, c: S };
|
||||
const E = enum(u8) { a = @typeInfo(U).@"union".fields.len, b = 0, c };
|
||||
const O = opaque {
|
||||
comptime {
|
||||
_ = @as(S, undefined);
|
||||
_ = @as(U, undefined);
|
||||
_ = @as(E, undefined);
|
||||
const Wrapper = struct { val: S };
|
||||
const wrapper: Wrapper = .{ .val = .{ .foo = 123, .nested = .{ .x = 456 } } };
|
||||
_ = wrapper;
|
||||
}
|
||||
};
|
||||
#expect_stdout=""
|
||||
Reference in New Issue
Block a user