zig/test/behavior/globals.zig
mlugg 3afda4322c
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
2024-12-24 02:18:41 +00:00

165 lines
5.0 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;
var pos = [2]f32{ 0.0, 0.0 };
test "store to global array" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
try expect(pos[1] == 0.0);
pos = [2]f32{ 0.0, 1.0 };
try expect(pos[1] == 1.0);
}
var vpos = @Vector(2, f32){ 0.0, 0.0 };
test "store to global vector" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
try expect(vpos[1] == 0.0);
vpos = @Vector(2, f32){ 0.0, 1.0 };
try expect(vpos[1] == 1.0);
}
test "slices pointing at the same address as global array." {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const S = struct {
const a = [_]u8{ 1, 2, 3 };
fn checkAddress(s: []const u8) !void {
for (s, 0..) |*i, j| {
try expect(i == &a[j]);
}
}
};
try S.checkAddress(&S.a);
try comptime S.checkAddress(&S.a);
}
test "global loads can affect liveness" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
const S = struct {
const ByRef = struct {
a: u32,
};
var global_ptr: *ByRef = undefined;
fn f() void {
global_ptr.* = .{ .a = 42 };
}
};
var x: S.ByRef = .{ .a = 1 };
S.global_ptr = &x;
const y = x;
S.f();
try std.testing.expect(y.a == 1);
}
test "global const can be self-referential" {
const S = struct {
self: *const @This(),
x: u32,
const foo: @This() = .{ .self = &foo, .x = 123 };
};
try std.testing.expect(S.foo.x == 123);
try std.testing.expect(S.foo.self.x == 123);
try std.testing.expect(S.foo.self.self.x == 123);
try std.testing.expect(S.foo.self == &S.foo);
try std.testing.expect(S.foo.self.self == &S.foo);
}
test "global var can be self-referential" {
const S = struct {
self: *@This(),
x: u32,
var foo: @This() = .{ .self = &foo, .x = undefined };
};
S.foo.x = 123;
try std.testing.expect(S.foo.x == 123);
try std.testing.expect(S.foo.self.x == 123);
try std.testing.expect(S.foo.self == &S.foo);
S.foo.self.x = 456;
try std.testing.expect(S.foo.x == 456);
try std.testing.expect(S.foo.self.x == 456);
try std.testing.expect(S.foo.self == &S.foo);
S.foo.self.self.x = 789;
try std.testing.expect(S.foo.x == 789);
try std.testing.expect(S.foo.self.x == 789);
try std.testing.expect(S.foo.self == &S.foo);
}
test "global const can be indirectly self-referential" {
const S = struct {
other: *const @This(),
x: u32,
const foo: @This() = .{ .other = &bar, .x = 123 };
const bar: @This() = .{ .other = &foo, .x = 456 };
};
try std.testing.expect(S.foo.x == 123);
try std.testing.expect(S.foo.other.x == 456);
try std.testing.expect(S.foo.other.other.x == 123);
try std.testing.expect(S.foo.other.other.other.x == 456);
try std.testing.expect(S.foo.other == &S.bar);
try std.testing.expect(S.foo.other.other == &S.foo);
try std.testing.expect(S.bar.x == 456);
try std.testing.expect(S.bar.other.x == 123);
try std.testing.expect(S.bar.other.other.x == 456);
try std.testing.expect(S.bar.other.other.other.x == 123);
try std.testing.expect(S.bar.other == &S.foo);
try std.testing.expect(S.bar.other.other == &S.bar);
}
test "global var can be indirectly self-referential" {
const S = struct {
other: *@This(),
x: u32,
var foo: @This() = .{ .other = &bar, .x = undefined };
var bar: @This() = .{ .other = &foo, .x = undefined };
};
S.foo.other.x = 123; // bar.x
S.foo.other.other.x = 456; // foo.x
try std.testing.expect(S.foo.x == 456);
try std.testing.expect(S.foo.other.x == 123);
try std.testing.expect(S.foo.other.other.x == 456);
try std.testing.expect(S.foo.other.other.other.x == 123);
try std.testing.expect(S.foo.other == &S.bar);
try std.testing.expect(S.foo.other.other == &S.foo);
S.bar.other.x = 111; // foo.x
S.bar.other.other.x = 222; // bar.x
try std.testing.expect(S.bar.x == 222);
try std.testing.expect(S.bar.other.x == 111);
try std.testing.expect(S.bar.other.other.x == 222);
try std.testing.expect(S.bar.other.other.other.x == 111);
try std.testing.expect(S.bar.other == &S.foo);
try std.testing.expect(S.bar.other.other == &S.bar);
}