Skip to content

Commit

Permalink
compiler: analyze type and value of global declaration separately
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mlugg committed Dec 24, 2024
1 parent 40aafcd commit 3afda43
Show file tree
Hide file tree
Showing 22 changed files with 1,033 additions and 410 deletions.
14 changes: 10 additions & 4 deletions src/Compilation.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2906,6 +2906,7 @@ const Header = extern struct {
file_deps_len: u32,
src_hash_deps_len: u32,
nav_val_deps_len: u32,
nav_ty_deps_len: u32,
namespace_deps_len: u32,
namespace_name_deps_len: u32,
first_dependency_len: u32,
Expand Down Expand Up @@ -2949,6 +2950,7 @@ pub fn saveState(comp: *Compilation) !void {
.file_deps_len = @intCast(ip.file_deps.count()),
.src_hash_deps_len = @intCast(ip.src_hash_deps.count()),
.nav_val_deps_len = @intCast(ip.nav_val_deps.count()),
.nav_ty_deps_len = @intCast(ip.nav_ty_deps.count()),
.namespace_deps_len = @intCast(ip.namespace_deps.count()),
.namespace_name_deps_len = @intCast(ip.namespace_name_deps.count()),
.first_dependency_len = @intCast(ip.first_dependency.count()),
Expand Down Expand Up @@ -2979,6 +2981,8 @@ pub fn saveState(comp: *Compilation) !void {
addBuf(&bufs, mem.sliceAsBytes(ip.src_hash_deps.values()));
addBuf(&bufs, mem.sliceAsBytes(ip.nav_val_deps.keys()));
addBuf(&bufs, mem.sliceAsBytes(ip.nav_val_deps.values()));
addBuf(&bufs, mem.sliceAsBytes(ip.nav_ty_deps.keys()));
addBuf(&bufs, mem.sliceAsBytes(ip.nav_ty_deps.values()));
addBuf(&bufs, mem.sliceAsBytes(ip.namespace_deps.keys()));
addBuf(&bufs, mem.sliceAsBytes(ip.namespace_deps.values()));
addBuf(&bufs, mem.sliceAsBytes(ip.namespace_name_deps.keys()));
Expand Down Expand Up @@ -3145,7 +3149,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {

const file_index = switch (anal_unit.unwrap()) {
.@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index.resolveFile(ip),
.nav_val => |nav| ip.getNav(nav).analysis.?.zir_index.resolveFile(ip),
.nav_val, .nav_ty => |nav| ip.getNav(nav).analysis.?.zir_index.resolveFile(ip),
.type => |ty| Type.fromInterned(ty).typeDeclInst(zcu).?.resolveFile(ip),
.func => |ip_index| zcu.funcInfo(ip_index).zir_body_inst.resolveFile(ip),
};
Expand Down Expand Up @@ -3380,7 +3384,7 @@ pub fn addModuleErrorMsg(
defer gpa.free(rt_file_path);
const name = switch (ref.referencer.unwrap()) {
.@"comptime" => "comptime",
.nav_val => |nav| ip.getNav(nav).name.toSlice(ip),
.nav_val, .nav_ty => |nav| ip.getNav(nav).name.toSlice(ip),
.type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip),
.func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip),
};
Expand Down Expand Up @@ -3647,6 +3651,7 @@ fn performAllTheWorkInner(
try comp.queueJob(switch (outdated.unwrap()) {
.func => |f| .{ .analyze_func = f },
.@"comptime",
.nav_ty,
.nav_val,
.type,
=> .{ .analyze_comptime_unit = outdated },
Expand Down Expand Up @@ -3679,7 +3684,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre
return;
}
}
assert(nav.status == .resolved);
assert(nav.status == .fully_resolved);
comp.dispatchCodegenTask(tid, .{ .codegen_nav = nav_index });
},
.codegen_func => |func| {
Expand Down Expand Up @@ -3709,6 +3714,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre

const maybe_err: Zcu.SemaError!void = switch (unit.unwrap()) {
.@"comptime" => |cu| pt.ensureComptimeUnitUpToDate(cu),
.nav_ty => |nav| pt.ensureNavTypeUpToDate(nav),
.nav_val => |nav| pt.ensureNavValUpToDate(nav),
.type => |ty| if (pt.ensureTypeUpToDate(ty)) |_| {} else |err| err,
.func => unreachable,
Expand All @@ -3734,7 +3740,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre
// Tests are always emitted in test binaries. The decl_refs are created by
// Zcu.populateTestFunctions, but this will not queue body analysis, so do
// that now.
try pt.zcu.ensureFuncBodyAnalysisQueued(ip.getNav(nav).status.resolved.val);
try pt.zcu.ensureFuncBodyAnalysisQueued(ip.getNav(nav).status.fully_resolved.val);
}
},
.resolve_type_fully => |ty| {
Expand Down
Loading

0 comments on commit 3afda43

Please sign in to comment.