compiler: progress towards incremental

Most basic re-analysis logic is now in place. Trivial updates are
hitting linker assertions.
This commit is contained in:
mlugg 2024-03-09 11:53:06 +00:00
parent 7ba8641d19
commit 1421d329a3
No known key found for this signature in database
GPG Key ID: E60D96B91E86EF9F
3 changed files with 175 additions and 56 deletions

View File

@ -7103,7 +7103,7 @@ pub fn getGeneratedTagEnumType(ip: *InternPool, gpa: Allocator, ini: GeneratedTa
return @enumFromInt(gop.index);
}
pub const OpaqueTypeIni = struct {
pub const OpaqueTypeInit = struct {
has_namespace: bool,
key: union(enum) {
declared: struct {
@ -7117,7 +7117,7 @@ pub const OpaqueTypeIni = struct {
},
};
pub fn getOpaqueType(ip: *InternPool, gpa: Allocator, ini: OpaqueTypeIni) Allocator.Error!WipNamespaceType.Result {
pub fn getOpaqueType(ip: *InternPool, gpa: Allocator, ini: OpaqueTypeInit) Allocator.Error!WipNamespaceType.Result {
const adapter: KeyAdapter = .{ .intern_pool = ip };
const gop = try ip.map.getOrPutAdapted(gpa, Key{ .opaque_type = switch (ini.key) {
.declared => |d| .{ .declared = .{

View File

@ -762,14 +762,14 @@ pub const Namespace = struct {
zcu: *Zcu,
pub fn hash(ctx: @This(), decl_index: Decl.Index) u32 {
const decl = ctx.module.declPtr(decl_index);
const decl = ctx.zcu.declPtr(decl_index);
return std.hash.uint32(@intFromEnum(decl.name));
}
pub fn eql(ctx: @This(), a_decl_index: Decl.Index, b_decl_index: Decl.Index, b_index: usize) bool {
_ = b_index;
const a_decl = ctx.module.declPtr(a_decl_index);
const b_decl = ctx.module.declPtr(b_decl_index);
const a_decl = ctx.zcu.declPtr(a_decl_index);
const b_decl = ctx.zcu.declPtr(b_decl_index);
return a_decl.name == b_decl.name;
}
};
@ -2655,7 +2655,7 @@ pub fn markDependeeOutdated(zcu: *Zcu, dependee: InternPool.Dependee) !void {
if (opt_po_entry) |e| e.value else 0,
);
log.debug("outdated: {}", .{depender});
if (opt_po_entry != null) {
if (opt_po_entry == null) {
// This is a new entry with no PO dependencies.
try zcu.outdated_ready.put(zcu.gpa, depender, {});
}
@ -2989,10 +2989,10 @@ pub fn mapOldZirToNew(
/// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl.
pub fn ensureFileAnalyzed(zcu: *Zcu, file: *File) SemaError!void {
if (file.root_decl == .none) {
return zcu.semaFile(file);
if (file.root_decl.unwrap()) |existing_root| {
return zcu.ensureDeclAnalyzed(existing_root);
} else {
return zcu.ensureDeclAnalyzed(file.root_decl);
return zcu.semaFile(file);
}
}
@ -3006,6 +3006,11 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
const decl = mod.declPtr(decl_index);
log.debug("ensureDeclAnalyzed '{d}' (name '{}')", .{
@intFromEnum(decl_index),
decl.name.fmt(&mod.intern_pool),
});
// Determine whether or not this Decl is outdated, i.e. requires re-analysis
// even if `complete`. If a Decl is PO, we pessismistically assume that it
// *does* require re-analysis, to ensure that the Decl is definitely
@ -3099,11 +3104,13 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
// TODO: we do not yet have separate dependencies for decl values vs types.
if (decl_was_outdated) {
if (sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref) {
log.debug("Decl tv invalidated ('{d}')", .{@intFromEnum(decl_index)});
// This dependency was marked as PO, meaning dependees were waiting
// on its analysis result, and it has turned out to be outdated.
// Update dependees accordingly.
try mod.markDependeeOutdated(.{ .decl_val = decl_index });
} else {
log.debug("Decl tv up-to-date ('{d}')", .{@intFromEnum(decl_index)});
// This dependency was previously PO, but turned out to be up-to-date.
// We do not need to queue successive analysis.
try mod.markPoDependeeUpToDate(.{ .decl_val = decl_index });
@ -3115,11 +3122,31 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
const tracy = trace(@src());
defer tracy.end();
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
const func = zcu.funcInfo(func_index);
const decl_index = func.owner_decl;
const decl = zcu.declPtr(decl_index);
log.debug("ensureFuncBodyAnalyzed '{d}' (instance of '{}')", .{
@intFromEnum(func_index),
decl.name.fmt(ip),
});
// First, our owner decl must be up-to-date. This will always be the case
// during the first update, but may not on successive updates if we happen
// to get analyzed before our parent decl.
try zcu.ensureDeclAnalyzed(decl_index);
// On an update, it's possible this function changed such that our owner
// decl now refers to a different function, making this one orphaned. If
// that's the case, we should remove this function from the binary.
if (decl.val.ip_index != func_index) {
ip.removeDependenciesForDepender(gpa, InternPool.Depender.wrap(.{ .func = func_index }));
ip.remove(func_index);
@panic("TODO: remove orphaned function from binary");
}
switch (decl.analysis) {
.unreferenced => unreachable,
.in_progress => unreachable,
@ -3143,7 +3170,7 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
}
switch (func.analysis(ip).state) {
.success,
.success => if (!was_outdated) return,
.sema_failure,
.dependency_failure,
.codegen_failure,
@ -3153,7 +3180,10 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError
.inline_only => unreachable, // don't queue work for this
}
const gpa = zcu.gpa;
log.debug("analyze and generate fn body '{d}'; reason='{s}'", .{
@intFromEnum(func_index),
if (was_outdated) "outdated" else "never analyzed",
});
var tmp_arena = std.heap.ArenaAllocator.init(gpa);
defer tmp_arena.deinit();
@ -3311,7 +3341,9 @@ pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: InternPool.Index)
/// https://github.com/ziglang/zig/issues/14307
pub fn semaPkg(mod: *Module, pkg: *Package.Module) !void {
const file = (try mod.importPkg(pkg)).file;
return mod.semaFile(file);
if (file.root_decl == .none) {
return mod.semaFile(file);
}
}
fn getFileRootStruct(zcu: *Zcu, decl_index: Decl.Index, namespace_index: Namespace.Index, file: *File) Allocator.Error!InternPool.Index {
@ -3383,9 +3415,13 @@ fn getFileRootStruct(zcu: *Zcu, decl_index: Decl.Index, namespace_index: Namespa
/// reconstructed at a new InternPool index. Otherwise, the namespace is just
/// re-analyzed. Returns whether the decl's tyval was invalidated.
fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool {
assert(file.root_decl != .none);
const decl = zcu.declPtr(file.root_decl.unwrap().?);
const decl = zcu.declPtr(file.root_decl);
log.debug("semaFileUpdate mod={s} sub_file_path={s} type_outdated={}", .{
file.mod.fully_qualified_name,
file.sub_file_path,
type_outdated,
});
if (file.status != .success_zir) {
if (decl.analysis == .file_failure) {
@ -3398,7 +3434,7 @@ fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool {
if (decl.analysis == .file_failure) {
// No struct type currently exists. Create one!
_ = try zcu.getFileRootStruct(file.root_decl, decl.src_namespace, file);
_ = try zcu.getFileRootStruct(file.root_decl.unwrap().?, decl.src_namespace, file);
return true;
}
@ -3407,9 +3443,10 @@ fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool {
if (type_outdated) {
// Invalidate the existing type, reusing the decl and namespace.
try zcu.intern_pool.remove(decl.val.toIntern());
zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, InternPool.Depender.wrap(.{ .decl = file.root_decl.unwrap().? }));
zcu.intern_pool.remove(decl.val.toIntern());
decl.val = undefined;
_ = try zcu.getFileRootStruct(file.root_decl, decl.src_namespace, file);
_ = try zcu.getFileRootStruct(file.root_decl.unwrap().?, decl.src_namespace, file);
return true;
}
@ -3420,7 +3457,7 @@ fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool {
const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len;
extra_index += @intFromEnum(small.has_fields_len);
extra_index += @intFromBool(small.has_fields_len);
const decls_len = if (small.has_decls_len) blk: {
const decls_len = file.zir.extra[extra_index];
extra_index += 1;
@ -3530,11 +3567,13 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
assert(!mod.declIsRoot(decl_index));
if (decl.owns_tv) {
// We are re-analyzing an owner Decl (for a function or a namespace type).
@panic("TODO: update owner Decl");
if (decl.zir_decl_index == .none and decl.owns_tv) {
// We are re-analyzing an anonymous owner Decl (for a function or a namespace type).
return mod.semaAnonOwnerDecl(decl_index);
}
log.debug("semaDecl '{d}'", .{@intFromEnum(decl_index)});
const decl_inst = decl.zir_decl_index.unwrap().?.resolve(ip);
const gpa = mod.gpa;
@ -3827,6 +3866,42 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
return result;
}
fn semaAnonOwnerDecl(zcu: *Zcu, decl_index: Decl.Index) !SemaDeclResult {
const decl = zcu.declPtr(decl_index);
assert(decl.has_tv);
assert(decl.owns_tv);
log.debug("semaAnonOwnerDecl '{d}'", .{@intFromEnum(decl_index)});
switch (decl.ty.zigTypeTag(zcu)) {
.Fn => @panic("TODO: update fn instance"),
.Type => {},
else => unreachable,
}
// We are the owner Decl of a type, and we were marked as outdated. That means the *structure*
// of this type changed; not just its namespace. Therefore, we need a new InternPool index.
//
// However, as soon as we make that, the context that created us will require re-analysis anyway
// (as it depends on this Decl's value), meaning the `struct_decl` (or equivalent) instruction
// will be analyzed again. Since Sema already needs to be able to reconstruct types like this,
// why should we bother implementing it here too when the Sema logic will be hit right after?
//
// So instead, let's just mark this Decl as failed - so that any remaining Decls which genuinely
// reference it (via `@This`) end up silently erroring too - and we'll let Sema make a new type
// with a new Decl.
//
// Yes, this does mean that any type owner Decl has a constant value for its entire lifetime.
zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, InternPool.Depender.wrap(.{ .decl = decl_index }));
zcu.intern_pool.remove(decl.val.toIntern());
decl.analysis = .dependency_failure;
return .{
.invalidate_decl_val = true,
.invalidate_decl_ref = true,
};
}
pub const ImportFileResult = struct {
file: *File,
is_new: bool,
@ -4152,7 +4227,7 @@ pub fn scanNamespace(
var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Decl.Index) = .{};
defer existing_by_inst.deinit(gpa);
try existing_by_inst.ensureTotalCapacity(namespace.decls.count());
try existing_by_inst.ensureTotalCapacity(gpa, @intCast(namespace.decls.count()));
for (namespace.decls.keys()) |decl_index| {
const decl = zcu.declPtr(decl_index);
@ -4357,7 +4432,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
.anon => unreachable,
.@"comptime" => true,
.@"usingnamespace" => a: {
namespace.usingnamespace_set.putNoClobber(decl_index, declaration.flags.is_pub);
namespace.usingnamespace_set.putAssumeCapacityNoClobber(decl_index, declaration.flags.is_pub);
break :a true;
},
.named => false,
@ -4533,6 +4608,11 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato
};
defer sema.deinit();
// Every runtime function has a dependency on the source of the Decl it originates from.
// It also depends on the value of its owner Decl.
try sema.declareDependency(.{ .src_hash = decl.zir_decl_index.unwrap().? });
try sema.declareDependency(.{ .decl_val = decl_index });
if (func.analysis(ip).inferred_error_set) {
const ies = try arena.create(Sema.InferredErrorSet);
ies.* = .{ .func = func_index };

View File

@ -2705,6 +2705,37 @@ fn getCaptures(sema: *Sema, block: *Block, extra_index: usize, captures_len: u32
return captures;
}
/// Given an `InternPool.WipNamespaceType` or `InternPool.WipEnumType`, apply
/// `sema.builtin_type_target_index` to it if necessary.
fn wrapWipTy(sema: *Sema, wip_ty: anytype) @TypeOf(wip_ty) {
if (sema.builtin_type_target_index == .none) return wip_ty;
var new = wip_ty;
new.index = sema.builtin_type_target_index;
sema.mod.intern_pool.resolveBuiltinType(new.index, wip_ty.index);
return new;
}
/// Given a type just looked up in the `InternPool`, check whether it is
/// considered outdated on this update. If so, remove it from the pool
/// and return `true`.
fn maybeRemoveOutdatedType(sema: *Sema, ty: InternPool.Index) !bool {
const zcu = sema.mod;
if (!zcu.comp.debug_incremental) return false;
const decl_index = Type.fromInterned(ty).getOwnerDecl(zcu);
const decl_as_depender = InternPool.Depender.wrap(.{ .decl = decl_index });
const was_outdated = zcu.outdated.swapRemove(decl_as_depender) or
zcu.potentially_outdated.swapRemove(decl_as_depender);
if (!was_outdated) return false;
_ = zcu.outdated_ready.swapRemove(decl_as_depender);
zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, InternPool.Depender.wrap(.{ .decl = decl_index }));
zcu.intern_pool.remove(ty);
zcu.declPtr(decl_index).analysis = .dependency_failure;
try zcu.markDependeeOutdated(.{ .decl_val = decl_index });
return true;
}
fn zirStructDecl(
sema: *Sema,
block: *Block,
@ -2748,7 +2779,7 @@ fn zirStructDecl(
}
}
const wip_ty = switch (try ip.getStructType(gpa, .{
const struct_init: InternPool.StructTypeInit = .{
.layout = small.layout,
.fields_len = fields_len,
.known_non_opv = small.known_non_opv,
@ -2763,16 +2794,14 @@ fn zirStructDecl(
.zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst),
.captures = captures,
} },
})) {
.existing => |ty| return Air.internedToRef(ty),
.wip => |wip| wip: {
if (sema.builtin_type_target_index == .none) break :wip wip;
var new = wip;
new.index = sema.builtin_type_target_index;
ip.resolveBuiltinType(new.index, wip.index);
break :wip new;
},
};
const wip_ty = sema.wrapWipTy(switch (try ip.getStructType(gpa, struct_init)) {
.existing => |ty| wip: {
if (!try sema.maybeRemoveOutdatedType(ty)) return Air.internedToRef(ty);
break :wip (try ip.getStructType(gpa, struct_init)).wip;
},
.wip => |wip| wip,
});
errdefer wip_ty.cancel(ip);
const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{
@ -2969,7 +2998,7 @@ fn zirEnumDecl(
if (bag != 0) break true;
} else false;
const wip_ty = switch (try ip.getEnumType(gpa, .{
const enum_init: InternPool.EnumTypeInit = .{
.has_namespace = true or decls_len > 0, // TODO: see below
.has_values = any_values,
.tag_mode = if (small.nonexhaustive)
@ -2983,16 +3012,14 @@ fn zirEnumDecl(
.zir_index = try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst),
.captures = captures,
} },
})) {
.wip => |wip| wip: {
if (sema.builtin_type_target_index == .none) break :wip wip;
var new = wip;
new.index = sema.builtin_type_target_index;
ip.resolveBuiltinType(new.index, wip.index);
break :wip new;
},
.existing => |ty| return Air.internedToRef(ty),
};
const wip_ty = sema.wrapWipTy(switch (try ip.getEnumType(gpa, enum_init)) {
.existing => |ty| wip: {
if (!try sema.maybeRemoveOutdatedType(ty)) return Air.internedToRef(ty);
break :wip (try ip.getEnumType(gpa, enum_init)).wip;
},
.wip => |wip| wip,
});
// Once this is `true`, we will not delete the decl or type even upon failure, since we
// have finished constructing the type and are in the process of analyzing it.
@ -3230,7 +3257,7 @@ fn zirUnionDecl(
const captures = try sema.getCaptures(block, extra_index, captures_len);
extra_index += captures_len;
const wip_ty = switch (try ip.getUnionType(gpa, .{
const union_init: InternPool.UnionTypeInit = .{
.flags = .{
.layout = small.layout,
.status = .none,
@ -3257,16 +3284,14 @@ fn zirUnionDecl(
.zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst),
.captures = captures,
} },
})) {
.wip => |wip| wip: {
if (sema.builtin_type_target_index == .none) break :wip wip;
var new = wip;
new.index = sema.builtin_type_target_index;
ip.resolveBuiltinType(new.index, wip.index);
break :wip new;
},
.existing => |ty| return Air.internedToRef(ty),
};
const wip_ty = sema.wrapWipTy(switch (try ip.getUnionType(gpa, union_init)) {
.existing => |ty| wip: {
if (!try sema.maybeRemoveOutdatedType(ty)) return Air.internedToRef(ty);
break :wip (try ip.getUnionType(gpa, union_init)).wip;
},
.wip => |wip| wip,
});
errdefer wip_ty.cancel(ip);
const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, src, .{
@ -3336,15 +3361,20 @@ fn zirOpaqueDecl(
const captures = try sema.getCaptures(block, extra_index, captures_len);
extra_index += captures_len;
const wip_ty = switch (try ip.getOpaqueType(gpa, .{
const opaque_init: InternPool.OpaqueTypeInit = .{
.has_namespace = decls_len != 0,
.key = .{ .declared = .{
.zir_index = try ip.trackZir(gpa, block.getFileScope(mod), inst),
.captures = captures,
} },
})) {
};
// No `wrapWipTy` needed as no std.builtin types are opaque.
const wip_ty = switch (try ip.getOpaqueType(gpa, opaque_init)) {
.existing => |ty| wip: {
if (!try sema.maybeRemoveOutdatedType(ty)) return Air.internedToRef(ty);
break :wip (try ip.getOpaqueType(gpa, opaque_init)).wip;
},
.wip => |wip| wip,
.existing => |ty| return Air.internedToRef(ty),
};
errdefer wip_ty.cancel(ip);
@ -39052,6 +39082,15 @@ fn ptrType(sema: *Sema, info: InternPool.Key.PtrType) CompileError!Type {
pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
if (!sema.mod.comp.debug_incremental) return;
// Avoid creating dependencies on ourselves. This situation can arise when we analyze the fields
// of a type and they use `@This()`. This dependency would be unnecessary, and in fact would
// just result in over-analysis since `Zcu.findOutdatedToAnalyze` would never be able to resolve
// the loop.
if (sema.owner_func_index == .none and dependee == .decl_val and dependee.decl_val == sema.owner_decl_index) {
return;
}
const depender = InternPool.Depender.wrap(
if (sema.owner_func_index != .none)
.{ .func = sema.owner_func_index }