aboutsummaryrefslogtreecommitdiff
path: root/src/Zcu/PerThread.zig
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2024-12-23 20:39:19 +0000
committermlugg <mlugg@mlugg.co.uk>2024-12-24 02:18:41 +0000
commit3afda4322c34dedc2319701fdfac3505c8d311e9 (patch)
tree467873c408750cb4223f3ccf31775e42ec9fbd5c /src/Zcu/PerThread.zig
parent40aafcd6a85d3c517f445f17149c17523c832420 (diff)
downloadzig-3afda4322c34dedc2319701fdfac3505c8d311e9.tar.gz
zig-3afda4322c34dedc2319701fdfac3505c8d311e9.zip
compiler: analyze type and value of global declaration separately
This commit separates semantic analysis of the annotated type vs value of a global declaration, therefore allowing recursive and mutually recursive values to be declared. Every `Nav` which undergoes analysis now has *two* corresponding `AnalUnit`s: `.{ .nav_val = n }` and `.{ .nav_ty = n }`. The `nav_val` unit is responsible for *fully resolving* the `Nav`: determining its value, linksection, addrspace, etc. The `nav_ty` unit, on the other hand, resolves only the information necessary to construct a *pointer* to the `Nav`: its type, addrspace, etc. (It does also analyze its linksection, but that could be moved to `nav_val` I think; it doesn't make any difference). Analyzing a `nav_ty` for a declaration with no type annotation will just mark a dependency on the `nav_val`, analyze it, and finish. Conversely, analyzing a `nav_val` for a declaration *with* a type annotation will first mark a dependency on the `nav_ty` and analyze it, using this as the result type when evaluating the value body. The `nav_val` and `nav_ty` units always have references to one another: so, if a `Nav`'s type is referenced, its value implicitly is too, and vice versa. However, these dependencies are trivial, so, to save memory, are only known implicitly by logic in `resolveReferences`. In general, analyzing ZIR `decl_val` will only analyze `nav_ty` of the corresponding `Nav`. There are two exceptions to this. If the declaration is an `extern` declaration, then we immediately ensure the `Nav` value is resolved (which doesn't actually require any more analysis, since such a declaration has no value body anyway). Additionally, if the resolved type has type tag `.@"fn"`, we again immediately resolve the `Nav` value. The latter restriction is in place for two reasons: * Functions are special, in that their externs are allowed to trivially alias; i.e. with a declaration `extern fn foo(...)`, you can write `const bar = foo;`. This is not allowed for non-function externs, and it means that function types are the only place where it is possible for a declaration `Nav` to have a `.@"extern"` value without actually being declared `extern`. We need to identify this situation immediately so that the `decl_ref` can create a pointer to the *real* extern `Nav`, not this alias. * In certain situations, such as taking a pointer to a `Nav`, Sema needs to queue analysis of a runtime function if the value is a function. To do this, the function value needs to be known, so we need to resolve the value immediately upon `&foo` where `foo` is a function. This restriction is simple to codify into the eventual language specification, and doesn't limit the utility of this feature in practice. A consequence of this commit is that codegen and linking logic needs to be more careful when looking at `Nav`s. In general: * When `updateNav` or `updateFunc` is called, it is safe to assume that the `Nav` being updated (the owner `Nav` for `updateFunc`) is fully resolved. * Any `Nav` whose value is/will be an `@"extern"` or a function is fully resolved; see `Nav.getExtern` for a helper for a common case here. * Any other `Nav` may only have its type resolved. This didn't seem to be too tricky to satisfy in any of the existing codegen/linker backends. Resolves: #131
Diffstat (limited to 'src/Zcu/PerThread.zig')
-rw-r--r--src/Zcu/PerThread.zig476
1 files changed, 340 insertions, 136 deletions
diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig
index 0d7ca0eb26..21908f769f 100644
--- a/src/Zcu/PerThread.zig
+++ b/src/Zcu/PerThread.zig
@@ -731,10 +731,12 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
+ _ = zcu.nav_val_analysis_queued.swapRemove(nav_id);
+
const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id });
const nav = ip.getNav(nav_id);
- log.debug("ensureNavUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)});
+ log.debug("ensureNavValUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)});
// Determine whether or not this `Nav`'s value is outdated. This also includes checking if the
// status is `.unresolved`, which indicates that the value is outdated because it has *never*
@@ -763,19 +765,19 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
} else {
// We can trust the current information about this unit.
if (prev_failed) return error.AnalysisFail;
- if (nav.status == .resolved) return;
+ switch (nav.status) {
+ .unresolved, .type_resolved => {},
+ .fully_resolved => return,
+ }
}
const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);
defer unit_prog_node.end();
- const sema_result: SemaNavResult, const new_failed: bool = if (pt.analyzeNavVal(nav_id)) |result| res: {
+ const invalidate_value: bool, const new_failed: bool = if (pt.analyzeNavVal(nav_id)) |result| res: {
break :res .{
- .{
- // If the unit has gone from failed to success, we still need to invalidate the dependencies.
- .invalidate_nav_val = result.invalidate_nav_val or prev_failed,
- .invalidate_nav_ref = result.invalidate_nav_ref or prev_failed,
- },
+ // If the unit has gone from failed to success, we still need to invalidate the dependencies.
+ result.val_changed or prev_failed,
false,
};
} else |err| switch (err) {
@@ -786,10 +788,7 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
}
- break :res .{ .{
- .invalidate_nav_val = !prev_failed,
- .invalidate_nav_ref = !prev_failed,
- }, true };
+ break :res .{ !prev_failed, true };
},
error.OutOfMemory => {
// TODO: it's unclear how to gracefully handle this.
@@ -806,10 +805,8 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
};
if (was_outdated) {
- // TODO: we do not yet have separate dependencies for Nav values vs types.
- const invalidate = sema_result.invalidate_nav_val or sema_result.invalidate_nav_ref;
const dependee: InternPool.Dependee = .{ .nav_val = nav_id };
- if (invalidate) {
+ if (invalidate_value) {
// 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.
@@ -824,14 +821,7 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
if (new_failed) return error.AnalysisFail;
}
-const SemaNavResult = packed struct {
- /// Whether the value of a `decl_val` of the corresponding Nav changed.
- invalidate_nav_val: bool,
- /// Whether the type of a `decl_ref` of the corresponding Nav changed.
- invalidate_nav_ref: bool,
-};
-
-fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!SemaNavResult {
+fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!struct { val_changed: bool } {
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
@@ -875,9 +865,13 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr
};
defer sema.deinit();
- // The comptime unit declares on the source of the corresponding declaration.
+ // Every `Nav` declares a dependency on the source of the corresponding declaration.
try sema.declareDependency(.{ .src_hash = old_nav.analysis.?.zir_index });
+ // In theory, we would also add a reference to the corresponding `nav_val` unit here: there are
+ // always references in both directions between a `nav_val` and `nav_ty`. However, to save memory,
+ // these references are known implicitly. See logic in `Zcu.resolveReferences`.
+
var block: Sema.Block = .{
.parent = null,
.sema = &sema,
@@ -891,31 +885,44 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr
defer block.instructions.deinit(gpa);
const zir_decl = zir.getDeclaration(inst_resolved.inst);
-
assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace"));
+ const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 });
+ const init_src = block.src(.{ .node_offset_var_decl_init = 0 });
const align_src = block.src(.{ .node_offset_var_decl_align = 0 });
const section_src = block.src(.{ .node_offset_var_decl_section = 0 });
const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 });
- const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 });
- const init_src = block.src(.{ .node_offset_var_decl_init = 0 });
+
+ const maybe_ty: ?Type = if (zir_decl.type_body != null) ty: {
+ // Since we have a type body, the type is resolved separately!
+ // Of course, we need to make sure we depend on it properly.
+ try sema.declareDependency(.{ .nav_ty = nav_id });
+ try pt.ensureNavTypeUpToDate(nav_id);
+ break :ty .fromInterned(ip.getNav(nav_id).status.type_resolved.type);
+ } else null;
+
+ const final_val: ?Value = if (zir_decl.value_body) |value_body| val: {
+ if (maybe_ty) |ty| {
+ // Put the resolved type into `inst_map` to be used as the result type of the init.
+ try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_resolved.inst});
+ sema.inst_map.putAssumeCapacity(inst_resolved.inst, Air.internedToRef(ty.toIntern()));
+ const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst);
+ assert(sema.inst_map.remove(inst_resolved.inst));
+
+ const result_ref = try sema.coerce(&block, ty, uncoerced_result_ref, init_src);
+ break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref);
+ } else {
+ // Just analyze the value; we have no type to offer.
+ const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst);
+ break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref);
+ }
+ } else null;
+
+ const nav_ty: Type = maybe_ty orelse final_val.?.typeOf(zcu);
// First, we must resolve the declaration's type. To do this, we analyze the type body if available,
// or otherwise, we analyze the value body, populating `early_val` in the process.
- const nav_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: {
- // We evaluate only the type now; no need for the value yet.
- const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_resolved.inst);
- const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src);
- break :ty .{ .fromInterned(type_ref.toInterned().?), null };
- } else ty: {
- // We don't have a type body, so we need to evaluate the value immediately.
- const value_body = zir_decl.value_body.?;
- const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst);
- const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref);
- break :ty .{ val.typeOf(zcu), val };
- };
-
switch (zir_decl.kind) {
.@"comptime" => unreachable, // this is not a Nav
.unnamed_test, .@"test", .decltest => assert(nav_ty.zigTypeTag(zcu) == .@"fn"),
@@ -932,58 +939,24 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr
// Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine
// the full pointer type of this declaration.
- const alignment: InternPool.Alignment = a: {
- const align_body = zir_decl.align_body orelse break :a .none;
- const align_ref = try sema.resolveInlineBody(&block, align_body, inst_resolved.inst);
- break :a try sema.analyzeAsAlign(&block, align_src, align_ref);
- };
-
- const @"linksection": InternPool.OptionalNullTerminatedString = ls: {
- const linksection_body = zir_decl.linksection_body orelse break :ls .none;
- const linksection_ref = try sema.resolveInlineBody(&block, linksection_body, inst_resolved.inst);
- const bytes = try sema.toConstString(&block, section_src, linksection_ref, .{
- .needed_comptime_reason = "linksection must be comptime-known",
- });
- if (std.mem.indexOfScalar(u8, bytes, 0) != null) {
- return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{});
- } else if (bytes.len == 0) {
- return sema.fail(&block, section_src, "linksection cannot be empty", .{});
- }
- break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls);
- };
-
- const @"addrspace": std.builtin.AddressSpace = as: {
- const addrspace_ctx: Sema.AddressSpaceContext = switch (zir_decl.kind) {
- .@"var" => .variable,
- else => switch (nav_ty.zigTypeTag(zcu)) {
- .@"fn" => .function,
- else => .constant,
+ const modifiers: Sema.NavPtrModifiers = if (zir_decl.type_body != null) m: {
+ // `analyzeNavType` (from the `ensureNavTypeUpToDate` call above) has already populated this data into
+ // the `Nav`. Load the new one, and pull the modifiers out.
+ switch (ip.getNav(nav_id).status) {
+ .unresolved => unreachable, // `analyzeNavType` will never leave us in this state
+ inline .type_resolved, .fully_resolved => |r| break :m .{
+ .alignment = r.alignment,
+ .@"linksection" = r.@"linksection",
+ .@"addrspace" = r.@"addrspace",
},
- };
- const target = zcu.getTarget();
- const addrspace_body = zir_decl.addrspace_body orelse break :as switch (addrspace_ctx) {
- .function => target_util.defaultAddressSpace(target, .function),
- .variable => target_util.defaultAddressSpace(target, .global_mutable),
- .constant => target_util.defaultAddressSpace(target, .global_constant),
- else => unreachable,
- };
- const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_resolved.inst);
- break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx);
+ }
+ } else m: {
+ // `analyzeNavType` is essentially a stub which calls us. We are responsible for resolving this data.
+ break :m try sema.resolveNavPtrModifiers(&block, zir_decl, inst_resolved.inst, nav_ty);
};
- // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations
- // don't have an associated value body.
-
- const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: {
- // Put the resolved type into `inst_map` to be used as the result type of the init.
- try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_resolved.inst});
- sema.inst_map.putAssumeCapacity(inst_resolved.inst, Air.internedToRef(nav_ty.toIntern()));
- const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst);
- assert(sema.inst_map.remove(inst_resolved.inst));
-
- const result_ref = try sema.coerce(&block, nav_ty, uncoerced_result_ref, init_src);
- break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref);
- } else null;
+ // Lastly, we must figure out the actual interned value to store to the `Nav`.
+ // This isn't necessarily the same as `final_val`!
const nav_val: Value = switch (zir_decl.linkage) {
.normal, .@"export" => switch (zir_decl.kind) {
@@ -1013,8 +986,8 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr
.is_threadlocal = zir_decl.is_threadlocal,
.is_weak_linkage = false,
.is_dll_import = false,
- .alignment = alignment,
- .@"addrspace" = @"addrspace",
+ .alignment = modifiers.alignment,
+ .@"addrspace" = modifiers.@"addrspace",
.zir_index = old_nav.analysis.?.zir_index, // `declaration` instruction
.owner_nav = undefined, // ignored by `getExtern`
}));
@@ -1047,10 +1020,7 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr
});
// TODO: usingnamespace cannot participate in incremental compilation
assert(zcu.analysis_in_progress.swapRemove(anal_unit));
- return .{
- .invalidate_nav_val = true,
- .invalidate_nav_ref = true,
- };
+ return .{ .val_changed = true };
}
const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(nav_val.toIntern())) {
@@ -1087,14 +1057,22 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr
ip.resolveNavValue(nav_id, .{
.val = nav_val.toIntern(),
- .alignment = alignment,
- .@"linksection" = @"linksection",
- .@"addrspace" = @"addrspace",
+ .alignment = modifiers.alignment,
+ .@"linksection" = modifiers.@"linksection",
+ .@"addrspace" = modifiers.@"addrspace",
});
// Mark the unit as completed before evaluating the export!
assert(zcu.analysis_in_progress.swapRemove(anal_unit));
+ if (zir_decl.type_body == null) {
+ // In this situation, it's possible that we were triggered by `analyzeNavType` up the stack. In that
+ // case, we must also signal that the *type* is now populated to make this export behave correctly.
+ // An alternative strategy would be to just put something on the job queue to perform the export, but
+ // this is a little more straightforward, if perhaps less elegant.
+ _ = zcu.analysis_in_progress.swapRemove(.wrap(.{ .nav_ty = nav_id }));
+ }
+
if (zir_decl.linkage == .@"export") {
const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.is_pub) });
const name_slice = zir.nullTerminatedString(zir_decl.name);
@@ -1117,21 +1095,246 @@ fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileErr
}
switch (old_nav.status) {
- .unresolved => return .{
- .invalidate_nav_val = true,
- .invalidate_nav_ref = true,
+ .unresolved, .type_resolved => return .{ .val_changed = true },
+ .fully_resolved => |old| return .{ .val_changed = old.val != nav_val.toIntern() },
+ }
+}
+
+pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.SemaError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const zcu = pt.zcu;
+ const gpa = zcu.gpa;
+ const ip = &zcu.intern_pool;
+
+ const anal_unit: AnalUnit = .wrap(.{ .nav_ty = nav_id });
+ const nav = ip.getNav(nav_id);
+
+ log.debug("ensureNavTypeUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)});
+
+ // Determine whether or not this `Nav`'s type is outdated. This also includes checking if the
+ // status is `.unresolved`, which indicates that the value is outdated because it has *never*
+ // been analyzed so far.
+ //
+ // Note that if the unit is PO, we pessimistically assume that it *does* require re-analysis, to
+ // ensure that the unit is definitely up-to-date when this function returns. This mechanism could
+ // result in over-analysis if analysis occurs in a poor order; we do our best to avoid this by
+ // carefully choosing which units to re-analyze. See `Zcu.findOutdatedToAnalyze`.
+
+ const was_outdated = zcu.outdated.swapRemove(anal_unit) or
+ zcu.potentially_outdated.swapRemove(anal_unit);
+
+ const prev_failed = zcu.failed_analysis.contains(anal_unit) or
+ zcu.transitive_failed_analysis.contains(anal_unit);
+
+ if (was_outdated) {
+ dev.check(.incremental);
+ _ = zcu.outdated_ready.swapRemove(anal_unit);
+ zcu.deleteUnitExports(anal_unit);
+ zcu.deleteUnitReferences(anal_unit);
+ if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
+ kv.value.destroy(gpa);
+ }
+ _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
+ } else {
+ // We can trust the current information about this unit.
+ if (prev_failed) return error.AnalysisFail;
+ switch (nav.status) {
+ .unresolved => {},
+ .type_resolved, .fully_resolved => return,
+ }
+ }
+
+ const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);
+ defer unit_prog_node.end();
+
+ const invalidate_type: bool, const new_failed: bool = if (pt.analyzeNavType(nav_id)) |result| res: {
+ break :res .{
+ // If the unit has gone from failed to success, we still need to invalidate the dependencies.
+ result.type_changed or prev_failed,
+ false,
+ };
+ } else |err| switch (err) {
+ error.AnalysisFail => res: {
+ if (!zcu.failed_analysis.contains(anal_unit)) {
+ // If this unit caused the error, it would have an entry in `failed_analysis`.
+ // Since it does not, this must be a transitive failure.
+ try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
+ log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
+ }
+ break :res .{ !prev_failed, true };
},
- .resolved => |old| {
- const new = ip.getNav(nav_id).status.resolved;
- return .{
- .invalidate_nav_val = new.val != old.val,
- .invalidate_nav_ref = ip.typeOf(new.val) != ip.typeOf(old.val) or
- new.alignment != old.alignment or
- new.@"linksection" != old.@"linksection" or
- new.@"addrspace" != old.@"addrspace",
- };
+ error.OutOfMemory => {
+ // TODO: it's unclear how to gracefully handle this.
+ // To report the error cleanly, we need to add a message to `failed_analysis` and a
+ // corresponding entry to `retryable_failures`; but either of these things is quite
+ // likely to OOM at this point.
+ // If that happens, what do we do? Perhaps we could have a special field on `Zcu`
+ // for reporting OOM errors without allocating.
+ return error.OutOfMemory;
},
+ error.GenericPoison => unreachable,
+ error.ComptimeReturn => unreachable,
+ error.ComptimeBreak => unreachable,
+ };
+
+ if (was_outdated) {
+ const dependee: InternPool.Dependee = .{ .nav_ty = nav_id };
+ if (invalidate_type) {
+ // 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 zcu.markDependeeOutdated(.marked_po, dependee);
+ } else {
+ // This dependency was previously PO, but turned out to be up-to-date.
+ // We do not need to queue successive analysis.
+ try zcu.markPoDependeeUpToDate(dependee);
+ }
}
+
+ if (new_failed) return error.AnalysisFail;
+}
+
+fn analyzeNavType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!struct { type_changed: bool } {
+ const zcu = pt.zcu;
+ const gpa = zcu.gpa;
+ const ip = &zcu.intern_pool;
+
+ const anal_unit: AnalUnit = .wrap(.{ .nav_ty = nav_id });
+ const old_nav = ip.getNav(nav_id);
+
+ log.debug("analyzeNavType {}", .{zcu.fmtAnalUnit(anal_unit)});
+
+ const inst_resolved = old_nav.analysis.?.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
+ const file = zcu.fileByIndex(inst_resolved.file);
+ // TODO: stop the compiler ever reaching Sema if there are failed files. That way, this check is
+ // unnecessary, and we can move the below `removeDependenciesForDepender` call up with its friends
+ // in `ensureComptimeUnitUpToDate`.
+ if (file.status != .success_zir) return error.AnalysisFail;
+ const zir = file.zir;
+
+ // We are about to re-analyze this unit; drop its depenndencies.
+ zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
+
+ try zcu.analysis_in_progress.put(gpa, anal_unit, {});
+ defer _ = zcu.analysis_in_progress.swapRemove(anal_unit);
+
+ var analysis_arena: std.heap.ArenaAllocator = .init(gpa);
+ defer analysis_arena.deinit();
+
+ var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa);
+ defer comptime_err_ret_trace.deinit();
+
+ var sema: Sema = .{
+ .pt = pt,
+ .gpa = gpa,
+ .arena = analysis_arena.allocator(),
+ .code = zir,
+ .owner = anal_unit,
+ .func_index = .none,
+ .func_is_naked = false,
+ .fn_ret_ty = .void,
+ .fn_ret_ty_ies = null,
+ .comptime_err_ret_trace = &comptime_err_ret_trace,
+ };
+ defer sema.deinit();
+
+ // Every `Nav` declares a dependency on the source of the corresponding declaration.
+ try sema.declareDependency(.{ .src_hash = old_nav.analysis.?.zir_index });
+
+ // In theory, we would also add a reference to the corresponding `nav_val` unit here: there are
+ // always references in both directions between a `nav_val` and `nav_ty`. However, to save memory,
+ // these references are known implicitly. See logic in `Zcu.resolveReferences`.
+
+ var block: Sema.Block = .{
+ .parent = null,
+ .sema = &sema,
+ .namespace = old_nav.analysis.?.namespace,
+ .instructions = .{},
+ .inlining = null,
+ .is_comptime = true,
+ .src_base_inst = old_nav.analysis.?.zir_index,
+ .type_name_ctx = old_nav.fqn,
+ };
+ defer block.instructions.deinit(gpa);
+
+ const zir_decl = zir.getDeclaration(inst_resolved.inst);
+ assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace"));
+
+ const type_body = zir_decl.type_body orelse {
+ // The type of this `Nav` is inferred from the value.
+ // In other words, this `nav_ty` depends on the corresponding `nav_val`.
+ try sema.declareDependency(.{ .nav_val = nav_id });
+ try pt.ensureNavValUpToDate(nav_id);
+ // Note that the above call, if it did any work, has removed our `analysis_in_progress` entry for us.
+ // (Our `defer` will run anyway, but it does nothing in this case.)
+
+ // There's not a great way for us to know whether the type actually changed.
+ // For instance, perhaps the `nav_val` was already up-to-date, but this `nav_ty` is being
+ // analyzed because this declaration had a type annotation on the *previous* update.
+ // However, such cases are rare, and it's not unreasonable to re-analyze in them; and in
+ // other cases where we get here, it's because the `nav_val` was already re-analyzed and
+ // is outdated.
+ return .{ .type_changed = true };
+ };
+
+ const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 });
+
+ const resolved_ty: Type = ty: {
+ const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_resolved.inst);
+ const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src);
+ break :ty .fromInterned(type_ref.toInterned().?);
+ };
+
+ // In the case where the type is specified, this function is also responsible for resolving
+ // the pointer modifiers, i.e. alignment, linksection, addrspace.
+ const modifiers = try sema.resolveNavPtrModifiers(&block, zir_decl, inst_resolved.inst, resolved_ty);
+
+ // Usually, we can infer this information from the resolved `Nav` value; see `Zcu.navValIsConst`.
+ // However, since we don't have one, we need to quickly check the ZIR to figure this out.
+ const is_const = switch (zir_decl.kind) {
+ .@"comptime" => unreachable,
+ .unnamed_test, .@"test", .decltest, .@"usingnamespace", .@"const" => true,
+ .@"var" => false,
+ };
+
+ const is_extern_decl = zir_decl.linkage == .@"extern";
+
+ // Now for the question of the day: are the type and modifiers the same as before?
+ // If they are, then we should actually keep the `Nav` as `fully_resolved` if it currently is.
+ // That's because `analyzeNavVal` will later want to look at the resolved value to figure out
+ // whether it's changed: if we threw that data away now, it would have to assume that the value
+ // had changed, potentially spinning off loads of unnecessary re-analysis!
+ const changed = switch (old_nav.status) {
+ .unresolved => true,
+ .type_resolved => |r| r.type != resolved_ty.toIntern() or
+ r.alignment != modifiers.alignment or
+ r.@"linksection" != modifiers.@"linksection" or
+ r.@"addrspace" != modifiers.@"addrspace" or
+ r.is_const != is_const or
+ r.is_extern_decl != is_extern_decl,
+ .fully_resolved => |r| ip.typeOf(r.val) != resolved_ty.toIntern() or
+ r.alignment != modifiers.alignment or
+ r.@"linksection" != modifiers.@"linksection" or
+ r.@"addrspace" != modifiers.@"addrspace" or
+ zcu.navValIsConst(r.val) != is_const or
+ (old_nav.getExtern(ip) != null) != is_extern_decl,
+ };
+
+ if (!changed) return .{ .type_changed = false };
+
+ ip.resolveNavType(nav_id, .{
+ .type = resolved_ty.toIntern(),
+ .alignment = modifiers.alignment,
+ .@"linksection" = modifiers.@"linksection",
+ .@"addrspace" = modifiers.@"addrspace",
+ .is_const = is_const,
+ .is_threadlocal = zir_decl.is_threadlocal,
+ .is_extern_decl = is_extern_decl,
+ });
+
+ return .{ .type_changed = true };
}
pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void {
@@ -1144,6 +1347,8 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
+ _ = zcu.func_body_analysis_queued.swapRemove(maybe_coerced_func_index);
+
// We only care about the uncoerced function.
const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index);
const anal_unit: AnalUnit = .wrap(.{ .func = func_index });
@@ -1171,11 +1376,7 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
if (prev_failed) {
return error.AnalysisFail;
}
- switch (func.analysisUnordered(ip).state) {
- .unreferenced => {}, // this is the first reference
- .queued => {}, // we're waiting on first-time analysis
- .analyzed => return, // up-to-date
- }
+ if (func.analysisUnordered(ip).is_analyzed) return;
}
const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0);
@@ -1236,7 +1437,7 @@ fn analyzeFuncBody(
if (func.generic_owner == .none) {
// Among another things, this ensures that the function's `zir_body_inst` is correct.
try pt.ensureNavValUpToDate(func.owner_nav);
- if (ip.getNav(func.owner_nav).status.resolved.val != func_index) {
+ if (ip.getNav(func.owner_nav).status.fully_resolved.val != func_index) {
// This function is no longer referenced! There's no point in re-analyzing it.
// Just mark a transitive failure and move on.
return error.AnalysisFail;
@@ -1245,7 +1446,7 @@ fn analyzeFuncBody(
const go_nav = zcu.funcInfo(func.generic_owner).owner_nav;
// Among another things, this ensures that the function's `zir_body_inst` is correct.
try pt.ensureNavValUpToDate(go_nav);
- if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) {
+ if (ip.getNav(go_nav).status.fully_resolved.val != func.generic_owner) {
// The generic owner is no longer referenced, so this function is also unreferenced.
// There's no point in re-analyzing it. Just mark a transitive failure and move on.
return error.AnalysisFail;
@@ -2172,7 +2373,7 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE
try zcu.analysis_in_progress.put(gpa, anal_unit, {});
errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit);
- func.setAnalysisState(ip, .analyzed);
+ func.setAnalyzed(ip);
if (func.analysisUnordered(ip).inferred_error_set) {
func.setResolvedErrorSet(ip, .none);
}
@@ -2550,8 +2751,8 @@ fn processExportsInner(
if (zcu.transitive_failed_analysis.contains(unit)) break :failed true;
}
const val = switch (nav.status) {
- .unresolved => break :failed true,
- .resolved => |r| Value.fromInterned(r.val),
+ .unresolved, .type_resolved => break :failed true,
+ .fully_resolved => |r| Value.fromInterned(r.val),
};
// If the value is a function, we also need to check if that function succeeded analysis.
if (val.typeOf(zcu).zigTypeTag(zcu) == .@"fn") {
@@ -3256,30 +3457,29 @@ pub fn getBuiltinNav(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Intern
const builtin_nav = std_namespace.pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse
@panic("lib/std.zig is corrupt and missing 'builtin'");
pt.ensureNavValUpToDate(builtin_nav) catch @panic("std.builtin is corrupt");
- const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.resolved.val);
+ const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.fully_resolved.val);
const builtin_namespace = zcu.namespacePtr(builtin_type.getNamespace(zcu).unwrap() orelse @panic("std.builtin is corrupt"));
const name_str = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls);
return builtin_namespace.pub_decls.getKeyAdapted(name_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse @panic("lib/std/builtin.zig is corrupt");
}
-pub fn navPtrType(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) Allocator.Error!Type {
+pub fn navPtrType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Allocator.Error!Type {
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
- const r = ip.getNav(nav_index).status.resolved;
- const ty = Value.fromInterned(r.val).typeOf(zcu);
+ const ty, const alignment, const @"addrspace", const is_const = switch (ip.getNav(nav_id).status) {
+ .unresolved => unreachable,
+ .type_resolved => |r| .{ r.type, r.alignment, r.@"addrspace", r.is_const },
+ .fully_resolved => |r| .{ ip.typeOf(r.val), r.alignment, r.@"addrspace", zcu.navValIsConst(r.val) },
+ };
return pt.ptrType(.{
- .child = ty.toIntern(),
+ .child = ty,
.flags = .{
- .alignment = if (r.alignment == ty.abiAlignment(zcu))
+ .alignment = if (alignment == Type.fromInterned(ty).abiAlignment(zcu))
.none
else
- r.alignment,
- .address_space = r.@"addrspace",
- .is_const = switch (ip.indexToKey(r.val)) {
- .variable => false,
- .@"extern" => |e| e.is_const,
- else => true,
- },
+ alignment,
+ .address_space = @"addrspace",
+ .is_const = is_const,
},
});
}
@@ -3299,9 +3499,13 @@ pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!
// TODO: this shouldn't need a `PerThread`! Fix the signature of `Type.abiAlignment`.
pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPool.Alignment {
const zcu = pt.zcu;
- const r = zcu.intern_pool.getNav(nav_index).status.resolved;
- if (r.alignment != .none) return r.alignment;
- return Value.fromInterned(r.val).typeOf(zcu).abiAlignment(zcu);
+ const ty: Type, const alignment = switch (zcu.intern_pool.getNav(nav_index).status) {
+ .unresolved => unreachable,
+ .type_resolved => |r| .{ .fromInterned(r.type), r.alignment },
+ .fully_resolved => |r| .{ Value.fromInterned(r.val).typeOf(zcu), r.alignment },
+ };
+ if (alignment != .none) return alignment;
+ return ty.abiAlignment(zcu);
}
/// Given a container type requiring resolution, ensures that it is up-to-date.