stage2: first pass at recursive dependency resolution

This commit is contained in:
Andrew Kelley
2020-05-28 12:19:00 -04:00
parent c7ca1fe6f7
commit 3eed7a4dea
4 changed files with 290 additions and 129 deletions

View File

@@ -128,6 +128,9 @@ pub const Decl = struct {
/// Completed successfully before; the `typed_value.most_recent` can be accessed, and
/// new semantic analysis is in progress.
repeat_in_progress,
/// Failed before; the `typed_value.most_recent` is not available, and
/// new semantic analysis is in progress.
repeat_in_progress_novalue,
/// Everything is done and updated.
complete,
},
@@ -136,18 +139,24 @@ pub const Decl = struct {
/// This is populated regardless of semantic analysis and code generation.
link: link.ElfFile.TextBlock = link.ElfFile.TextBlock.empty,
contents_hash: Hash,
/// The shallow set of other decls whose typed_value could possibly change if this Decl's
/// typed_value is modified.
/// TODO look into using a lightweight map/set data structure rather than a linear array.
dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){},
contents_hash: Hash,
/// The shallow set of other decls whose typed_value changing indicates that this Decl's
/// typed_value may need to be regenerated.
/// TODO look into using a lightweight map/set data structure rather than a linear array.
dependencies: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){},
pub fn destroy(self: *Decl, allocator: *Allocator) void {
allocator.free(mem.spanZ(self.name));
if (self.typedValueManaged()) |tvm| {
tvm.deinit(allocator);
}
self.dependants.deinit(allocator);
self.dependencies.deinit(allocator);
allocator.destroy(self);
}
@@ -204,6 +213,7 @@ pub const Decl = struct {
.initial_in_progress,
.initial_dependency_failure,
.initial_sema_failure,
.repeat_in_progress_novalue,
=> return null,
.codegen_failure,
.codegen_failure_retryable,
@@ -214,6 +224,31 @@ pub const Decl = struct {
=> return &self.typed_value.most_recent,
}
}
fn flagForRegeneration(self: *Decl) void {
if (self.typedValueManaged() == null) {
self.analysis = .repeat_in_progress_novalue;
} else {
self.analysis = .repeat_in_progress;
}
}
fn isFlaggedForRegeneration(self: *Decl) bool {
return switch (self.analysis) {
.repeat_in_progress, .repeat_in_progress_novalue => true,
else => false,
};
}
fn removeDependant(self: *Decl, other: *Decl) void {
for (self.dependants.items) |item, i| {
if (item == other) {
_ = self.dependants.swapRemove(i);
return;
}
}
unreachable;
}
};
/// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
@@ -266,12 +301,12 @@ pub const Scope = struct {
/// Asserts the scope has a parent which is a DeclAnalysis and
/// returns the Decl.
pub fn decl(self: *Scope) *Decl {
switch (self.tag) {
.block => return self.cast(Block).?.decl,
.decl => return self.cast(DeclAnalysis).?.decl,
.zir_module => unreachable,
}
pub fn decl(self: *Scope) ?*Decl {
return switch (self.tag) {
.block => self.cast(Block).?.decl,
.decl => self.cast(DeclAnalysis).?.decl,
.zir_module => null,
};
}
/// Asserts the scope has a parent which is a ZIRModule and
@@ -517,11 +552,7 @@ pub fn deinit(self: *Module) void {
{
var it = self.export_owners.iterator();
while (it.next()) |kv| {
const export_list = kv.value;
for (export_list) |exp| {
allocator.destroy(exp);
}
allocator.free(export_list);
freeExportList(allocator, kv.value);
}
self.export_owners.deinit();
}
@@ -532,6 +563,13 @@ pub fn deinit(self: *Module) void {
self.* = undefined;
}
fn freeExportList(allocator: *Allocator, export_list: []*Export) void {
for (export_list) |exp| {
allocator.destroy(exp);
}
allocator.free(export_list);
}
pub fn target(self: Module) std.Target {
return self.bin_file.options.target;
}
@@ -634,9 +672,9 @@ const InnerError = error{ OutOfMemory, AnalysisFail };
pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
while (self.work_queue.readItem()) |work_item| switch (work_item) {
.codegen_decl => |decl| switch (decl.analysis) {
.initial_in_progress,
.repeat_in_progress,
=> unreachable,
.initial_in_progress => unreachable,
.repeat_in_progress => unreachable,
.repeat_in_progress_novalue => unreachable,
.initial_sema_failure,
.repeat_sema_failure,
@@ -686,6 +724,23 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
};
}
fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void {
try depender.dependencies.ensureCapacity(self.allocator, depender.dependencies.items.len + 1);
try dependee.dependants.ensureCapacity(self.allocator, dependee.dependants.items.len + 1);
for (depender.dependencies.items) |item| {
if (item == dependee) break; // Already in the set.
} else {
depender.dependencies.appendAssumeCapacity(dependee);
}
for (dependee.dependants.items) |item| {
if (item == depender) break; // Already in the set.
} else {
dependee.dependants.appendAssumeCapacity(depender);
}
}
fn getSource(self: *Module, root_scope: *Scope.ZIRModule) ![:0]const u8 {
switch (root_scope.source) {
.unloaded => {
@@ -772,37 +827,105 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void {
=> {
const src_module = try self.getSrcModule(root_scope);
// Look for changed decls.
// Look for changed decls. First we add all the decls that changed
// into the set.
var regen_decl_set = std.ArrayList(*Decl).init(self.allocator);
defer regen_decl_set.deinit();
try regen_decl_set.ensureCapacity(src_module.decls.len);
var exports_to_resolve = std.ArrayList(*zir.Inst).init(self.allocator);
defer exports_to_resolve.deinit();
for (src_module.decls) |src_decl| {
const name_hash = Decl.hashSimpleName(src_decl.name);
if (self.decl_table.get(name_hash)) |kv| {
const decl = kv.value;
const new_contents_hash = Decl.hashSimpleName(src_decl.contents);
if (!mem.eql(u8, &new_contents_hash, &decl.contents_hash)) {
// TODO recursive dependency management
//std.debug.warn("noticed that '{}' changed\n", .{src_decl.name});
self.decl_table.removeAssertDiscard(name_hash);
const saved_link = decl.link;
decl.destroy(self.allocator);
if (self.export_owners.getValue(decl)) |exports| {
@panic("TODO handle updating a decl that does an export");
}
const new_decl = self.resolveDecl(
&root_scope.base,
src_decl,
saved_link,
) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => continue,
};
if (self.decl_exports.remove(decl)) |entry| {
self.decl_exports.putAssumeCapacityNoClobber(new_decl, entry.value);
}
std.debug.warn("noticed that '{}' changed\n", .{src_decl.name});
regen_decl_set.appendAssumeCapacity(decl);
}
} else if (src_decl.cast(zir.Inst.Export)) |export_inst| {
_ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.TextBlock.empty);
try exports_to_resolve.append(&export_inst.base);
}
}
// Next, recursively chase the dependency graph, to populate the set.
{
var i: usize = 0;
while (i < regen_decl_set.items.len) : (i += 1) {
const decl = regen_decl_set.items[i];
if (decl.isFlaggedForRegeneration()) {
// We already looked at this decl's dependency graph.
continue;
}
decl.flagForRegeneration();
// Remove itself from its dependencies, because we are about to destroy the
// decl pointer.
for (decl.dependencies.items) |dep| {
dep.removeDependant(decl);
}
// Populate the set with decls that need to get regenerated because they
// depend on this one.
// TODO If it is only a function body that is modified, it should break the chain
// and not cause its dependants to be regenerated.
for (decl.dependants.items) |dep| {
if (!dep.isFlaggedForRegeneration()) {
regen_decl_set.appendAssumeCapacity(dep);
}
}
}
}
// Remove them all from the decl_table.
for (regen_decl_set.items) |decl| {
const decl_name = mem.spanZ(decl.name);
const old_name_hash = Decl.hashSimpleName(decl_name);
self.decl_table.removeAssertDiscard(old_name_hash);
if (self.export_owners.remove(decl)) |kv| {
for (kv.value) |exp| {
self.bin_file.deleteExport(exp.link);
}
freeExportList(self.allocator, kv.value);
}
}
// Regenerate the decls in the set.
const zir_module = try self.getSrcModule(root_scope);
while (regen_decl_set.popOrNull()) |decl| {
const decl_name = mem.spanZ(decl.name);
std.debug.warn("regenerating {}\n", .{decl_name});
const saved_link = decl.link;
const decl_exports_entry = if (self.decl_exports.remove(decl)) |kv| kv.value else null;
const src_decl = zir_module.findDecl(decl_name) orelse {
@panic("TODO treat this as a deleted decl");
};
decl.destroy(self.allocator);
const new_decl = self.resolveDecl(
&root_scope.base,
src_decl,
saved_link,
) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => continue,
};
if (decl_exports_entry) |entry| {
const gop = try self.decl_exports.getOrPut(new_decl);
if (gop.found_existing) {
self.allocator.free(entry);
} else {
gop.kv.value = entry;
}
}
}
for (exports_to_resolve.items) |export_inst| {
_ = try self.resolveDecl(&root_scope.base, export_inst, link.ElfFile.TextBlock.empty);
}
},
}
}
@@ -906,11 +1029,13 @@ fn resolveDecl(
}
}
/// Declares a dependency on the decl.
fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl {
const decl = try self.resolveDecl(scope, old_inst, link.ElfFile.TextBlock.empty);
switch (decl.analysis) {
.initial_in_progress => unreachable,
.repeat_in_progress => unreachable,
.repeat_in_progress_novalue => unreachable,
.initial_dependency_failure,
.repeat_dependency_failure,
.initial_sema_failure,
@@ -919,8 +1044,12 @@ fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerE
.codegen_failure_retryable,
=> return error.AnalysisFail,
.complete => return decl,
.complete => {},
}
if (scope.decl()) |scope_decl| {
try self.declareDeclDependency(scope_decl, decl);
}
return decl;
}
fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst {
@@ -998,7 +1127,7 @@ fn analyzeExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) In
const new_export = try self.allocator.create(Export);
errdefer self.allocator.destroy(new_export);
const owner_decl = scope.decl();
const owner_decl = scope.decl().?;
new_export.* = .{
.options = .{ .name = symbol_name },
@@ -1327,7 +1456,7 @@ fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError
new_func.* = .{
.fn_type = fn_type,
.analysis = .{ .queued = fn_inst },
.owner_decl = scope.decl(),
.owner_decl = scope.decl().?,
};
const fn_payload = try scope.arena().create(Value.Payload.Function);
fn_payload.* = .{ .func = new_func };