commit a0e89c9b46fc91d9b1dfebd02eaae233802e3cbc (tree)
parent 94383d14df77fa638dac14f4b2bda5a2e3f21c5c
Author: Andrew Kelley <andrew@ziglang.org>
Date: Fri, 2 Apr 2021 12:09:38 -0700
Merge remote-tracking branch 'origin/master' into llvm12
Diffstat:
52 files changed, 17120 insertions(+), 11701 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -539,7 +539,7 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/ThreadPool.zig"
"${CMAKE_SOURCE_DIR}/src/TypedValue.zig"
"${CMAKE_SOURCE_DIR}/src/WaitGroup.zig"
- "${CMAKE_SOURCE_DIR}/src/astgen.zig"
+ "${CMAKE_SOURCE_DIR}/src/AstGen.zig"
"${CMAKE_SOURCE_DIR}/src/clang.zig"
"${CMAKE_SOURCE_DIR}/src/clang_options.zig"
"${CMAKE_SOURCE_DIR}/src/clang_options_data.zig"
@@ -591,7 +591,7 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/value.zig"
"${CMAKE_SOURCE_DIR}/src/windows_sdk.zig"
"${CMAKE_SOURCE_DIR}/src/zir.zig"
- "${CMAKE_SOURCE_DIR}/src/zir_sema.zig"
+ "${CMAKE_SOURCE_DIR}/src/Sema.zig"
)
if(MSVC)
diff --git a/doc/docgen.zig b/doc/docgen.zig
@@ -1349,7 +1349,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any
}
const escaped_stderr = try escapeHtml(allocator, result.stderr);
const colored_stderr = try termColor(allocator, escaped_stderr);
- try out.print("<pre><code class=\"shell\">$ zig test {s}.zig{s}\n{s}</code></pre>\n", .{
+ try out.print("<pre><code class=\"shell\">$ zig test {s}.zig {s}\n{s}</code></pre>\n", .{
code.name,
mode_arg,
colored_stderr,
diff --git a/doc/langref.html.in b/doc/langref.html.in
@@ -6594,13 +6594,11 @@ const std = @import("std");
const expect = std.testing.expect;
test "async and await" {
- // Here we have an exception where we do not match an async
- // with an await. The test block is not async and so cannot
- // have a suspend point in it.
- // This is well-defined behavior, and everything is OK here.
- // Note however that there would be no way to collect the
- // return value of amain, if it were something other than void.
- _ = async amain();
+ // The test block is not async and so cannot have a suspend
+ // point in it. By using the nosuspend keyword, we promise that
+ // the code in amain will finish executing without suspending
+ // back to the test block.
+ nosuspend amain();
}
fn amain() void {
@@ -10799,9 +10797,16 @@ fn readU32Be() u32 {}
<pre>{#syntax#}nosuspend{#endsyntax#}</pre>
</td>
<td>
- The {#syntax#}nosuspend{#endsyntax#} keyword.
+ The {#syntax#}nosuspend{#endsyntax#} keyword can be used in front of a block, statement or expression, to mark a scope where no suspension points are reached.
+ In particular, inside a {#syntax#}nosuspend{#endsyntax#} scope:
+ <ul>
+ <li>Using the {#syntax#}suspend{#endsyntax#} keyword results in a compile error.</li>
+ <li>Using {#syntax#}await{#endsyntax#} on a function frame which hasn't completed yet results in safety-checked {#link|Undefined Behavior#}.</li>
+ <li>Calling an async function may result in safety-checked {#link|Undefined Behavior#}, because it's equivalent to <code>await async some_async_fn()</code>, which contains an {#syntax#}await{#endsyntax#}.</li>
+ </ul>
+ Code inside a {#syntax#}nosuspend{#endsyntax#} scope does not cause the enclosing function to become an {#link|async function|Async Functions#}.
<ul>
- <li>TODO add documentation for nosuspend</li>
+ <li>See also {#link|Async Functions#}</li>
</ul>
</td>
</tr>
diff --git a/lib/std/enums.zig b/lib/std/enums.zig
@@ -32,7 +32,7 @@ pub fn EnumFieldStruct(comptime E: type, comptime Data: type, comptime field_def
.fields = fields,
.decls = &[_]std.builtin.TypeInfo.Declaration{},
.is_tuple = false,
- }});
+ } });
}
/// Looks up the supplied fields in the given enum type.
@@ -70,7 +70,7 @@ pub fn values(comptime E: type) []const E {
test "std.enum.values" {
const E = extern enum { a, b, c, d = 0 };
- testing.expectEqualSlices(E, &.{.a, .b, .c, .d}, values(E));
+ testing.expectEqualSlices(E, &.{ .a, .b, .c, .d }, values(E));
}
/// Returns the set of all unique named values in the given enum, in
@@ -82,10 +82,10 @@ pub fn uniqueValues(comptime E: type) []const E {
test "std.enum.uniqueValues" {
const E = extern enum { a, b, c, d = 0, e, f = 3 };
- testing.expectEqualSlices(E, &.{.a, .b, .c, .f}, uniqueValues(E));
+ testing.expectEqualSlices(E, &.{ .a, .b, .c, .f }, uniqueValues(E));
const F = enum { a, b, c };
- testing.expectEqualSlices(F, &.{.a, .b, .c}, uniqueValues(F));
+ testing.expectEqualSlices(F, &.{ .a, .b, .c }, uniqueValues(F));
}
/// Returns the set of all unique field values in the given enum, in
@@ -102,8 +102,7 @@ pub fn uniqueFields(comptime E: type) []const EnumField {
}
var unique_fields: []const EnumField = &[_]EnumField{};
- outer:
- for (raw_fields) |candidate| {
+ outer: for (raw_fields) |candidate| {
for (unique_fields) |u| {
if (u.value == candidate.value)
continue :outer;
@@ -116,28 +115,25 @@ pub fn uniqueFields(comptime E: type) []const EnumField {
}
/// Determines the length of a direct-mapped enum array, indexed by
-/// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive.
+/// @intCast(usize, @enumToInt(enum_value)).
+/// If the enum is non-exhaustive, the resulting length will only be enough
+/// to hold all explicit fields.
/// If the enum contains any fields with values that cannot be represented
/// by usize, a compile error is issued. The max_unused_slots parameter limits
/// the total number of items which have no matching enum key (holes in the enum
/// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots
/// must be at least 3, to allow unused slots 0, 3, and 4.
fn directEnumArrayLen(comptime E: type, comptime max_unused_slots: comptime_int) comptime_int {
- const info = @typeInfo(E).Enum;
- if (!info.is_exhaustive) {
- @compileError("Cannot create direct array of non-exhaustive enum "++@typeName(E));
- }
-
var max_value: comptime_int = -1;
const max_usize: comptime_int = ~@as(usize, 0);
const fields = uniqueFields(E);
for (fields) |f| {
if (f.value < 0) {
- @compileError("Cannot create a direct enum array for "++@typeName(E)++", field ."++f.name++" has a negative value.");
+ @compileError("Cannot create a direct enum array for " ++ @typeName(E) ++ ", field ." ++ f.name ++ " has a negative value.");
}
if (f.value > max_value) {
if (f.value > max_usize) {
- @compileError("Cannot create a direct enum array for "++@typeName(E)++", field ."++f.name++" is larger than the max value of usize.");
+ @compileError("Cannot create a direct enum array for " ++ @typeName(E) ++ ", field ." ++ f.name ++ " is larger than the max value of usize.");
}
max_value = f.value;
}
@@ -147,14 +143,16 @@ fn directEnumArrayLen(comptime E: type, comptime max_unused_slots: comptime_int)
if (unused_slots > max_unused_slots) {
const unused_str = std.fmt.comptimePrint("{d}", .{unused_slots});
const allowed_str = std.fmt.comptimePrint("{d}", .{max_unused_slots});
- @compileError("Cannot create a direct enum array for "++@typeName(E)++". It would have "++unused_str++" unused slots, but only "++allowed_str++" are allowed.");
+ @compileError("Cannot create a direct enum array for " ++ @typeName(E) ++ ". It would have " ++ unused_str ++ " unused slots, but only " ++ allowed_str ++ " are allowed.");
}
return max_value + 1;
}
/// Initializes an array of Data which can be indexed by
-/// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive.
+/// @intCast(usize, @enumToInt(enum_value)).
+/// If the enum is non-exhaustive, the resulting array will only be large enough
+/// to hold all explicit fields.
/// If the enum contains any fields with values that cannot be represented
/// by usize, a compile error is issued. The max_unused_slots parameter limits
/// the total number of items which have no matching enum key (holes in the enum
@@ -243,9 +241,9 @@ pub fn nameCast(comptime E: type, comptime value: anytype) E {
if (@hasField(E, n)) {
return @field(E, n);
}
- @compileError("Enum "++@typeName(E)++" has no field named "++n);
+ @compileError("Enum " ++ @typeName(E) ++ " has no field named " ++ n);
}
- @compileError("Cannot cast from "++@typeName(@TypeOf(value))++" to "++@typeName(E));
+ @compileError("Cannot cast from " ++ @typeName(@TypeOf(value)) ++ " to " ++ @typeName(E));
}
}
@@ -256,7 +254,7 @@ test "std.enums.nameCast" {
testing.expectEqual(A.a, nameCast(A, A.a));
testing.expectEqual(A.a, nameCast(A, B.a));
testing.expectEqual(A.a, nameCast(A, "a"));
- testing.expectEqual(A.a, nameCast(A, @as(*const[1]u8, "a")));
+ testing.expectEqual(A.a, nameCast(A, @as(*const [1]u8, "a")));
testing.expectEqual(A.a, nameCast(A, @as([:0]const u8, "a")));
testing.expectEqual(A.a, nameCast(A, @as([]const u8, "a")));
@@ -398,12 +396,12 @@ pub fn EnumArray(comptime E: type, comptime V: type) type {
pub fn NoExtension(comptime Self: type) type {
return NoExt;
}
-const NoExt = struct{};
+const NoExt = struct {};
/// A set type with an Indexer mapping from keys to indices.
/// Presence or absence is stored as a dense bitfield. This
/// type does no allocation and can be copied by value.
-pub fn IndexedSet(comptime I: type, comptime Ext: fn(type)type) type {
+pub fn IndexedSet(comptime I: type, comptime Ext: fn (type) type) type {
comptime ensureIndexer(I);
return struct {
const Self = @This();
@@ -422,7 +420,7 @@ pub fn IndexedSet(comptime I: type, comptime Ext: fn(type)type) type {
bits: BitSet = BitSet.initEmpty(),
- /// Returns a set containing all possible keys.
+ /// Returns a set containing all possible keys.
pub fn initFull() Self {
return .{ .bits = BitSet.initFull() };
}
@@ -492,7 +490,8 @@ pub fn IndexedSet(comptime I: type, comptime Ext: fn(type)type) type {
pub fn next(self: *Iterator) ?Key {
return if (self.inner.next()) |index|
Indexer.keyForIndex(index)
- else null;
+ else
+ null;
}
};
};
@@ -501,7 +500,7 @@ pub fn IndexedSet(comptime I: type, comptime Ext: fn(type)type) type {
/// A map from keys to values, using an index lookup. Uses a
/// bitfield to track presence and a dense array of values.
/// This type does no allocation and can be copied by value.
-pub fn IndexedMap(comptime I: type, comptime V: type, comptime Ext: fn(type)type) type {
+pub fn IndexedMap(comptime I: type, comptime V: type, comptime Ext: fn (type) type) type {
comptime ensureIndexer(I);
return struct {
const Self = @This();
@@ -652,7 +651,8 @@ pub fn IndexedMap(comptime I: type, comptime V: type, comptime Ext: fn(type)type
.key = Indexer.keyForIndex(index),
.value = &self.values[index],
}
- else null;
+ else
+ null;
}
};
};
@@ -660,7 +660,7 @@ pub fn IndexedMap(comptime I: type, comptime V: type, comptime Ext: fn(type)type
/// A dense array of values, using an indexed lookup.
/// This type does no allocation and can be copied by value.
-pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: fn(type)type) type {
+pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: fn (type) type) type {
comptime ensureIndexer(I);
return struct {
const Self = @This();
@@ -769,9 +769,9 @@ pub fn ensureIndexer(comptime T: type) void {
if (!@hasDecl(T, "count")) @compileError("Indexer must have decl count: usize.");
if (@TypeOf(T.count) != usize) @compileError("Indexer.count must be a usize.");
if (!@hasDecl(T, "indexOf")) @compileError("Indexer.indexOf must be a fn(Key)usize.");
- if (@TypeOf(T.indexOf) != fn(T.Key)usize) @compileError("Indexer must have decl indexOf: fn(Key)usize.");
+ if (@TypeOf(T.indexOf) != fn (T.Key) usize) @compileError("Indexer must have decl indexOf: fn(Key)usize.");
if (!@hasDecl(T, "keyForIndex")) @compileError("Indexer must have decl keyForIndex: fn(usize)Key.");
- if (@TypeOf(T.keyForIndex) != fn(usize)T.Key) @compileError("Indexer.keyForIndex must be a fn(usize)Key.");
+ if (@TypeOf(T.keyForIndex) != fn (usize) T.Key) @compileError("Indexer.keyForIndex must be a fn(usize)Key.");
}
}
@@ -802,14 +802,18 @@ pub fn EnumIndexer(comptime E: type) type {
return struct {
pub const Key = E;
pub const count: usize = 0;
- pub fn indexOf(e: E) usize { unreachable; }
- pub fn keyForIndex(i: usize) E { unreachable; }
+ pub fn indexOf(e: E) usize {
+ unreachable;
+ }
+ pub fn keyForIndex(i: usize) E {
+ unreachable;
+ }
};
}
std.sort.sort(EnumField, &fields, {}, ascByValue);
const min = fields[0].value;
- const max = fields[fields.len-1].value;
- if (max - min == fields.len-1) {
+ const max = fields[fields.len - 1].value;
+ if (max - min == fields.len - 1) {
return struct {
pub const Key = E;
pub const count = fields.len;
@@ -844,7 +848,7 @@ pub fn EnumIndexer(comptime E: type) type {
}
test "std.enums.EnumIndexer dense zeroed" {
- const E = enum{ b = 1, a = 0, c = 2 };
+ const E = enum { b = 1, a = 0, c = 2 };
const Indexer = EnumIndexer(E);
ensureIndexer(Indexer);
testing.expectEqual(E, Indexer.Key);
@@ -908,7 +912,7 @@ test "std.enums.EnumIndexer sparse" {
}
test "std.enums.EnumIndexer repeats" {
- const E = extern enum{ a = -2, c = 6, b = 4, b2 = 4 };
+ const E = extern enum { a = -2, c = 6, b = 4, b2 = 4 };
const Indexer = EnumIndexer(E);
ensureIndexer(Indexer);
testing.expectEqual(E, Indexer.Key);
@@ -957,7 +961,8 @@ test "std.enums.EnumSet" {
}
var mut = Set.init(.{
- .a=true, .c=true,
+ .a = true,
+ .c = true,
});
testing.expectEqual(@as(usize, 2), mut.count());
testing.expectEqual(true, mut.contains(.a));
@@ -986,7 +991,7 @@ test "std.enums.EnumSet" {
testing.expectEqual(@as(?E, null), it.next());
}
- mut.toggleSet(Set.init(.{ .a=true, .b=true }));
+ mut.toggleSet(Set.init(.{ .a = true, .b = true }));
testing.expectEqual(@as(usize, 2), mut.count());
testing.expectEqual(true, mut.contains(.a));
testing.expectEqual(false, mut.contains(.b));
@@ -994,7 +999,7 @@ test "std.enums.EnumSet" {
testing.expectEqual(true, mut.contains(.d));
testing.expectEqual(true, mut.contains(.e)); // aliases a
- mut.setUnion(Set.init(.{ .a=true, .b=true }));
+ mut.setUnion(Set.init(.{ .a = true, .b = true }));
testing.expectEqual(@as(usize, 3), mut.count());
testing.expectEqual(true, mut.contains(.a));
testing.expectEqual(true, mut.contains(.b));
@@ -1009,7 +1014,7 @@ test "std.enums.EnumSet" {
testing.expectEqual(false, mut.contains(.c));
testing.expectEqual(true, mut.contains(.d));
- mut.setIntersection(Set.init(.{ .a=true, .b=true }));
+ mut.setIntersection(Set.init(.{ .a = true, .b = true }));
testing.expectEqual(@as(usize, 1), mut.count());
testing.expectEqual(true, mut.contains(.a));
testing.expectEqual(false, mut.contains(.b));
@@ -1072,7 +1077,7 @@ test "std.enums.EnumArray sized" {
const undef = Array.initUndefined();
var inst = Array.initFill(5);
const inst2 = Array.init(.{ .a = 1, .b = 2, .c = 3, .d = 4 });
- const inst3 = Array.initDefault(6, .{.b = 4, .c = 2});
+ const inst3 = Array.initDefault(6, .{ .b = 4, .c = 2 });
testing.expectEqual(@as(usize, 5), inst.get(.a));
testing.expectEqual(@as(usize, 5), inst.get(.b));
@@ -1272,10 +1277,12 @@ test "std.enums.EnumMap sized" {
var iter = a.iterator();
const Entry = Map.Entry;
testing.expectEqual(@as(?Entry, Entry{
- .key = .b, .value = &a.values[1],
+ .key = .b,
+ .value = &a.values[1],
}), iter.next());
testing.expectEqual(@as(?Entry, Entry{
- .key = .d, .value = &a.values[3],
+ .key = .d,
+ .value = &a.values[3],
}), iter.next());
testing.expectEqual(@as(?Entry, null), iter.next());
}
diff --git a/lib/std/os.zig b/lib/std/os.zig
@@ -3267,6 +3267,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
.WSAEADDRINUSE => return error.AddressInUse,
.WSAEADDRNOTAVAIL => return error.AddressNotAvailable,
.WSAECONNREFUSED => return error.ConnectionRefused,
+ .WSAECONNRESET => return error.ConnectionResetByPeer,
.WSAETIMEDOUT => return error.ConnectionTimedOut,
.WSAEHOSTUNREACH, // TODO: should we return NetworkUnreachable in this case as well?
.WSAENETUNREACH,
@@ -3296,6 +3297,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
EBADF => unreachable, // sockfd is not a valid open file descriptor.
ECONNREFUSED => return error.ConnectionRefused,
+ ECONNRESET => return error.ConnectionResetByPeer,
EFAULT => unreachable, // The socket structure address is outside the user's address space.
EINTR => continue,
EISCONN => unreachable, // The socket is already connected.
diff --git a/lib/std/os/linux/io_uring.zig b/lib/std/os/linux/io_uring.zig
@@ -1353,7 +1353,9 @@ test "timeout (after a relative time)" {
.res = -linux.ETIME,
.flags = 0,
}, cqe);
- testing.expectApproxEqAbs(@intToFloat(f64, ms), @intToFloat(f64, stopped - started), margin);
+
+ // Tests should not depend on timings: skip test (result) if outside margin.
+ if (!std.math.approxEqAbs(f64, ms, @intToFloat(f64, stopped - started), margin)) return error.SkipZigTest;
}
test "timeout (after a number of completions)" {
diff --git a/lib/std/priority_dequeue.zig b/lib/std/priority_dequeue.zig
@@ -0,0 +1,972 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2015-2021 Zig Contributors
+// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
+// The MIT license requires this copyright notice to be included in all copies
+// and substantial portions of the software.
+const std = @import("std.zig");
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
+const warn = std.debug.warn;
+const Order = std.math.Order;
+const testing = std.testing;
+const expect = testing.expect;
+const expectEqual = testing.expectEqual;
+const expectError = testing.expectError;
+
+/// Priority Dequeue for storing generic data. Initialize with `init`.
+pub fn PriorityDequeue(comptime T: type) type {
+ return struct {
+ const Self = @This();
+
+ items: []T,
+ len: usize,
+ allocator: *Allocator,
+ compareFn: fn (a: T, b: T) Order,
+
+ /// Initialize and return a new priority dequeue. Provide `compareFn`
+ /// that returns `Order.lt` when its first argument should
+ /// get min-popped before its second argument, `Order.eq` if the
+ /// arguments are of equal priority, or `Order.gt` if the second
+ /// argument should be min-popped first. Popping the max element works
+ /// in reverse. For example, to make `popMin` return the smallest
+ /// number, provide
+ ///
+ /// `fn lessThan(a: T, b: T) Order { return std.math.order(a, b); }`
+ pub fn init(allocator: *Allocator, compareFn: fn (T, T) Order) Self {
+ return Self{
+ .items = &[_]T{},
+ .len = 0,
+ .allocator = allocator,
+ .compareFn = compareFn,
+ };
+ }
+
+ /// Free memory used by the dequeue.
+ pub fn deinit(self: Self) void {
+ self.allocator.free(self.items);
+ }
+
+ /// Insert a new element, maintaining priority.
+ pub fn add(self: *Self, elem: T) !void {
+ try ensureCapacity(self, self.len + 1);
+ addUnchecked(self, elem);
+ }
+
+ /// Add each element in `items` to the dequeue.
+ pub fn addSlice(self: *Self, items: []const T) !void {
+ try self.ensureCapacity(self.len + items.len);
+ for (items) |e| {
+ self.addUnchecked(e);
+ }
+ }
+
+ fn addUnchecked(self: *Self, elem: T) void {
+ self.items[self.len] = elem;
+
+ if (self.len > 0) {
+ const start = self.getStartForSiftUp(elem, self.len);
+ self.siftUp(start);
+ }
+
+ self.len += 1;
+ }
+
+ fn isMinLayer(index: usize) bool {
+ // In the min-max heap structure:
+ // The first element is on a min layer;
+ // next two are on a max layer;
+ // next four are on a min layer, and so on.
+ const leading_zeros = @clz(usize, index + 1);
+ const highest_set_bit = @bitSizeOf(usize) - 1 - leading_zeros;
+ return (highest_set_bit & 1) == 0;
+ }
+
+ fn nextIsMinLayer(self: Self) bool {
+ return isMinLayer(self.len);
+ }
+
+ const StartIndexAndLayer = struct {
+ index: usize,
+ min_layer: bool,
+ };
+
+ fn getStartForSiftUp(self: Self, child: T, index: usize) StartIndexAndLayer {
+ var child_index = index;
+ var parent_index = parentIndex(child_index);
+ const parent = self.items[parent_index];
+
+ const min_layer = self.nextIsMinLayer();
+ const order = self.compareFn(child, parent);
+ if ((min_layer and order == .gt) or (!min_layer and order == .lt)) {
+ // We must swap the item with it's parent if it is on the "wrong" layer
+ self.items[parent_index] = child;
+ self.items[child_index] = parent;
+ return .{
+ .index = parent_index,
+ .min_layer = !min_layer,
+ };
+ } else {
+ return .{
+ .index = child_index,
+ .min_layer = min_layer,
+ };
+ }
+ }
+
+ fn siftUp(self: *Self, start: StartIndexAndLayer) void {
+ if (start.min_layer) {
+ doSiftUp(self, start.index, .lt);
+ } else {
+ doSiftUp(self, start.index, .gt);
+ }
+ }
+
+ fn doSiftUp(self: *Self, start_index: usize, target_order: Order) void {
+ var child_index = start_index;
+ while (child_index > 2) {
+ var grandparent_index = grandparentIndex(child_index);
+ const child = self.items[child_index];
+ const grandparent = self.items[grandparent_index];
+
+ // If the grandparent is already better or equal, we have gone as far as we need to
+ if (self.compareFn(child, grandparent) != target_order) break;
+
+ // Otherwise swap the item with it's grandparent
+ self.items[grandparent_index] = child;
+ self.items[child_index] = grandparent;
+ child_index = grandparent_index;
+ }
+ }
+
+ /// Look at the smallest element in the dequeue. Returns
+ /// `null` if empty.
+ pub fn peekMin(self: *Self) ?T {
+ return if (self.len > 0) self.items[0] else null;
+ }
+
+ /// Look at the largest element in the dequeue. Returns
+ /// `null` if empty.
+ pub fn peekMax(self: *Self) ?T {
+ if (self.len == 0) return null;
+ if (self.len == 1) return self.items[0];
+ if (self.len == 2) return self.items[1];
+ return self.bestItemAtIndices(1, 2, .gt).item;
+ }
+
+ fn maxIndex(self: Self) ?usize {
+ if (self.len == 0) return null;
+ if (self.len == 1) return 0;
+ if (self.len == 2) return 1;
+ return self.bestItemAtIndices(1, 2, .gt).index;
+ }
+
+ /// Pop the smallest element from the dequeue. Returns
+ /// `null` if empty.
+ pub fn removeMinOrNull(self: *Self) ?T {
+ return if (self.len > 0) self.removeMin() else null;
+ }
+
+ /// Remove and return the smallest element from the
+ /// dequeue.
+ pub fn removeMin(self: *Self) T {
+ return self.removeIndex(0);
+ }
+
+ /// Pop the largest element from the dequeue. Returns
+ /// `null` if empty.
+ pub fn removeMaxOrNull(self: *Self) ?T {
+ return if (self.len > 0) self.removeMax() else null;
+ }
+
+ /// Remove and return the largest element from the
+ /// dequeue.
+ pub fn removeMax(self: *Self) T {
+ return self.removeIndex(self.maxIndex().?);
+ }
+
+ /// Remove and return element at index. Indices are in the
+ /// same order as iterator, which is not necessarily priority
+ /// order.
+ pub fn removeIndex(self: *Self, index: usize) T {
+ assert(self.len > index);
+ const item = self.items[index];
+ const last = self.items[self.len - 1];
+
+ self.items[index] = last;
+ self.len -= 1;
+ siftDown(self, index);
+
+ return item;
+ }
+
+ fn siftDown(self: *Self, index: usize) void {
+ if (isMinLayer(index)) {
+ self.doSiftDown(index, .lt);
+ } else {
+ self.doSiftDown(index, .gt);
+ }
+ }
+
+ fn doSiftDown(self: *Self, start_index: usize, target_order: Order) void {
+ var index = start_index;
+ const half = self.len >> 1;
+ while (true) {
+ const first_grandchild_index = firstGrandchildIndex(index);
+ const last_grandchild_index = first_grandchild_index + 3;
+
+ const elem = self.items[index];
+
+ if (last_grandchild_index < self.len) {
+ // All four grandchildren exist
+ const index2 = first_grandchild_index + 1;
+ const index3 = index2 + 1;
+
+ // Find the best grandchild
+ const best_left = self.bestItemAtIndices(first_grandchild_index, index2, target_order);
+ const best_right = self.bestItemAtIndices(index3, last_grandchild_index, target_order);
+ const best_grandchild = self.bestItem(best_left, best_right, target_order);
+
+ // If the item is better than or equal to its best grandchild, we are done
+ if (self.compareFn(best_grandchild.item, elem) != target_order) return;
+
+ // Otherwise, swap them
+ self.items[best_grandchild.index] = elem;
+ self.items[index] = best_grandchild.item;
+ index = best_grandchild.index;
+
+ // We might need to swap the element with it's parent
+ self.swapIfParentIsBetter(elem, index, target_order);
+ } else {
+ // The children or grandchildren are the last layer
+ const first_child_index = firstChildIndex(index);
+ if (first_child_index > self.len) return;
+
+ const best_descendent = self.bestDescendent(first_child_index, first_grandchild_index, target_order);
+
+ // If the item is better than or equal to its best descendant, we are done
+ if (self.compareFn(best_descendent.item, elem) != target_order) return;
+
+ // Otherwise swap them
+ self.items[best_descendent.index] = elem;
+ self.items[index] = best_descendent.item;
+ index = best_descendent.index;
+
+ // If we didn't swap a grandchild, we are done
+ if (index < first_grandchild_index) return;
+
+ // We might need to swap the element with it's parent
+ self.swapIfParentIsBetter(elem, index, target_order);
+ return;
+ }
+
+ // If we are now in the last layer, we are done
+ if (index >= half) return;
+ }
+ }
+
+ fn swapIfParentIsBetter(self: *Self, child: T, child_index: usize, target_order: Order) void {
+ const parent_index = parentIndex(child_index);
+ const parent = self.items[parent_index];
+
+ if (self.compareFn(parent, child) == target_order) {
+ self.items[parent_index] = child;
+ self.items[child_index] = parent;
+ }
+ }
+
+ const ItemAndIndex = struct {
+ item: T,
+ index: usize,
+ };
+
+ fn getItem(self: Self, index: usize) ItemAndIndex {
+ return .{
+ .item = self.items[index],
+ .index = index,
+ };
+ }
+
+ fn bestItem(self: Self, item1: ItemAndIndex, item2: ItemAndIndex, target_order: Order) ItemAndIndex {
+ if (self.compareFn(item1.item, item2.item) == target_order) {
+ return item1;
+ } else {
+ return item2;
+ }
+ }
+
+ fn bestItemAtIndices(self: Self, index1: usize, index2: usize, target_order: Order) ItemAndIndex {
+ var item1 = self.getItem(index1);
+ var item2 = self.getItem(index2);
+ return self.bestItem(item1, item2, target_order);
+ }
+
+ fn bestDescendent(self: Self, first_child_index: usize, first_grandchild_index: usize, target_order: Order) ItemAndIndex {
+ const second_child_index = first_child_index + 1;
+ if (first_grandchild_index >= self.len) {
+ // No grandchildren, find the best child (second may not exist)
+ if (second_child_index >= self.len) {
+ return .{
+ .item = self.items[first_child_index],
+ .index = first_child_index,
+ };
+ } else {
+ return self.bestItemAtIndices(first_child_index, second_child_index, target_order);
+ }
+ }
+
+ const second_grandchild_index = first_grandchild_index + 1;
+ if (second_grandchild_index >= self.len) {
+ // One grandchild, so we know there is a second child. Compare first grandchild and second child
+ return self.bestItemAtIndices(first_grandchild_index, second_child_index, target_order);
+ }
+
+ const best_left_grandchild_index = self.bestItemAtIndices(first_grandchild_index, second_grandchild_index, target_order).index;
+ const third_grandchild_index = second_grandchild_index + 1;
+ if (third_grandchild_index >= self.len) {
+ // Two grandchildren, and we know the best. Compare this to second child.
+ return self.bestItemAtIndices(best_left_grandchild_index, second_child_index, target_order);
+ } else {
+ // Three grandchildren, compare the min of the first two with the third
+ return self.bestItemAtIndices(best_left_grandchild_index, third_grandchild_index, target_order);
+ }
+ }
+
+ /// Return the number of elements remaining in the dequeue
+ pub fn count(self: Self) usize {
+ return self.len;
+ }
+
+ /// Return the number of elements that can be added to the
+ /// dequeue before more memory is allocated.
+ pub fn capacity(self: Self) usize {
+ return self.items.len;
+ }
+
+ /// Dequeue takes ownership of the passed in slice. The slice must have been
+ /// allocated with `allocator`.
+ /// De-initialize with `deinit`.
+ pub fn fromOwnedSlice(allocator: *Allocator, compareFn: fn (T, T) Order, items: []T) Self {
+ var queue = Self{
+ .items = items,
+ .len = items.len,
+ .allocator = allocator,
+ .compareFn = compareFn,
+ };
+
+ if (queue.len <= 1) return queue;
+
+ const half = (queue.len >> 1) - 1;
+ var i: usize = 0;
+ while (i <= half) : (i += 1) {
+ const index = half - i;
+ queue.siftDown(index);
+ }
+ return queue;
+ }
+
+ pub fn ensureCapacity(self: *Self, new_capacity: usize) !void {
+ var better_capacity = self.capacity();
+ if (better_capacity >= new_capacity) return;
+ while (true) {
+ better_capacity += better_capacity / 2 + 8;
+ if (better_capacity >= new_capacity) break;
+ }
+ self.items = try self.allocator.realloc(self.items, better_capacity);
+ }
+
+ /// Reduce allocated capacity to `new_len`.
+ pub fn shrinkAndFree(self: *Self, new_len: usize) void {
+ assert(new_len <= self.items.len);
+
+ // Cannot shrink to smaller than the current queue size without invalidating the heap property
+ assert(new_len >= self.len);
+
+ self.items = self.allocator.realloc(self.items[0..], new_len) catch |e| switch (e) {
+ error.OutOfMemory => { // no problem, capacity is still correct then.
+ self.items.len = new_len;
+ return;
+ },
+ };
+ self.len = new_len;
+ }
+
+ /// Reduce length to `new_len`.
+ pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
+ assert(new_len <= self.items.len);
+
+ // Cannot shrink to smaller than the current queue size without invalidating the heap property
+ assert(new_len >= self.len);
+
+ self.len = new_len;
+ }
+
+ pub fn update(self: *Self, elem: T, new_elem: T) !void {
+ var old_index: usize = std.mem.indexOfScalar(T, self.items[0..self.len], elem) orelse return error.ElementNotFound;
+ _ = self.removeIndex(old_index);
+ self.addUnchecked(new_elem);
+ }
+
+ pub const Iterator = struct {
+ queue: *PriorityDequeue(T),
+ count: usize,
+
+ pub fn next(it: *Iterator) ?T {
+ if (it.count >= it.queue.len) return null;
+ const out = it.count;
+ it.count += 1;
+ return it.queue.items[out];
+ }
+
+ pub fn reset(it: *Iterator) void {
+ it.count = 0;
+ }
+ };
+
+ /// Return an iterator that walks the queue without consuming
+ /// it. Invalidated if the queue is modified.
+ pub fn iterator(self: *Self) Iterator {
+ return Iterator{
+ .queue = self,
+ .count = 0,
+ };
+ }
+
+ fn dump(self: *Self) void {
+ warn("{{ ", .{});
+ warn("items: ", .{});
+ for (self.items) |e, i| {
+ if (i >= self.len) break;
+ warn("{}, ", .{e});
+ }
+ warn("array: ", .{});
+ for (self.items) |e, i| {
+ warn("{}, ", .{e});
+ }
+ warn("len: {} ", .{self.len});
+ warn("capacity: {}", .{self.capacity()});
+ warn(" }}\n", .{});
+ }
+
+ fn parentIndex(index: usize) usize {
+ return (index - 1) >> 1;
+ }
+
+ fn grandparentIndex(index: usize) usize {
+ return parentIndex(parentIndex(index));
+ }
+
+ fn firstChildIndex(index: usize) usize {
+ return (index << 1) + 1;
+ }
+
+ fn firstGrandchildIndex(index: usize) usize {
+ return firstChildIndex(firstChildIndex(index));
+ }
+ };
+}
+
+fn lessThanComparison(a: u32, b: u32) Order {
+ return std.math.order(a, b);
+}
+
+const PDQ = PriorityDequeue(u32);
+
+test "std.PriorityDequeue: add and remove min" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(54);
+ try queue.add(12);
+ try queue.add(7);
+ try queue.add(23);
+ try queue.add(25);
+ try queue.add(13);
+
+ expectEqual(@as(u32, 7), queue.removeMin());
+ expectEqual(@as(u32, 12), queue.removeMin());
+ expectEqual(@as(u32, 13), queue.removeMin());
+ expectEqual(@as(u32, 23), queue.removeMin());
+ expectEqual(@as(u32, 25), queue.removeMin());
+ expectEqual(@as(u32, 54), queue.removeMin());
+}
+
+test "std.PriorityDequeue: add and remove min structs" {
+ const S = struct {
+ size: u32,
+ };
+ var queue = PriorityDequeue(S).init(testing.allocator, struct {
+ fn order(a: S, b: S) Order {
+ return std.math.order(a.size, b.size);
+ }
+ }.order);
+ defer queue.deinit();
+
+ try queue.add(.{ .size = 54 });
+ try queue.add(.{ .size = 12 });
+ try queue.add(.{ .size = 7 });
+ try queue.add(.{ .size = 23 });
+ try queue.add(.{ .size = 25 });
+ try queue.add(.{ .size = 13 });
+
+ expectEqual(@as(u32, 7), queue.removeMin().size);
+ expectEqual(@as(u32, 12), queue.removeMin().size);
+ expectEqual(@as(u32, 13), queue.removeMin().size);
+ expectEqual(@as(u32, 23), queue.removeMin().size);
+ expectEqual(@as(u32, 25), queue.removeMin().size);
+ expectEqual(@as(u32, 54), queue.removeMin().size);
+}
+
+test "std.PriorityDequeue: add and remove max" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(54);
+ try queue.add(12);
+ try queue.add(7);
+ try queue.add(23);
+ try queue.add(25);
+ try queue.add(13);
+
+ expectEqual(@as(u32, 54), queue.removeMax());
+ expectEqual(@as(u32, 25), queue.removeMax());
+ expectEqual(@as(u32, 23), queue.removeMax());
+ expectEqual(@as(u32, 13), queue.removeMax());
+ expectEqual(@as(u32, 12), queue.removeMax());
+ expectEqual(@as(u32, 7), queue.removeMax());
+}
+
+test "std.PriorityDequeue: add and remove same min" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(1);
+ try queue.add(1);
+ try queue.add(2);
+ try queue.add(2);
+ try queue.add(1);
+ try queue.add(1);
+
+ expectEqual(@as(u32, 1), queue.removeMin());
+ expectEqual(@as(u32, 1), queue.removeMin());
+ expectEqual(@as(u32, 1), queue.removeMin());
+ expectEqual(@as(u32, 1), queue.removeMin());
+ expectEqual(@as(u32, 2), queue.removeMin());
+ expectEqual(@as(u32, 2), queue.removeMin());
+}
+
+test "std.PriorityDequeue: add and remove same max" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(1);
+ try queue.add(1);
+ try queue.add(2);
+ try queue.add(2);
+ try queue.add(1);
+ try queue.add(1);
+
+ expectEqual(@as(u32, 2), queue.removeMax());
+ expectEqual(@as(u32, 2), queue.removeMax());
+ expectEqual(@as(u32, 1), queue.removeMax());
+ expectEqual(@as(u32, 1), queue.removeMax());
+ expectEqual(@as(u32, 1), queue.removeMax());
+ expectEqual(@as(u32, 1), queue.removeMax());
+}
+
+test "std.PriorityDequeue: removeOrNull empty" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ expect(queue.removeMinOrNull() == null);
+ expect(queue.removeMaxOrNull() == null);
+}
+
+test "std.PriorityDequeue: edge case 3 elements" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(9);
+ try queue.add(3);
+ try queue.add(2);
+
+ expectEqual(@as(u32, 2), queue.removeMin());
+ expectEqual(@as(u32, 3), queue.removeMin());
+ expectEqual(@as(u32, 9), queue.removeMin());
+}
+
+test "std.PriorityDequeue: edge case 3 elements max" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(9);
+ try queue.add(3);
+ try queue.add(2);
+
+ expectEqual(@as(u32, 9), queue.removeMax());
+ expectEqual(@as(u32, 3), queue.removeMax());
+ expectEqual(@as(u32, 2), queue.removeMax());
+}
+
+test "std.PriorityDequeue: peekMin" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ expect(queue.peekMin() == null);
+
+ try queue.add(9);
+ try queue.add(3);
+ try queue.add(2);
+
+ expect(queue.peekMin().? == 2);
+ expect(queue.peekMin().? == 2);
+}
+
+test "std.PriorityDequeue: peekMax" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ expect(queue.peekMin() == null);
+
+ try queue.add(9);
+ try queue.add(3);
+ try queue.add(2);
+
+ expect(queue.peekMax().? == 9);
+ expect(queue.peekMax().? == 9);
+}
+
+test "std.PriorityDequeue: sift up with odd indices" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+ const items = [_]u32{ 15, 7, 21, 14, 13, 22, 12, 6, 7, 25, 5, 24, 11, 16, 15, 24, 2, 1 };
+ for (items) |e| {
+ try queue.add(e);
+ }
+
+ const sorted_items = [_]u32{ 1, 2, 5, 6, 7, 7, 11, 12, 13, 14, 15, 15, 16, 21, 22, 24, 24, 25 };
+ for (sorted_items) |e| {
+ expectEqual(e, queue.removeMin());
+ }
+}
+
+test "std.PriorityDequeue: sift up with odd indices" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+ const items = [_]u32{ 15, 7, 21, 14, 13, 22, 12, 6, 7, 25, 5, 24, 11, 16, 15, 24, 2, 1 };
+ for (items) |e| {
+ try queue.add(e);
+ }
+
+ const sorted_items = [_]u32{ 25, 24, 24, 22, 21, 16, 15, 15, 14, 13, 12, 11, 7, 7, 6, 5, 2, 1 };
+ for (sorted_items) |e| {
+ expectEqual(e, queue.removeMax());
+ }
+}
+
+test "std.PriorityDequeue: addSlice min" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+ const items = [_]u32{ 15, 7, 21, 14, 13, 22, 12, 6, 7, 25, 5, 24, 11, 16, 15, 24, 2, 1 };
+ try queue.addSlice(items[0..]);
+
+ const sorted_items = [_]u32{ 1, 2, 5, 6, 7, 7, 11, 12, 13, 14, 15, 15, 16, 21, 22, 24, 24, 25 };
+ for (sorted_items) |e| {
+ expectEqual(e, queue.removeMin());
+ }
+}
+
+test "std.PriorityDequeue: addSlice max" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+ const items = [_]u32{ 15, 7, 21, 14, 13, 22, 12, 6, 7, 25, 5, 24, 11, 16, 15, 24, 2, 1 };
+ try queue.addSlice(items[0..]);
+
+ const sorted_items = [_]u32{ 25, 24, 24, 22, 21, 16, 15, 15, 14, 13, 12, 11, 7, 7, 6, 5, 2, 1 };
+ for (sorted_items) |e| {
+ expectEqual(e, queue.removeMax());
+ }
+}
+
+test "std.PriorityDequeue: fromOwnedSlice trivial case 0" {
+ const items = [0]u32{};
+ const queue_items = try testing.allocator.dupe(u32, &items);
+ var queue = PDQ.fromOwnedSlice(testing.allocator, lessThanComparison, queue_items[0..]);
+ defer queue.deinit();
+ expectEqual(@as(usize, 0), queue.len);
+ expect(queue.removeMinOrNull() == null);
+}
+
+test "std.PriorityDequeue: fromOwnedSlice trivial case 1" {
+ const items = [1]u32{1};
+ const queue_items = try testing.allocator.dupe(u32, &items);
+ var queue = PDQ.fromOwnedSlice(testing.allocator, lessThanComparison, queue_items[0..]);
+ defer queue.deinit();
+
+ expectEqual(@as(usize, 1), queue.len);
+ expectEqual(items[0], queue.removeMin());
+ expect(queue.removeMinOrNull() == null);
+}
+
+test "std.PriorityDequeue: fromOwnedSlice" {
+ const items = [_]u32{ 15, 7, 21, 14, 13, 22, 12, 6, 7, 25, 5, 24, 11, 16, 15, 24, 2, 1 };
+ const queue_items = try testing.allocator.dupe(u32, items[0..]);
+ var queue = PDQ.fromOwnedSlice(testing.allocator, lessThanComparison, queue_items[0..]);
+ defer queue.deinit();
+
+ const sorted_items = [_]u32{ 1, 2, 5, 6, 7, 7, 11, 12, 13, 14, 15, 15, 16, 21, 22, 24, 24, 25 };
+ for (sorted_items) |e| {
+ expectEqual(e, queue.removeMin());
+ }
+}
+
+test "std.PriorityDequeue: update min queue" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(55);
+ try queue.add(44);
+ try queue.add(11);
+ try queue.update(55, 5);
+ try queue.update(44, 4);
+ try queue.update(11, 1);
+ expectEqual(@as(u32, 1), queue.removeMin());
+ expectEqual(@as(u32, 4), queue.removeMin());
+ expectEqual(@as(u32, 5), queue.removeMin());
+}
+
+test "std.PriorityDequeue: update same min queue" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(1);
+ try queue.add(1);
+ try queue.add(2);
+ try queue.add(2);
+ try queue.update(1, 5);
+ try queue.update(2, 4);
+ expectEqual(@as(u32, 1), queue.removeMin());
+ expectEqual(@as(u32, 2), queue.removeMin());
+ expectEqual(@as(u32, 4), queue.removeMin());
+ expectEqual(@as(u32, 5), queue.removeMin());
+}
+
+test "std.PriorityDequeue: update max queue" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(55);
+ try queue.add(44);
+ try queue.add(11);
+ try queue.update(55, 5);
+ try queue.update(44, 1);
+ try queue.update(11, 4);
+
+ expectEqual(@as(u32, 5), queue.removeMax());
+ expectEqual(@as(u32, 4), queue.removeMax());
+ expectEqual(@as(u32, 1), queue.removeMax());
+}
+
+test "std.PriorityDequeue: update same max queue" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(1);
+ try queue.add(1);
+ try queue.add(2);
+ try queue.add(2);
+ try queue.update(1, 5);
+ try queue.update(2, 4);
+ expectEqual(@as(u32, 5), queue.removeMax());
+ expectEqual(@as(u32, 4), queue.removeMax());
+ expectEqual(@as(u32, 2), queue.removeMax());
+ expectEqual(@as(u32, 1), queue.removeMax());
+}
+
+test "std.PriorityDequeue: iterator" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ var map = std.AutoHashMap(u32, void).init(testing.allocator);
+ defer {
+ queue.deinit();
+ map.deinit();
+ }
+
+ const items = [_]u32{ 54, 12, 7, 23, 25, 13 };
+ for (items) |e| {
+ _ = try queue.add(e);
+ _ = try map.put(e, {});
+ }
+
+ var it = queue.iterator();
+ while (it.next()) |e| {
+ _ = map.remove(e);
+ }
+
+ expectEqual(@as(usize, 0), map.count());
+}
+
+test "std.PriorityDequeue: remove at index" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.add(3);
+ try queue.add(2);
+ try queue.add(1);
+
+ var it = queue.iterator();
+ var elem = it.next();
+ var idx: usize = 0;
+ const two_idx = while (elem != null) : (elem = it.next()) {
+ if (elem.? == 2)
+ break idx;
+ idx += 1;
+ } else unreachable;
+
+ expectEqual(queue.removeIndex(two_idx), 2);
+ expectEqual(queue.removeMin(), 1);
+ expectEqual(queue.removeMin(), 3);
+ expectEqual(queue.removeMinOrNull(), null);
+}
+
+test "std.PriorityDequeue: iterator while empty" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ var it = queue.iterator();
+
+ expectEqual(it.next(), null);
+}
+
+test "std.PriorityDequeue: shrinkRetainingCapacity and shrinkAndFree" {
+ var queue = PDQ.init(testing.allocator, lessThanComparison);
+ defer queue.deinit();
+
+ try queue.ensureCapacity(4);
+ expect(queue.capacity() >= 4);
+
+ try queue.add(1);
+ try queue.add(2);
+ try queue.add(3);
+ expect(queue.capacity() >= 4);
+ expectEqual(@as(usize, 3), queue.len);
+
+ queue.shrinkRetainingCapacity(3);
+ expect(queue.capacity() >= 4);
+ expectEqual(@as(usize, 3), queue.len);
+
+ queue.shrinkAndFree(3);
+ expectEqual(@as(usize, 3), queue.capacity());
+ expectEqual(@as(usize, 3), queue.len);
+
+ expectEqual(@as(u32, 3), queue.removeMax());
+ expectEqual(@as(u32, 2), queue.removeMax());
+ expectEqual(@as(u32, 1), queue.removeMax());
+ expect(queue.removeMaxOrNull() == null);
+}
+
+test "std.PriorityDequeue: fuzz testing min" {
+ var prng = std.rand.DefaultPrng.init(0x12345678);
+
+ const test_case_count = 100;
+ const queue_size = 1_000;
+
+ var i: usize = 0;
+ while (i < test_case_count) : (i += 1) {
+ try fuzzTestMin(&prng.random, queue_size);
+ }
+}
+
+fn fuzzTestMin(rng: *std.rand.Random, comptime queue_size: usize) !void {
+ const allocator = testing.allocator;
+ const items = try generateRandomSlice(allocator, rng, queue_size);
+
+ var queue = PDQ.fromOwnedSlice(allocator, lessThanComparison, items);
+ defer queue.deinit();
+
+ var last_removed: ?u32 = null;
+ while (queue.removeMinOrNull()) |next| {
+ if (last_removed) |last| {
+ expect(last <= next);
+ }
+ last_removed = next;
+ }
+}
+
+test "std.PriorityDequeue: fuzz testing max" {
+ var prng = std.rand.DefaultPrng.init(0x87654321);
+
+ const test_case_count = 100;
+ const queue_size = 1_000;
+
+ var i: usize = 0;
+ while (i < test_case_count) : (i += 1) {
+ try fuzzTestMax(&prng.random, queue_size);
+ }
+}
+
+fn fuzzTestMax(rng: *std.rand.Random, queue_size: usize) !void {
+ const allocator = testing.allocator;
+ const items = try generateRandomSlice(allocator, rng, queue_size);
+
+ var queue = PDQ.fromOwnedSlice(testing.allocator, lessThanComparison, items);
+ defer queue.deinit();
+
+ var last_removed: ?u32 = null;
+ while (queue.removeMaxOrNull()) |next| {
+ if (last_removed) |last| {
+ expect(last >= next);
+ }
+ last_removed = next;
+ }
+}
+
+test "std.PriorityDequeue: fuzz testing min and max" {
+ var prng = std.rand.DefaultPrng.init(0x87654321);
+
+ const test_case_count = 100;
+ const queue_size = 1_000;
+
+ var i: usize = 0;
+ while (i < test_case_count) : (i += 1) {
+ try fuzzTestMinMax(&prng.random, queue_size);
+ }
+}
+
+fn fuzzTestMinMax(rng: *std.rand.Random, queue_size: usize) !void {
+ const allocator = testing.allocator;
+ const items = try generateRandomSlice(allocator, rng, queue_size);
+
+ var queue = PDQ.fromOwnedSlice(allocator, lessThanComparison, items);
+ defer queue.deinit();
+
+ var last_min: ?u32 = null;
+ var last_max: ?u32 = null;
+ var i: usize = 0;
+ while (i < queue_size) : (i += 1) {
+ if (i % 2 == 0) {
+ const next = queue.removeMin();
+ if (last_min) |last| {
+ expect(last <= next);
+ }
+ last_min = next;
+ } else {
+ const next = queue.removeMax();
+ if (last_max) |last| {
+ expect(last >= next);
+ }
+ last_max = next;
+ }
+ }
+}
+
+fn generateRandomSlice(allocator: *std.mem.Allocator, rng: *std.rand.Random, size: usize) ![]u32 {
+ var array = std.ArrayList(u32).init(allocator);
+ try array.ensureCapacity(size);
+
+ var i: usize = 0;
+ while (i < size) : (i += 1) {
+ const elem = rng.int(u32);
+ try array.append(elem);
+ }
+
+ return array.toOwnedSlice();
+}
diff --git a/lib/std/priority_queue.zig b/lib/std/priority_queue.zig
@@ -6,6 +6,8 @@
const std = @import("std.zig");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
+const warn = std.debug.warn;
+const Order = std.math.Order;
const testing = std.testing;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
@@ -19,15 +21,17 @@ pub fn PriorityQueue(comptime T: type) type {
items: []T,
len: usize,
allocator: *Allocator,
- compareFn: fn (a: T, b: T) bool,
-
- /// Initialize and return a priority queue. Provide
- /// `compareFn` that returns `true` when its first argument
- /// should get popped before its second argument. For example,
- /// to make `pop` return the minimum value, provide
+ compareFn: fn (a: T, b: T) Order,
+
+ /// Initialize and return a priority queue. Provide `compareFn`
+ /// that returns `Order.lt` when its first argument should
+ /// get popped before its second argument, `Order.eq` if the
+ /// arguments are of equal priority, or `Order.gt` if the second
+ /// argument should be popped first. For example, to make `pop`
+ /// return the smallest number, provide
///
- /// `fn lessThan(a: T, b: T) bool { return a < b; }`
- pub fn init(allocator: *Allocator, compareFn: fn (a: T, b: T) bool) Self {
+ /// `fn lessThan(a: T, b: T) Order { return std.math.order(a, b); }`
+ pub fn init(allocator: *Allocator, compareFn: fn (a: T, b: T) Order) Self {
return Self{
.items = &[_]T{},
.len = 0,
@@ -60,7 +64,7 @@ pub fn PriorityQueue(comptime T: type) type {
const child = self.items[child_index];
const parent = self.items[parent_index];
- if (!self.compareFn(child, parent)) break;
+ if (self.compareFn(child, parent) != .lt) break;
self.items[parent_index] = child;
self.items[child_index] = parent;
@@ -132,14 +136,14 @@ pub fn PriorityQueue(comptime T: type) type {
var smallest = self.items[index];
if (left) |e| {
- if (self.compareFn(e, smallest)) {
+ if (self.compareFn(e, smallest) == .lt) {
smallest_index = left_index;
smallest = e;
}
}
if (right) |e| {
- if (self.compareFn(e, smallest)) {
+ if (self.compareFn(e, smallest) == .lt) {
smallest_index = right_index;
smallest = e;
}
@@ -158,13 +162,16 @@ pub fn PriorityQueue(comptime T: type) type {
/// PriorityQueue takes ownership of the passed in slice. The slice must have been
/// allocated with `allocator`.
/// Deinitialize with `deinit`.
- pub fn fromOwnedSlice(allocator: *Allocator, compareFn: fn (a: T, b: T) bool, items: []T) Self {
+ pub fn fromOwnedSlice(allocator: *Allocator, compareFn: fn (a: T, b: T) Order, items: []T) Self {
var queue = Self{
.items = items,
.len = items.len,
.allocator = allocator,
.compareFn = compareFn,
};
+
+ if (queue.len <= 1) return queue;
+
const half = (queue.len >> 1) - 1;
var i: usize = 0;
while (i <= half) : (i += 1) {
@@ -183,25 +190,40 @@ pub fn PriorityQueue(comptime T: type) type {
self.items = try self.allocator.realloc(self.items, better_capacity);
}
- pub fn resize(self: *Self, new_len: usize) !void {
- try self.ensureCapacity(new_len);
+ /// Reduce allocated capacity to `new_len`.
+ pub fn shrinkAndFree(self: *Self, new_len: usize) void {
+ assert(new_len <= self.items.len);
+
+ // Cannot shrink to smaller than the current queue size without invalidating the heap property
+ assert(new_len >= self.len);
+
+ self.items = self.allocator.realloc(self.items[0..], new_len) catch |e| switch (e) {
+ error.OutOfMemory => { // no problem, capacity is still correct then.
+ self.items.len = new_len;
+ return;
+ },
+ };
self.len = new_len;
}
- pub fn shrink(self: *Self, new_len: usize) void {
- // TODO take advantage of the new realloc semantics
- assert(new_len <= self.len);
+ /// Reduce length to `new_len`.
+ pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void {
+ assert(new_len <= self.items.len);
+
+ // Cannot shrink to smaller than the current queue size without invalidating the heap property
+ assert(new_len >= self.len);
+
self.len = new_len;
}
pub fn update(self: *Self, elem: T, new_elem: T) !void {
- var update_index: usize = std.mem.indexOfScalar(T, self.items, elem) orelse return error.ElementNotFound;
+ var update_index: usize = std.mem.indexOfScalar(T, self.items[0..self.len], elem) orelse return error.ElementNotFound;
const old_elem: T = self.items[update_index];
self.items[update_index] = new_elem;
- if (self.compareFn(new_elem, old_elem)) {
- siftUp(self, update_index);
- } else {
- siftDown(self, update_index);
+ switch (self.compareFn(new_elem, old_elem)) {
+ .lt => siftUp(self, update_index),
+ .gt => siftDown(self, update_index),
+ .eq => {}, // Nothing to do as the items have equal priority
}
}
@@ -248,12 +270,12 @@ pub fn PriorityQueue(comptime T: type) type {
};
}
-fn lessThan(a: u32, b: u32) bool {
- return a < b;
+fn lessThan(a: u32, b: u32) Order {
+ return std.math.order(a, b);
}
-fn greaterThan(a: u32, b: u32) bool {
- return a > b;
+fn greaterThan(a: u32, b: u32) Order {
+ return lessThan(a, b).invert();
}
const PQ = PriorityQueue(u32);
@@ -351,6 +373,26 @@ test "std.PriorityQueue: addSlice" {
}
}
+test "std.PriorityQueue: fromOwnedSlice trivial case 0" {
+ const items = [0]u32{};
+ const queue_items = try testing.allocator.dupe(u32, &items);
+ var queue = PQ.fromOwnedSlice(testing.allocator, lessThan, queue_items[0..]);
+ defer queue.deinit();
+ expectEqual(@as(usize, 0), queue.len);
+ expect(queue.removeOrNull() == null);
+}
+
+test "std.PriorityQueue: fromOwnedSlice trivial case 1" {
+ const items = [1]u32{1};
+ const queue_items = try testing.allocator.dupe(u32, &items);
+ var queue = PQ.fromOwnedSlice(testing.allocator, lessThan, queue_items[0..]);
+ defer queue.deinit();
+
+ expectEqual(@as(usize, 1), queue.len);
+ expectEqual(items[0], queue.remove());
+ expect(queue.removeOrNull() == null);
+}
+
test "std.PriorityQueue: fromOwnedSlice" {
const items = [_]u32{ 15, 7, 21, 14, 13, 22, 12, 6, 7, 25, 5, 24, 11, 16, 15, 24, 2, 1 };
const heap_items = try testing.allocator.dupe(u32, items[0..]);
@@ -453,6 +495,33 @@ test "std.PriorityQueue: iterator while empty" {
expectEqual(it.next(), null);
}
+test "std.PriorityQueue: shrinkRetainingCapacity and shrinkAndFree" {
+ var queue = PQ.init(testing.allocator, lessThan);
+ defer queue.deinit();
+
+ try queue.ensureCapacity(4);
+ expect(queue.capacity() >= 4);
+
+ try queue.add(1);
+ try queue.add(2);
+ try queue.add(3);
+ expect(queue.capacity() >= 4);
+ expectEqual(@as(usize, 3), queue.len);
+
+ queue.shrinkRetainingCapacity(3);
+ expect(queue.capacity() >= 4);
+ expectEqual(@as(usize, 3), queue.len);
+
+ queue.shrinkAndFree(3);
+ expectEqual(@as(usize, 3), queue.capacity());
+ expectEqual(@as(usize, 3), queue.len);
+
+ expectEqual(@as(u32, 1), queue.remove());
+ expectEqual(@as(u32, 2), queue.remove());
+ expectEqual(@as(u32, 3), queue.remove());
+ expect(queue.removeOrNull() == null);
+}
+
test "std.PriorityQueue: update min heap" {
var queue = PQ.init(testing.allocator, lessThan);
defer queue.deinit();
diff --git a/lib/std/rand/Isaac64.zig b/lib/std/rand/Isaac64.zig
@@ -208,3 +208,35 @@ test "isaac64 sequence" {
std.testing.expect(s == r.next());
}
}
+
+test "isaac64 fill" {
+ var r = Isaac64.init(0);
+
+ // from reference implementation
+ const seq = [_]u64{
+ 0xf67dfba498e4937c,
+ 0x84a5066a9204f380,
+ 0xfee34bd5f5514dbb,
+ 0x4d1664739b8f80d6,
+ 0x8607459ab52a14aa,
+ 0x0e78bc5a98529e49,
+ 0xfe5332822ad13777,
+ 0x556c27525e33d01a,
+ 0x08643ca615f3149f,
+ 0xd0771faf3cb04714,
+ 0x30e86f68a37b008d,
+ 0x3074ebc0488a3adf,
+ 0x270645ea7a2790bc,
+ 0x5601a0a8d3763c6a,
+ 0x2f83071f53f325dd,
+ 0xb9090f3d42d2d2ea,
+ };
+
+ for (seq) |s| {
+ var buf0: [8]u8 = undefined;
+ var buf1: [7]u8 = undefined;
+ std.mem.writeIntLittle(u64, &buf0, s);
+ Isaac64.fill(&r.random, &buf1);
+ std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
+ }
+}
diff --git a/lib/std/rand/Pcg.zig b/lib/std/rand/Pcg.zig
@@ -75,7 +75,7 @@ fn fill(r: *Random, buf: []u8) void {
var n = self.next();
while (i < buf.len) : (i += 1) {
buf[i] = @truncate(u8, n);
- n >>= 4;
+ n >>= 8;
}
}
}
@@ -99,3 +99,27 @@ test "pcg sequence" {
std.testing.expect(s == r.next());
}
}
+
+test "pcg fill" {
+ var r = Pcg.init(0);
+ const s0: u64 = 0x9394bf54ce5d79de;
+ const s1: u64 = 0x84e9c579ef59bbf7;
+ r.seedTwo(s0, s1);
+
+ const seq = [_]u32{
+ 2881561918,
+ 3063928540,
+ 1199791034,
+ 2487695858,
+ 1479648952,
+ 3247963454,
+ };
+
+ for (seq) |s| {
+ var buf0: [4]u8 = undefined;
+ var buf1: [3]u8 = undefined;
+ std.mem.writeIntLittle(u32, &buf0, s);
+ Pcg.fill(&r.random, &buf1);
+ std.testing.expect(std.mem.eql(u8, buf0[0..3], buf1[0..]));
+ }
+}
diff --git a/lib/std/rand/Sfc64.zig b/lib/std/rand/Sfc64.zig
@@ -106,3 +106,35 @@ test "Sfc64 sequence" {
std.testing.expectEqual(s, r.next());
}
}
+
+test "Sfc64 fill" {
+ // Unfortunately there does not seem to be an official test sequence.
+ var r = Sfc64.init(0);
+
+ const seq = [_]u64{
+ 0x3acfa029e3cc6041,
+ 0xf5b6515bf2ee419c,
+ 0x1259635894a29b61,
+ 0xb6ae75395f8ebd6,
+ 0x225622285ce302e2,
+ 0x520d28611395cb21,
+ 0xdb909c818901599d,
+ 0x8ffd195365216f57,
+ 0xe8c4ad5e258ac04a,
+ 0x8f8ef2c89fdb63ca,
+ 0xf9865b01d98d8e2f,
+ 0x46555871a65d08ba,
+ 0x66868677c6298fcd,
+ 0x2ce15a7e6329f57d,
+ 0xb2f1833ca91ca79,
+ 0x4b0890ac9bf453ca,
+ };
+
+ for (seq) |s| {
+ var buf0: [8]u8 = undefined;
+ var buf1: [7]u8 = undefined;
+ std.mem.writeIntLittle(u64, &buf0, s);
+ Sfc64.fill(&r.random, &buf1);
+ std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
+ }
+}
diff --git a/lib/std/rand/Xoroshiro128.zig b/lib/std/rand/Xoroshiro128.zig
@@ -131,3 +131,26 @@ test "xoroshiro sequence" {
std.testing.expect(s == r.next());
}
}
+
+test "xoroshiro fill" {
+ var r = Xoroshiro128.init(0);
+ r.s[0] = 0xaeecf86f7878dd75;
+ r.s[1] = 0x01cd153642e72622;
+
+ const seq = [_]u64{
+ 0xb0ba0da5bb600397,
+ 0x18a08afde614dccc,
+ 0xa2635b956a31b929,
+ 0xabe633c971efa045,
+ 0x9ac19f9706ca3cac,
+ 0xf62b426578c1e3fb,
+ };
+
+ for (seq) |s| {
+ var buf0: [8]u8 = undefined;
+ var buf1: [7]u8 = undefined;
+ std.mem.writeIntLittle(u64, &buf0, s);
+ Xoroshiro128.fill(&r.random, &buf1);
+ std.testing.expect(std.mem.eql(u8, buf0[0..7], buf1[0..]));
+ }
+}
diff --git a/lib/std/special/docs/index.html b/lib/std/special/docs/index.html
@@ -515,6 +515,7 @@
</style>
</head>
<body class="canvas">
+ <div style="background-color: darkred; width: 100vw; text-align: center; color: white; padding: 15px 5px;">These docs are experimental. <a style="color: bisque;text-decoration: underline;" href="https://kristoff.it/blog/zig-new-relationship-llvm/">Progress depends on the self-hosted compiler</a>, <a style="color: bisque;text-decoration: underline;" href="https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code">consider reading the stlib source in the meantime</a>.</div>
<div class="flex-main">
<div class="flex-filler"></div>
<div class="flex-left sidebar">
diff --git a/lib/std/std.zig b/lib/std/std.zig
@@ -31,6 +31,7 @@ pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayE
pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice;
pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian;
pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
+pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue;
pub const Progress = @import("Progress.zig");
pub const SemanticVersion = @import("SemanticVersion.zig");
pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
diff --git a/lib/std/zig.zig b/lib/std/zig.zig
@@ -11,7 +11,7 @@ pub const Tokenizer = tokenizer.Tokenizer;
pub const fmtId = @import("zig/fmt.zig").fmtId;
pub const fmtEscapes = @import("zig/fmt.zig").fmtEscapes;
pub const parse = @import("zig/parse.zig").parse;
-pub const parseStringLiteral = @import("zig/string_literal.zig").parse;
+pub const string_literal = @import("zig/string_literal.zig");
pub const ast = @import("zig/ast.zig");
pub const system = @import("zig/system.zig");
pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget;
diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig
@@ -1252,6 +1252,7 @@ pub const Tree = struct {
buffer[0] = data.lhs;
const params = if (data.lhs == 0) buffer[0..0] else buffer[0..1];
return tree.fullFnProto(.{
+ .proto_node = node,
.fn_token = tree.nodes.items(.main_token)[node],
.return_type = data.rhs,
.params = params,
@@ -1267,6 +1268,7 @@ pub const Tree = struct {
const params_range = tree.extraData(data.lhs, Node.SubRange);
const params = tree.extra_data[params_range.start..params_range.end];
return tree.fullFnProto(.{
+ .proto_node = node,
.fn_token = tree.nodes.items(.main_token)[node],
.return_type = data.rhs,
.params = params,
@@ -1283,6 +1285,7 @@ pub const Tree = struct {
buffer[0] = extra.param;
const params = if (extra.param == 0) buffer[0..0] else buffer[0..1];
return tree.fullFnProto(.{
+ .proto_node = node,
.fn_token = tree.nodes.items(.main_token)[node],
.return_type = data.rhs,
.params = params,
@@ -1298,6 +1301,7 @@ pub const Tree = struct {
const extra = tree.extraData(data.lhs, Node.FnProto);
const params = tree.extra_data[extra.params_start..extra.params_end];
return tree.fullFnProto(.{
+ .proto_node = node,
.fn_token = tree.nodes.items(.main_token)[node],
.return_type = data.rhs,
.params = params,
@@ -1430,7 +1434,7 @@ pub const Tree = struct {
.ast = .{
.lbracket = tree.nodes.items(.main_token)[node],
.elem_count = data.lhs,
- .sentinel = null,
+ .sentinel = 0,
.elem_type = data.rhs,
},
};
@@ -1440,6 +1444,7 @@ pub const Tree = struct {
assert(tree.nodes.items(.tag)[node] == .array_type_sentinel);
const data = tree.nodes.items(.data)[node];
const extra = tree.extraData(data.rhs, Node.ArrayTypeSentinel);
+ assert(extra.sentinel != 0);
return .{
.ast = .{
.lbracket = tree.nodes.items(.main_token)[node],
@@ -2119,6 +2124,7 @@ pub const full = struct {
ast: Ast,
pub const Ast = struct {
+ proto_node: Node.Index,
fn_token: TokenIndex,
return_type: Node.Index,
params: []const Node.Index,
@@ -2262,7 +2268,7 @@ pub const full = struct {
pub const Ast = struct {
lbracket: TokenIndex,
elem_count: Node.Index,
- sentinel: ?Node.Index,
+ sentinel: Node.Index,
elem_type: Node.Index,
};
};
@@ -2549,9 +2555,9 @@ pub const Node = struct {
@"await",
/// `?lhs`. rhs unused. main_token is the `?`.
optional_type,
- /// `[lhs]rhs`. lhs can be omitted to make it a slice.
+ /// `[lhs]rhs`.
array_type,
- /// `[lhs:a]b`. `array_type_sentinel[rhs]`.
+ /// `[lhs:a]b`. `ArrayTypeSentinel[rhs]`.
array_type_sentinel,
/// `[*]align(lhs) rhs`. lhs can be omitted.
/// `*align(lhs) rhs`. lhs can be omitted.
diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig
@@ -59,10 +59,7 @@ pub fn parse(gpa: *Allocator, source: []const u8) Allocator.Error!Tree {
parser.nodes.appendAssumeCapacity(.{
.tag = .root,
.main_token = 0,
- .data = .{
- .lhs = undefined,
- .rhs = undefined,
- },
+ .data = undefined,
});
const root_members = try parser.parseContainerMembers();
const root_decls = try root_members.toSpan(&parser);
@@ -139,6 +136,16 @@ const Parser = struct {
return result;
}
+ fn setNode(p: *Parser, i: usize, elem: ast.NodeList.Elem) Node.Index {
+ p.nodes.set(i, elem);
+ return @intCast(Node.Index, i);
+ }
+
+ fn reserveNode(p: *Parser) !usize {
+ try p.nodes.resize(p.gpa, p.nodes.len + 1);
+ return p.nodes.len - 1;
+ }
+
fn addExtra(p: *Parser, extra: anytype) Allocator.Error!Node.Index {
const fields = std.meta.fields(@TypeOf(extra));
try p.extra_data.ensureCapacity(p.gpa, p.extra_data.items.len + fields.len);
@@ -554,9 +561,10 @@ const Parser = struct {
return fn_proto;
},
.l_brace => {
+ const fn_decl_index = try p.reserveNode();
const body_block = try p.parseBlock();
assert(body_block != 0);
- return p.addNode(.{
+ return p.setNode(fn_decl_index, .{
.tag = .fn_decl,
.main_token = p.nodes.items(.main_token)[fn_proto],
.data = .{
@@ -634,6 +642,10 @@ const Parser = struct {
/// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? CallConv? EXCLAMATIONMARK? (Keyword_anytype / TypeExpr)
fn parseFnProto(p: *Parser) !Node.Index {
const fn_token = p.eatToken(.keyword_fn) orelse return null_node;
+
+ // We want the fn proto node to be before its children in the array.
+ const fn_proto_index = try p.reserveNode();
+
_ = p.eatToken(.identifier);
const params = try p.parseParamDeclList();
defer params.deinit(p.gpa);
@@ -651,7 +663,7 @@ const Parser = struct {
if (align_expr == 0 and section_expr == 0 and callconv_expr == 0) {
switch (params) {
- .zero_or_one => |param| return p.addNode(.{
+ .zero_or_one => |param| return p.setNode(fn_proto_index, .{
.tag = .fn_proto_simple,
.main_token = fn_token,
.data = .{
@@ -661,7 +673,7 @@ const Parser = struct {
}),
.multi => |list| {
const span = try p.listToSpan(list);
- return p.addNode(.{
+ return p.setNode(fn_proto_index, .{
.tag = .fn_proto_multi,
.main_token = fn_token,
.data = .{
@@ -676,7 +688,7 @@ const Parser = struct {
}
}
switch (params) {
- .zero_or_one => |param| return p.addNode(.{
+ .zero_or_one => |param| return p.setNode(fn_proto_index, .{
.tag = .fn_proto_one,
.main_token = fn_token,
.data = .{
@@ -691,7 +703,7 @@ const Parser = struct {
}),
.multi => |list| {
const span = try p.listToSpan(list);
- return p.addNode(.{
+ return p.setNode(fn_proto_index, .{
.tag = .fn_proto,
.main_token = fn_token,
.data = .{
diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig
@@ -717,9 +717,9 @@ fn renderArrayType(
ais.pushIndentNextLine();
try renderToken(ais, tree, array_type.ast.lbracket, inner_space); // lbracket
try renderExpression(gpa, ais, tree, array_type.ast.elem_count, inner_space);
- if (array_type.ast.sentinel) |sentinel| {
- try renderToken(ais, tree, tree.firstToken(sentinel) - 1, inner_space); // colon
- try renderExpression(gpa, ais, tree, sentinel, inner_space);
+ if (array_type.ast.sentinel != 0) {
+ try renderToken(ais, tree, tree.firstToken(array_type.ast.sentinel) - 1, inner_space); // colon
+ try renderExpression(gpa, ais, tree, array_type.ast.sentinel, inner_space);
}
ais.popIndent();
try renderToken(ais, tree, rbracket, .none); // rbracket
diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig
@@ -6,112 +6,143 @@
const std = @import("../std.zig");
const assert = std.debug.assert;
-const State = enum {
- Start,
- Backslash,
-};
-
pub const ParseError = error{
OutOfMemory,
+ InvalidStringLiteral,
+};
- /// When this is returned, index will be the position of the character.
- InvalidCharacter,
+pub const Result = union(enum) {
+ success,
+ /// Found an invalid character at this index.
+ invalid_character: usize,
+ /// Expected hex digits at this index.
+ expected_hex_digits: usize,
+ /// Invalid hex digits at this index.
+ invalid_hex_escape: usize,
+ /// Invalid unicode escape at this index.
+ invalid_unicode_escape: usize,
+ /// The left brace at this index is missing a matching right brace.
+ missing_matching_rbrace: usize,
+ /// Expected unicode digits at this index.
+ expected_unicode_digits: usize,
};
-/// caller owns returned memory
-pub fn parse(
- allocator: *std.mem.Allocator,
- bytes: []const u8,
- bad_index: *usize, // populated if error.InvalidCharacter is returned
-) ParseError![]u8 {
+/// Parses `bytes` as a Zig string literal and appends the result to `buf`.
+/// Asserts `bytes` has '"' at beginning and end.
+pub fn parseAppend(buf: *std.ArrayList(u8), bytes: []const u8) error{OutOfMemory}!Result {
assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"');
+ const slice = bytes[1..];
- var list = std.ArrayList(u8).init(allocator);
- errdefer list.deinit();
+ const prev_len = buf.items.len;
+ try buf.ensureCapacity(prev_len + slice.len - 1);
+ errdefer buf.shrinkRetainingCapacity(prev_len);
- const slice = bytes[1..];
- try list.ensureCapacity(slice.len - 1);
+ const State = enum {
+ Start,
+ Backslash,
+ };
var state = State.Start;
var index: usize = 0;
- while (index < slice.len) : (index += 1) {
+ while (true) : (index += 1) {
const b = slice[index];
switch (state) {
State.Start => switch (b) {
'\\' => state = State.Backslash,
'\n' => {
- bad_index.* = index;
- return error.InvalidCharacter;
+ return Result{ .invalid_character = index };
},
- '"' => return list.toOwnedSlice(),
- else => try list.append(b),
+ '"' => return Result.success,
+ else => try buf.append(b),
},
State.Backslash => switch (b) {
'n' => {
- try list.append('\n');
+ try buf.append('\n');
state = State.Start;
},
'r' => {
- try list.append('\r');
+ try buf.append('\r');
state = State.Start;
},
'\\' => {
- try list.append('\\');
+ try buf.append('\\');
state = State.Start;
},
't' => {
- try list.append('\t');
+ try buf.append('\t');
state = State.Start;
},
'\'' => {
- try list.append('\'');
+ try buf.append('\'');
state = State.Start;
},
'"' => {
- try list.append('"');
+ try buf.append('"');
state = State.Start;
},
'x' => {
// TODO: add more/better/broader tests for this.
const index_continue = index + 3;
- if (slice.len >= index_continue)
- if (std.fmt.parseUnsigned(u8, slice[index + 1 .. index_continue], 16)) |char| {
- try list.append(char);
- state = State.Start;
- index = index_continue - 1; // loop-header increments again
- continue;
- } else |_| {};
-
- bad_index.* = index;
- return error.InvalidCharacter;
+ if (slice.len < index_continue) {
+ return Result{ .expected_hex_digits = index };
+ }
+ if (std.fmt.parseUnsigned(u8, slice[index + 1 .. index_continue], 16)) |byte| {
+ try buf.append(byte);
+ state = State.Start;
+ index = index_continue - 1; // loop-header increments again
+ } else |err| switch (err) {
+ error.Overflow => unreachable, // 2 digits base 16 fits in a u8.
+ error.InvalidCharacter => {
+ return Result{ .invalid_hex_escape = index + 1 };
+ },
+ }
},
'u' => {
// TODO: add more/better/broader tests for this.
- if (slice.len > index + 2 and slice[index + 1] == '{')
+ // TODO: we are already inside a nice, clean state machine... use it
+ // instead of this hacky code.
+ if (slice.len > index + 2 and slice[index + 1] == '{') {
if (std.mem.indexOfScalarPos(u8, slice[0..std.math.min(index + 9, slice.len)], index + 3, '}')) |index_end| {
const hex_str = slice[index + 2 .. index_end];
if (std.fmt.parseUnsigned(u32, hex_str, 16)) |uint| {
if (uint <= 0x10ffff) {
- try list.appendSlice(std.mem.toBytes(uint)[0..]);
+ try buf.appendSlice(std.mem.toBytes(uint)[0..]);
state = State.Start;
index = index_end; // loop-header increments
continue;
}
- } else |_| {}
- };
-
- bad_index.* = index;
- return error.InvalidCharacter;
+ } else |err| switch (err) {
+ error.Overflow => unreachable,
+ error.InvalidCharacter => {
+ return Result{ .invalid_unicode_escape = index + 1 };
+ },
+ }
+ } else {
+ return Result{ .missing_matching_rbrace = index + 1 };
+ }
+ } else {
+ return Result{ .expected_unicode_digits = index };
+ }
},
else => {
- bad_index.* = index;
- return error.InvalidCharacter;
+ return Result{ .invalid_character = index };
},
},
}
+ } else unreachable; // TODO should not need else unreachable on while(true)
+}
+
+/// Higher level API. Does not return extra info about parse errors.
+/// Caller owns returned memory.
+pub fn parseAlloc(allocator: *std.mem.Allocator, bytes: []const u8) ParseError![]u8 {
+ var buf = std.ArrayList(u8).init(allocator);
+ defer buf.deinit();
+
+ switch (try parseAppend(&buf, bytes)) {
+ .success => return buf.toOwnedSlice(),
+ else => return error.InvalidStringLiteral,
}
- unreachable;
}
test "parse" {
@@ -121,9 +152,8 @@ test "parse" {
var fixed_buf_mem: [32]u8 = undefined;
var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(fixed_buf_mem[0..]);
var alloc = &fixed_buf_alloc.allocator;
- var bad_index: usize = undefined;
- expect(eql(u8, "foo", try parse(alloc, "\"foo\"", &bad_index)));
- expect(eql(u8, "foo", try parse(alloc, "\"f\x6f\x6f\"", &bad_index)));
- expect(eql(u8, "f💯", try parse(alloc, "\"f\u{1f4af}\"", &bad_index)));
+ expect(eql(u8, "foo", try parseAlloc(alloc, "\"foo\"")));
+ expect(eql(u8, "foo", try parseAlloc(alloc, "\"f\x6f\x6f\"")));
+ expect(eql(u8, "f💯", try parseAlloc(alloc, "\"f\u{1f4af}\"")));
}
diff --git a/src/AstGen.zig b/src/AstGen.zig
@@ -0,0 +1,4450 @@
+//! A Work-In-Progress `zir.Code`. This is a shared parent of all
+//! `GenZir` scopes. Once the `zir.Code` is produced, this struct
+//! is deinitialized.
+//! The `GenZir.finish` function converts this to a `zir.Code`.
+
+const AstGen = @This();
+
+const std = @import("std");
+const ast = std.zig.ast;
+const mem = std.mem;
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
+const ArrayListUnmanaged = std.ArrayListUnmanaged;
+
+const Value = @import("value.zig").Value;
+const Type = @import("type.zig").Type;
+const TypedValue = @import("TypedValue.zig");
+const zir = @import("zir.zig");
+const Module = @import("Module.zig");
+const trace = @import("tracy.zig").trace;
+const Scope = Module.Scope;
+const GenZir = Scope.GenZir;
+const InnerError = Module.InnerError;
+const Decl = Module.Decl;
+const LazySrcLoc = Module.LazySrcLoc;
+const BuiltinFn = @import("BuiltinFn.zig");
+
+instructions: std.MultiArrayList(zir.Inst) = .{},
+string_bytes: ArrayListUnmanaged(u8) = .{},
+extra: ArrayListUnmanaged(u32) = .{},
+decl_map: std.StringArrayHashMapUnmanaged(void) = .{},
+decls: ArrayListUnmanaged(*Decl) = .{},
+/// The end of special indexes. `zir.Inst.Ref` subtracts against this number to convert
+/// to `zir.Inst.Index`. The default here is correct if there are 0 parameters.
+ref_start_index: u32 = zir.Inst.Ref.typed_value_map.len,
+mod: *Module,
+decl: *Decl,
+arena: *Allocator,
+
+/// Call `deinit` on the result.
+pub fn init(mod: *Module, decl: *Decl, arena: *Allocator) !AstGen {
+ var astgen: AstGen = .{
+ .mod = mod,
+ .decl = decl,
+ .arena = arena,
+ };
+ // Must be a block instruction at index 0 with the root body.
+ try astgen.instructions.append(mod.gpa, .{
+ .tag = .block,
+ .data = .{ .pl_node = .{
+ .src_node = 0,
+ .payload_index = undefined,
+ } },
+ });
+ return astgen;
+}
+
+pub fn addExtra(astgen: *AstGen, extra: anytype) Allocator.Error!u32 {
+ const fields = std.meta.fields(@TypeOf(extra));
+ try astgen.extra.ensureCapacity(astgen.mod.gpa, astgen.extra.items.len + fields.len);
+ return addExtraAssumeCapacity(astgen, extra);
+}
+
+pub fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 {
+ const fields = std.meta.fields(@TypeOf(extra));
+ const result = @intCast(u32, astgen.extra.items.len);
+ inline for (fields) |field| {
+ astgen.extra.appendAssumeCapacity(switch (field.field_type) {
+ u32 => @field(extra, field.name),
+ zir.Inst.Ref => @enumToInt(@field(extra, field.name)),
+ else => @compileError("bad field type"),
+ });
+ }
+ return result;
+}
+
+pub fn appendRefs(astgen: *AstGen, refs: []const zir.Inst.Ref) !void {
+ const coerced = @bitCast([]const u32, refs);
+ return astgen.extra.appendSlice(astgen.mod.gpa, coerced);
+}
+
+pub fn appendRefsAssumeCapacity(astgen: *AstGen, refs: []const zir.Inst.Ref) void {
+ const coerced = @bitCast([]const u32, refs);
+ astgen.extra.appendSliceAssumeCapacity(coerced);
+}
+
+pub fn refIsNoReturn(astgen: AstGen, inst_ref: zir.Inst.Ref) bool {
+ if (inst_ref == .unreachable_value) return true;
+ if (astgen.refToIndex(inst_ref)) |inst_index| {
+ return astgen.instructions.items(.tag)[inst_index].isNoReturn();
+ }
+ return false;
+}
+
+pub fn indexToRef(astgen: AstGen, inst: zir.Inst.Index) zir.Inst.Ref {
+ return @intToEnum(zir.Inst.Ref, astgen.ref_start_index + inst);
+}
+
+pub fn refToIndex(astgen: AstGen, inst: zir.Inst.Ref) ?zir.Inst.Index {
+ const ref_int = @enumToInt(inst);
+ if (ref_int >= astgen.ref_start_index) {
+ return ref_int - astgen.ref_start_index;
+ } else {
+ return null;
+ }
+}
+
+pub fn deinit(astgen: *AstGen) void {
+ const gpa = astgen.mod.gpa;
+ astgen.instructions.deinit(gpa);
+ astgen.extra.deinit(gpa);
+ astgen.string_bytes.deinit(gpa);
+ astgen.decl_map.deinit(gpa);
+ astgen.decls.deinit(gpa);
+}
+
+pub const ResultLoc = union(enum) {
+ /// The expression is the right-hand side of assignment to `_`. Only the side-effects of the
+ /// expression should be generated. The result instruction from the expression must
+ /// be ignored.
+ discard,
+ /// The expression has an inferred type, and it will be evaluated as an rvalue.
+ none,
+ /// The expression must generate a pointer rather than a value. For example, the left hand side
+ /// of an assignment uses this kind of result location.
+ ref,
+ /// The expression will be coerced into this type, but it will be evaluated as an rvalue.
+ ty: zir.Inst.Ref,
+ /// The expression must store its result into this typed pointer. The result instruction
+ /// from the expression must be ignored.
+ ptr: zir.Inst.Ref,
+ /// The expression must store its result into this allocation, which has an inferred type.
+ /// The result instruction from the expression must be ignored.
+ /// Always an instruction with tag `alloc_inferred`.
+ inferred_ptr: zir.Inst.Ref,
+ /// There is a pointer for the expression to store its result into, however, its type
+ /// is inferred based on peer type resolution for a `zir.Inst.Block`.
+ /// The result instruction from the expression must be ignored.
+ block_ptr: *GenZir,
+
+ pub const Strategy = struct {
+ elide_store_to_block_ptr_instructions: bool,
+ tag: Tag,
+
+ pub const Tag = enum {
+ /// Both branches will use break_void; result location is used to communicate the
+ /// result instruction.
+ break_void,
+ /// Use break statements to pass the block result value, and call rvalue() at
+ /// the end depending on rl. Also elide the store_to_block_ptr instructions
+ /// depending on rl.
+ break_operand,
+ };
+ };
+
+ fn strategy(rl: ResultLoc, block_scope: *GenZir) Strategy {
+ var elide_store_to_block_ptr_instructions = false;
+ switch (rl) {
+ // In this branch there will not be any store_to_block_ptr instructions.
+ .discard, .none, .ty, .ref => return .{
+ .tag = .break_operand,
+ .elide_store_to_block_ptr_instructions = false,
+ },
+ // The pointer got passed through to the sub-expressions, so we will use
+ // break_void here.
+ // In this branch there will not be any store_to_block_ptr instructions.
+ .ptr => return .{
+ .tag = .break_void,
+ .elide_store_to_block_ptr_instructions = false,
+ },
+ .inferred_ptr, .block_ptr => {
+ if (block_scope.rvalue_rl_count == block_scope.break_count) {
+ // Neither prong of the if consumed the result location, so we can
+ // use break instructions to create an rvalue.
+ return .{
+ .tag = .break_operand,
+ .elide_store_to_block_ptr_instructions = true,
+ };
+ } else {
+ // Allow the store_to_block_ptr instructions to remain so that
+ // semantic analysis can turn them into bitcasts.
+ return .{
+ .tag = .break_void,
+ .elide_store_to_block_ptr_instructions = false,
+ };
+ }
+ },
+ }
+ }
+};
+
+pub fn typeExpr(gz: *GenZir, scope: *Scope, type_node: ast.Node.Index) InnerError!zir.Inst.Ref {
+ return expr(gz, scope, .{ .ty = .type_type }, type_node);
+}
+
+fn lvalExpr(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const node_tags = tree.nodes.items(.tag);
+ const main_tokens = tree.nodes.items(.main_token);
+ switch (node_tags[node]) {
+ .root => unreachable,
+ .@"usingnamespace" => unreachable,
+ .test_decl => unreachable,
+ .global_var_decl => unreachable,
+ .local_var_decl => unreachable,
+ .simple_var_decl => unreachable,
+ .aligned_var_decl => unreachable,
+ .switch_case => unreachable,
+ .switch_case_one => unreachable,
+ .container_field_init => unreachable,
+ .container_field_align => unreachable,
+ .container_field => unreachable,
+ .asm_output => unreachable,
+ .asm_input => unreachable,
+
+ .assign,
+ .assign_bit_and,
+ .assign_bit_or,
+ .assign_bit_shift_left,
+ .assign_bit_shift_right,
+ .assign_bit_xor,
+ .assign_div,
+ .assign_sub,
+ .assign_sub_wrap,
+ .assign_mod,
+ .assign_add,
+ .assign_add_wrap,
+ .assign_mul,
+ .assign_mul_wrap,
+ .add,
+ .add_wrap,
+ .sub,
+ .sub_wrap,
+ .mul,
+ .mul_wrap,
+ .div,
+ .mod,
+ .bit_and,
+ .bit_or,
+ .bit_shift_left,
+ .bit_shift_right,
+ .bit_xor,
+ .bang_equal,
+ .equal_equal,
+ .greater_than,
+ .greater_or_equal,
+ .less_than,
+ .less_or_equal,
+ .array_cat,
+ .array_mult,
+ .bool_and,
+ .bool_or,
+ .@"asm",
+ .asm_simple,
+ .string_literal,
+ .integer_literal,
+ .call,
+ .call_comma,
+ .async_call,
+ .async_call_comma,
+ .call_one,
+ .call_one_comma,
+ .async_call_one,
+ .async_call_one_comma,
+ .unreachable_literal,
+ .@"return",
+ .@"if",
+ .if_simple,
+ .@"while",
+ .while_simple,
+ .while_cont,
+ .bool_not,
+ .address_of,
+ .float_literal,
+ .undefined_literal,
+ .true_literal,
+ .false_literal,
+ .null_literal,
+ .optional_type,
+ .block,
+ .block_semicolon,
+ .block_two,
+ .block_two_semicolon,
+ .@"break",
+ .ptr_type_aligned,
+ .ptr_type_sentinel,
+ .ptr_type,
+ .ptr_type_bit_range,
+ .array_type,
+ .array_type_sentinel,
+ .enum_literal,
+ .multiline_string_literal,
+ .char_literal,
+ .@"defer",
+ .@"errdefer",
+ .@"catch",
+ .error_union,
+ .merge_error_sets,
+ .switch_range,
+ .@"await",
+ .bit_not,
+ .negation,
+ .negation_wrap,
+ .@"resume",
+ .@"try",
+ .slice,
+ .slice_open,
+ .slice_sentinel,
+ .array_init_one,
+ .array_init_one_comma,
+ .array_init_dot_two,
+ .array_init_dot_two_comma,
+ .array_init_dot,
+ .array_init_dot_comma,
+ .array_init,
+ .array_init_comma,
+ .struct_init_one,
+ .struct_init_one_comma,
+ .struct_init_dot_two,
+ .struct_init_dot_two_comma,
+ .struct_init_dot,
+ .struct_init_dot_comma,
+ .struct_init,
+ .struct_init_comma,
+ .@"switch",
+ .switch_comma,
+ .@"for",
+ .for_simple,
+ .@"suspend",
+ .@"continue",
+ .@"anytype",
+ .fn_proto_simple,
+ .fn_proto_multi,
+ .fn_proto_one,
+ .fn_proto,
+ .fn_decl,
+ .anyframe_type,
+ .anyframe_literal,
+ .error_set_decl,
+ .container_decl,
+ .container_decl_trailing,
+ .container_decl_two,
+ .container_decl_two_trailing,
+ .container_decl_arg,
+ .container_decl_arg_trailing,
+ .tagged_union,
+ .tagged_union_trailing,
+ .tagged_union_two,
+ .tagged_union_two_trailing,
+ .tagged_union_enum_tag,
+ .tagged_union_enum_tag_trailing,
+ .@"comptime",
+ .@"nosuspend",
+ .error_value,
+ => return gz.astgen.mod.failNode(scope, node, "invalid left-hand side to assignment", .{}),
+
+ .builtin_call,
+ .builtin_call_comma,
+ .builtin_call_two,
+ .builtin_call_two_comma,
+ => {
+ const builtin_token = main_tokens[node];
+ const builtin_name = tree.tokenSlice(builtin_token);
+ // If the builtin is an invalid name, we don't cause an error here; instead
+ // let it pass, and the error will be "invalid builtin function" later.
+ if (BuiltinFn.list.get(builtin_name)) |info| {
+ if (!info.allows_lvalue) {
+ return gz.astgen.mod.failNode(scope, node, "invalid left-hand side to assignment", .{});
+ }
+ }
+ },
+
+ // These can be assigned to.
+ .unwrap_optional,
+ .deref,
+ .field_access,
+ .array_access,
+ .identifier,
+ .grouped_expression,
+ .@"orelse",
+ => {},
+ }
+ return expr(gz, scope, .ref, node);
+}
+
+/// Turn Zig AST into untyped ZIR istructions.
+/// When `rl` is discard, ptr, inferred_ptr, or inferred_ptr, the
+/// result instruction can be used to inspect whether it is isNoReturn() but that is it,
+/// it must otherwise not be used.
+pub fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref {
+ const mod = gz.astgen.mod;
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const token_tags = tree.tokens.items(.tag);
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+
+ switch (node_tags[node]) {
+ .root => unreachable, // Top-level declaration.
+ .@"usingnamespace" => unreachable, // Top-level declaration.
+ .test_decl => unreachable, // Top-level declaration.
+ .container_field_init => unreachable, // Top-level declaration.
+ .container_field_align => unreachable, // Top-level declaration.
+ .container_field => unreachable, // Top-level declaration.
+ .fn_decl => unreachable, // Top-level declaration.
+
+ .global_var_decl => unreachable, // Handled in `blockExpr`.
+ .local_var_decl => unreachable, // Handled in `blockExpr`.
+ .simple_var_decl => unreachable, // Handled in `blockExpr`.
+ .aligned_var_decl => unreachable, // Handled in `blockExpr`.
+
+ .switch_case => unreachable, // Handled in `switchExpr`.
+ .switch_case_one => unreachable, // Handled in `switchExpr`.
+ .switch_range => unreachable, // Handled in `switchExpr`.
+
+ .asm_output => unreachable, // Handled in `asmExpr`.
+ .asm_input => unreachable, // Handled in `asmExpr`.
+
+ .assign => {
+ try assign(gz, scope, node);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_bit_and => {
+ try assignOp(gz, scope, node, .bit_and);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_bit_or => {
+ try assignOp(gz, scope, node, .bit_or);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_bit_shift_left => {
+ try assignOp(gz, scope, node, .shl);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_bit_shift_right => {
+ try assignOp(gz, scope, node, .shr);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_bit_xor => {
+ try assignOp(gz, scope, node, .xor);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_div => {
+ try assignOp(gz, scope, node, .div);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_sub => {
+ try assignOp(gz, scope, node, .sub);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_sub_wrap => {
+ try assignOp(gz, scope, node, .subwrap);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_mod => {
+ try assignOp(gz, scope, node, .mod_rem);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_add => {
+ try assignOp(gz, scope, node, .add);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_add_wrap => {
+ try assignOp(gz, scope, node, .addwrap);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_mul => {
+ try assignOp(gz, scope, node, .mul);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+ .assign_mul_wrap => {
+ try assignOp(gz, scope, node, .mulwrap);
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+
+ .add => return simpleBinOp(gz, scope, rl, node, .add),
+ .add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap),
+ .sub => return simpleBinOp(gz, scope, rl, node, .sub),
+ .sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap),
+ .mul => return simpleBinOp(gz, scope, rl, node, .mul),
+ .mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap),
+ .div => return simpleBinOp(gz, scope, rl, node, .div),
+ .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
+ .bit_and => return simpleBinOp(gz, scope, rl, node, .bit_and),
+ .bit_or => return simpleBinOp(gz, scope, rl, node, .bit_or),
+ .bit_shift_left => return simpleBinOp(gz, scope, rl, node, .shl),
+ .bit_shift_right => return simpleBinOp(gz, scope, rl, node, .shr),
+ .bit_xor => return simpleBinOp(gz, scope, rl, node, .xor),
+
+ .bang_equal => return simpleBinOp(gz, scope, rl, node, .cmp_neq),
+ .equal_equal => return simpleBinOp(gz, scope, rl, node, .cmp_eq),
+ .greater_than => return simpleBinOp(gz, scope, rl, node, .cmp_gt),
+ .greater_or_equal => return simpleBinOp(gz, scope, rl, node, .cmp_gte),
+ .less_than => return simpleBinOp(gz, scope, rl, node, .cmp_lt),
+ .less_or_equal => return simpleBinOp(gz, scope, rl, node, .cmp_lte),
+
+ .array_cat => return simpleBinOp(gz, scope, rl, node, .array_cat),
+ .array_mult => return simpleBinOp(gz, scope, rl, node, .array_mul),
+
+ .error_union => return simpleBinOp(gz, scope, rl, node, .error_union_type),
+ .merge_error_sets => return simpleBinOp(gz, scope, rl, node, .merge_error_sets),
+
+ .bool_and => return boolBinOp(gz, scope, rl, node, .bool_br_and),
+ .bool_or => return boolBinOp(gz, scope, rl, node, .bool_br_or),
+
+ .bool_not => return boolNot(gz, scope, rl, node),
+ .bit_not => return bitNot(gz, scope, rl, node),
+
+ .negation => return negation(gz, scope, rl, node, .negate),
+ .negation_wrap => return negation(gz, scope, rl, node, .negate_wrap),
+
+ .identifier => return identifier(gz, scope, rl, node),
+
+ .asm_simple => return asmExpr(gz, scope, rl, node, tree.asmSimple(node)),
+ .@"asm" => return asmExpr(gz, scope, rl, node, tree.asmFull(node)),
+
+ .string_literal => return stringLiteral(gz, scope, rl, node),
+ .multiline_string_literal => return multilineStringLiteral(gz, scope, rl, node),
+
+ .integer_literal => return integerLiteral(gz, scope, rl, node),
+
+ .builtin_call_two, .builtin_call_two_comma => {
+ if (node_datas[node].lhs == 0) {
+ const params = [_]ast.Node.Index{};
+ return builtinCall(gz, scope, rl, node, ¶ms);
+ } else if (node_datas[node].rhs == 0) {
+ const params = [_]ast.Node.Index{node_datas[node].lhs};
+ return builtinCall(gz, scope, rl, node, ¶ms);
+ } else {
+ const params = [_]ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs };
+ return builtinCall(gz, scope, rl, node, ¶ms);
+ }
+ },
+ .builtin_call, .builtin_call_comma => {
+ const params = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs];
+ return builtinCall(gz, scope, rl, node, params);
+ },
+
+ .call_one, .call_one_comma, .async_call_one, .async_call_one_comma => {
+ var params: [1]ast.Node.Index = undefined;
+ return callExpr(gz, scope, rl, node, tree.callOne(¶ms, node));
+ },
+ .call, .call_comma, .async_call, .async_call_comma => {
+ return callExpr(gz, scope, rl, node, tree.callFull(node));
+ },
+
+ .unreachable_literal => {
+ _ = try gz.addAsIndex(.{
+ .tag = .@"unreachable",
+ .data = .{ .@"unreachable" = .{
+ .safety = true,
+ .src_node = gz.astgen.decl.nodeIndexToRelative(node),
+ } },
+ });
+ return zir.Inst.Ref.unreachable_value;
+ },
+ .@"return" => return ret(gz, scope, node),
+ .field_access => return fieldAccess(gz, scope, rl, node),
+ .float_literal => return floatLiteral(gz, scope, rl, node),
+
+ .if_simple => return ifExpr(gz, scope, rl, node, tree.ifSimple(node)),
+ .@"if" => return ifExpr(gz, scope, rl, node, tree.ifFull(node)),
+
+ .while_simple => return whileExpr(gz, scope, rl, node, tree.whileSimple(node)),
+ .while_cont => return whileExpr(gz, scope, rl, node, tree.whileCont(node)),
+ .@"while" => return whileExpr(gz, scope, rl, node, tree.whileFull(node)),
+
+ .for_simple => return forExpr(gz, scope, rl, node, tree.forSimple(node)),
+ .@"for" => return forExpr(gz, scope, rl, node, tree.forFull(node)),
+
+ .slice_open => {
+ const lhs = try expr(gz, scope, .ref, node_datas[node].lhs);
+ const start = try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs);
+ const result = try gz.addPlNode(.slice_start, node, zir.Inst.SliceStart{
+ .lhs = lhs,
+ .start = start,
+ });
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .slice => {
+ const lhs = try expr(gz, scope, .ref, node_datas[node].lhs);
+ const extra = tree.extraData(node_datas[node].rhs, ast.Node.Slice);
+ const start = try expr(gz, scope, .{ .ty = .usize_type }, extra.start);
+ const end = try expr(gz, scope, .{ .ty = .usize_type }, extra.end);
+ const result = try gz.addPlNode(.slice_end, node, zir.Inst.SliceEnd{
+ .lhs = lhs,
+ .start = start,
+ .end = end,
+ });
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .slice_sentinel => {
+ const lhs = try expr(gz, scope, .ref, node_datas[node].lhs);
+ const extra = tree.extraData(node_datas[node].rhs, ast.Node.SliceSentinel);
+ const start = try expr(gz, scope, .{ .ty = .usize_type }, extra.start);
+ const end = try expr(gz, scope, .{ .ty = .usize_type }, extra.end);
+ const sentinel = try expr(gz, scope, .{ .ty = .usize_type }, extra.sentinel);
+ const result = try gz.addPlNode(.slice_sentinel, node, zir.Inst.SliceSentinel{
+ .lhs = lhs,
+ .start = start,
+ .end = end,
+ .sentinel = sentinel,
+ });
+ return rvalue(gz, scope, rl, result, node);
+ },
+
+ .deref => {
+ const lhs = try expr(gz, scope, .none, node_datas[node].lhs);
+ const result = try gz.addUnNode(.load, lhs, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .address_of => {
+ const result = try expr(gz, scope, .ref, node_datas[node].lhs);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .undefined_literal => return rvalue(gz, scope, rl, .undef, node),
+ .true_literal => return rvalue(gz, scope, rl, .bool_true, node),
+ .false_literal => return rvalue(gz, scope, rl, .bool_false, node),
+ .null_literal => return rvalue(gz, scope, rl, .null_value, node),
+ .optional_type => {
+ const operand = try typeExpr(gz, scope, node_datas[node].lhs);
+ const result = try gz.addUnNode(.optional_type, operand, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .unwrap_optional => switch (rl) {
+ .ref => return gz.addUnNode(
+ .optional_payload_safe_ptr,
+ try expr(gz, scope, .ref, node_datas[node].lhs),
+ node,
+ ),
+ else => return rvalue(gz, scope, rl, try gz.addUnNode(
+ .optional_payload_safe,
+ try expr(gz, scope, .none, node_datas[node].lhs),
+ node,
+ ), node),
+ },
+ .block_two, .block_two_semicolon => {
+ const statements = [2]ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs };
+ if (node_datas[node].lhs == 0) {
+ return blockExpr(gz, scope, rl, node, statements[0..0]);
+ } else if (node_datas[node].rhs == 0) {
+ return blockExpr(gz, scope, rl, node, statements[0..1]);
+ } else {
+ return blockExpr(gz, scope, rl, node, statements[0..2]);
+ }
+ },
+ .block, .block_semicolon => {
+ const statements = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs];
+ return blockExpr(gz, scope, rl, node, statements);
+ },
+ .enum_literal => return simpleStrTok(gz, scope, rl, main_tokens[node], node, .enum_literal),
+ .error_value => return simpleStrTok(gz, scope, rl, node_datas[node].rhs, node, .error_value),
+ .anyframe_literal => return mod.failNode(scope, node, "async and related features are not yet supported", .{}),
+ .anyframe_type => return mod.failNode(scope, node, "async and related features are not yet supported", .{}),
+ .@"catch" => {
+ const catch_token = main_tokens[node];
+ const payload_token: ?ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe)
+ catch_token + 2
+ else
+ null;
+ switch (rl) {
+ .ref => return orelseCatchExpr(
+ gz,
+ scope,
+ rl,
+ node,
+ node_datas[node].lhs,
+ .is_err_ptr,
+ .err_union_payload_unsafe_ptr,
+ .err_union_code_ptr,
+ node_datas[node].rhs,
+ payload_token,
+ ),
+ else => return orelseCatchExpr(
+ gz,
+ scope,
+ rl,
+ node,
+ node_datas[node].lhs,
+ .is_err,
+ .err_union_payload_unsafe,
+ .err_union_code,
+ node_datas[node].rhs,
+ payload_token,
+ ),
+ }
+ },
+ .@"orelse" => switch (rl) {
+ .ref => return orelseCatchExpr(
+ gz,
+ scope,
+ rl,
+ node,
+ node_datas[node].lhs,
+ .is_null_ptr,
+ .optional_payload_unsafe_ptr,
+ undefined,
+ node_datas[node].rhs,
+ null,
+ ),
+ else => return orelseCatchExpr(
+ gz,
+ scope,
+ rl,
+ node,
+ node_datas[node].lhs,
+ .is_null,
+ .optional_payload_unsafe,
+ undefined,
+ node_datas[node].rhs,
+ null,
+ ),
+ },
+
+ .ptr_type_aligned => return ptrType(gz, scope, rl, node, tree.ptrTypeAligned(node)),
+ .ptr_type_sentinel => return ptrType(gz, scope, rl, node, tree.ptrTypeSentinel(node)),
+ .ptr_type => return ptrType(gz, scope, rl, node, tree.ptrType(node)),
+ .ptr_type_bit_range => return ptrType(gz, scope, rl, node, tree.ptrTypeBitRange(node)),
+
+ .container_decl,
+ .container_decl_trailing,
+ => return containerDecl(gz, scope, rl, node, tree.containerDecl(node)),
+ .container_decl_two, .container_decl_two_trailing => {
+ var buffer: [2]ast.Node.Index = undefined;
+ return containerDecl(gz, scope, rl, node, tree.containerDeclTwo(&buffer, node));
+ },
+ .container_decl_arg,
+ .container_decl_arg_trailing,
+ => return containerDecl(gz, scope, rl, node, tree.containerDeclArg(node)),
+
+ .tagged_union,
+ .tagged_union_trailing,
+ => return containerDecl(gz, scope, rl, node, tree.taggedUnion(node)),
+ .tagged_union_two, .tagged_union_two_trailing => {
+ var buffer: [2]ast.Node.Index = undefined;
+ return containerDecl(gz, scope, rl, node, tree.taggedUnionTwo(&buffer, node));
+ },
+ .tagged_union_enum_tag,
+ .tagged_union_enum_tag_trailing,
+ => return containerDecl(gz, scope, rl, node, tree.taggedUnionEnumTag(node)),
+
+ .@"break" => return breakExpr(gz, scope, node),
+ .@"continue" => return continueExpr(gz, scope, node),
+ .grouped_expression => return expr(gz, scope, rl, node_datas[node].lhs),
+ .array_type => return arrayType(gz, scope, rl, node),
+ .array_type_sentinel => return arrayTypeSentinel(gz, scope, rl, node),
+ .char_literal => return charLiteral(gz, scope, rl, node),
+ .error_set_decl => return errorSetDecl(gz, scope, rl, node),
+ .array_access => return arrayAccess(gz, scope, rl, node),
+ .@"comptime" => return comptimeExpr(gz, scope, rl, node_datas[node].lhs),
+ .@"switch", .switch_comma => return switchExpr(gz, scope, rl, node),
+
+ .@"nosuspend" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}),
+ .@"suspend" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}),
+ .@"await" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}),
+ .@"resume" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}),
+
+ .@"defer" => return mod.failNode(scope, node, "TODO implement astgen.expr for .defer", .{}),
+ .@"errdefer" => return mod.failNode(scope, node, "TODO implement astgen.expr for .errdefer", .{}),
+ .@"try" => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}),
+
+ .array_init_one,
+ .array_init_one_comma,
+ .array_init_dot_two,
+ .array_init_dot_two_comma,
+ .array_init_dot,
+ .array_init_dot_comma,
+ .array_init,
+ .array_init_comma,
+ => return mod.failNode(scope, node, "TODO implement astgen.expr for array literals", .{}),
+
+ .struct_init_one, .struct_init_one_comma => {
+ var fields: [1]ast.Node.Index = undefined;
+ return structInitExpr(gz, scope, rl, node, tree.structInitOne(&fields, node));
+ },
+ .struct_init_dot_two, .struct_init_dot_two_comma => {
+ var fields: [2]ast.Node.Index = undefined;
+ return structInitExpr(gz, scope, rl, node, tree.structInitDotTwo(&fields, node));
+ },
+ .struct_init_dot,
+ .struct_init_dot_comma,
+ => return structInitExpr(gz, scope, rl, node, tree.structInitDot(node)),
+ .struct_init,
+ .struct_init_comma,
+ => return structInitExpr(gz, scope, rl, node, tree.structInit(node)),
+
+ .@"anytype" => return mod.failNode(scope, node, "TODO implement astgen.expr for .anytype", .{}),
+ .fn_proto_simple,
+ .fn_proto_multi,
+ .fn_proto_one,
+ .fn_proto,
+ => return mod.failNode(scope, node, "TODO implement astgen.expr for function prototypes", .{}),
+ }
+}
+
+pub fn structInitExpr(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ struct_init: ast.full.StructInit,
+) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const astgen = gz.astgen;
+ const mod = astgen.mod;
+ const gpa = mod.gpa;
+
+ if (struct_init.ast.fields.len == 0) {
+ if (struct_init.ast.type_expr == 0) {
+ return rvalue(gz, scope, rl, .empty_struct, node);
+ } else {
+ const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr);
+ const result = try gz.addUnNode(.struct_init_empty, ty_inst, node);
+ return rvalue(gz, scope, rl, result, node);
+ }
+ }
+ switch (rl) {
+ .discard => return mod.failNode(scope, node, "TODO implement structInitExpr discard", .{}),
+ .none => return mod.failNode(scope, node, "TODO implement structInitExpr none", .{}),
+ .ref => unreachable, // struct literal not valid as l-value
+ .ty => |ty_inst| {
+ return mod.failNode(scope, node, "TODO implement structInitExpr ty", .{});
+ },
+ .ptr => |ptr_inst| {
+ const field_ptr_list = try gpa.alloc(zir.Inst.Index, struct_init.ast.fields.len);
+ defer gpa.free(field_ptr_list);
+
+ for (struct_init.ast.fields) |field_init, i| {
+ const name_token = tree.firstToken(field_init) - 2;
+ const str_index = try gz.identAsString(name_token);
+ const field_ptr = try gz.addPlNode(.field_ptr, field_init, zir.Inst.Field{
+ .lhs = ptr_inst,
+ .field_name_start = str_index,
+ });
+ field_ptr_list[i] = astgen.refToIndex(field_ptr).?;
+ _ = try expr(gz, scope, .{ .ptr = field_ptr }, field_init);
+ }
+ const validate_inst = try gz.addPlNode(.validate_struct_init_ptr, node, zir.Inst.Block{
+ .body_len = @intCast(u32, field_ptr_list.len),
+ });
+ try astgen.extra.appendSlice(gpa, field_ptr_list);
+ return validate_inst;
+ },
+ .inferred_ptr => |ptr_inst| {
+ return mod.failNode(scope, node, "TODO implement structInitExpr inferred_ptr", .{});
+ },
+ .block_ptr => |block_gz| {
+ return mod.failNode(scope, node, "TODO implement structInitExpr block", .{});
+ },
+ }
+}
+
+pub fn comptimeExpr(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const prev_force_comptime = gz.force_comptime;
+ gz.force_comptime = true;
+ const result = try expr(gz, scope, rl, node);
+ gz.force_comptime = prev_force_comptime;
+ return result;
+}
+
+fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref {
+ const mod = parent_gz.astgen.mod;
+ const tree = parent_gz.tree();
+ const node_datas = tree.nodes.items(.data);
+ const break_label = node_datas[node].lhs;
+ const rhs = node_datas[node].rhs;
+
+ // Look for the label in the scope.
+ var scope = parent_scope;
+ while (true) {
+ switch (scope.tag) {
+ .gen_zir => {
+ const block_gz = scope.cast(GenZir).?;
+
+ const block_inst = blk: {
+ if (break_label != 0) {
+ if (block_gz.label) |*label| {
+ if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) {
+ label.used = true;
+ break :blk label.block_inst;
+ }
+ }
+ } else if (block_gz.break_block != 0) {
+ break :blk block_gz.break_block;
+ }
+ scope = block_gz.parent;
+ continue;
+ };
+
+ if (rhs == 0) {
+ _ = try parent_gz.addBreak(.@"break", block_inst, .void_value);
+ return zir.Inst.Ref.unreachable_value;
+ }
+ block_gz.break_count += 1;
+ const prev_rvalue_rl_count = block_gz.rvalue_rl_count;
+ const operand = try expr(parent_gz, parent_scope, block_gz.break_result_loc, rhs);
+ const have_store_to_block = block_gz.rvalue_rl_count != prev_rvalue_rl_count;
+
+ const br = try parent_gz.addBreak(.@"break", block_inst, operand);
+
+ if (block_gz.break_result_loc == .block_ptr) {
+ try block_gz.labeled_breaks.append(mod.gpa, br);
+
+ if (have_store_to_block) {
+ const zir_tags = parent_gz.astgen.instructions.items(.tag);
+ const zir_datas = parent_gz.astgen.instructions.items(.data);
+ const store_inst = @intCast(u32, zir_tags.len - 2);
+ assert(zir_tags[store_inst] == .store_to_block_ptr);
+ assert(zir_datas[store_inst].bin.lhs == block_gz.rl_ptr);
+ try block_gz.labeled_store_to_block_ptr_list.append(mod.gpa, store_inst);
+ }
+ }
+ return zir.Inst.Ref.unreachable_value;
+ },
+ .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
+ .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+ else => if (break_label != 0) {
+ const label_name = try mod.identifierTokenString(parent_scope, break_label);
+ return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name});
+ } else {
+ return mod.failNode(parent_scope, node, "break expression outside loop", .{});
+ },
+ }
+ }
+}
+
+fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref {
+ const mod = parent_gz.astgen.mod;
+ const tree = parent_gz.tree();
+ const node_datas = tree.nodes.items(.data);
+ const break_label = node_datas[node].lhs;
+
+ // Look for the label in the scope.
+ var scope = parent_scope;
+ while (true) {
+ switch (scope.tag) {
+ .gen_zir => {
+ const gen_zir = scope.cast(GenZir).?;
+ const continue_block = gen_zir.continue_block;
+ if (continue_block == 0) {
+ scope = gen_zir.parent;
+ continue;
+ }
+ if (break_label != 0) blk: {
+ if (gen_zir.label) |*label| {
+ if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) {
+ label.used = true;
+ break :blk;
+ }
+ }
+ // found continue but either it has a different label, or no label
+ scope = gen_zir.parent;
+ continue;
+ }
+
+ // TODO emit a break_inline if the loop being continued is inline
+ _ = try parent_gz.addBreak(.@"break", continue_block, .void_value);
+ return zir.Inst.Ref.unreachable_value;
+ },
+ .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
+ .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+ else => if (break_label != 0) {
+ const label_name = try mod.identifierTokenString(parent_scope, break_label);
+ return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name});
+ } else {
+ return mod.failNode(parent_scope, node, "continue expression outside loop", .{});
+ },
+ }
+ }
+}
+
+pub fn blockExpr(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ block_node: ast.Node.Index,
+ statements: []const ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const token_tags = tree.tokens.items(.tag);
+
+ const lbrace = main_tokens[block_node];
+ if (token_tags[lbrace - 1] == .colon and
+ token_tags[lbrace - 2] == .identifier)
+ {
+ return labeledBlockExpr(gz, scope, rl, block_node, statements, .block);
+ }
+
+ try blockExprStmts(gz, scope, block_node, statements);
+ return rvalue(gz, scope, rl, .void_value, block_node);
+}
+
+fn checkLabelRedefinition(mod: *Module, parent_scope: *Scope, label: ast.TokenIndex) !void {
+ // Look for the label in the scope.
+ var scope = parent_scope;
+ while (true) {
+ switch (scope.tag) {
+ .gen_zir => {
+ const gen_zir = scope.cast(GenZir).?;
+ if (gen_zir.label) |prev_label| {
+ if (try tokenIdentEql(mod, parent_scope, label, prev_label.token)) {
+ const tree = parent_scope.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+
+ const label_name = try mod.identifierTokenString(parent_scope, label);
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ parent_scope,
+ gen_zir.tokSrcLoc(label),
+ "redefinition of label '{s}'",
+ .{label_name},
+ );
+ errdefer msg.destroy(mod.gpa);
+ try mod.errNote(
+ parent_scope,
+ gen_zir.tokSrcLoc(prev_label.token),
+ msg,
+ "previous definition is here",
+ .{},
+ );
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(parent_scope, msg);
+ }
+ }
+ scope = gen_zir.parent;
+ },
+ .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
+ .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
+ else => return,
+ }
+ }
+}
+
+fn labeledBlockExpr(
+ gz: *GenZir,
+ parent_scope: *Scope,
+ rl: ResultLoc,
+ block_node: ast.Node.Index,
+ statements: []const ast.Node.Index,
+ zir_tag: zir.Inst.Tag,
+) InnerError!zir.Inst.Ref {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ assert(zir_tag == .block);
+
+ const mod = gz.astgen.mod;
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const token_tags = tree.tokens.items(.tag);
+
+ const lbrace = main_tokens[block_node];
+ const label_token = lbrace - 2;
+ assert(token_tags[label_token] == .identifier);
+
+ try checkLabelRedefinition(mod, parent_scope, label_token);
+
+ // Reserve the Block ZIR instruction index so that we can put it into the GenZir struct
+ // so that break statements can reference it.
+ const block_inst = try gz.addBlock(zir_tag, block_node);
+ try gz.instructions.append(mod.gpa, block_inst);
+
+ var block_scope: GenZir = .{
+ .parent = parent_scope,
+ .astgen = gz.astgen,
+ .force_comptime = gz.force_comptime,
+ .instructions = .{},
+ // TODO @as here is working around a stage1 miscompilation bug :(
+ .label = @as(?GenZir.Label, GenZir.Label{
+ .token = label_token,
+ .block_inst = block_inst,
+ }),
+ };
+ block_scope.setBreakResultLoc(rl);
+ defer block_scope.instructions.deinit(mod.gpa);
+ defer block_scope.labeled_breaks.deinit(mod.gpa);
+ defer block_scope.labeled_store_to_block_ptr_list.deinit(mod.gpa);
+
+ try blockExprStmts(&block_scope, &block_scope.base, block_node, statements);
+
+ if (!block_scope.label.?.used) {
+ return mod.failTok(parent_scope, label_token, "unused block label", .{});
+ }
+
+ const zir_tags = gz.astgen.instructions.items(.tag);
+ const zir_datas = gz.astgen.instructions.items(.data);
+
+ const strat = rl.strategy(&block_scope);
+ switch (strat.tag) {
+ .break_void => {
+ // The code took advantage of the result location as a pointer.
+ // Turn the break instruction operands into void.
+ for (block_scope.labeled_breaks.items) |br| {
+ zir_datas[br].@"break".operand = .void_value;
+ }
+ try block_scope.setBlockBody(block_inst);
+
+ return gz.astgen.indexToRef(block_inst);
+ },
+ .break_operand => {
+ // All break operands are values that did not use the result location pointer.
+ if (strat.elide_store_to_block_ptr_instructions) {
+ for (block_scope.labeled_store_to_block_ptr_list.items) |inst| {
+ zir_tags[inst] = .elided;
+ zir_datas[inst] = undefined;
+ }
+ // TODO technically not needed since we changed the tag to elided but
+ // would be better still to elide the ones that are in this list.
+ }
+ try block_scope.setBlockBody(block_inst);
+ const block_ref = gz.astgen.indexToRef(block_inst);
+ switch (rl) {
+ .ref => return block_ref,
+ else => return rvalue(gz, parent_scope, rl, block_ref, block_node),
+ }
+ },
+ }
+}
+
+fn blockExprStmts(
+ gz: *GenZir,
+ parent_scope: *Scope,
+ node: ast.Node.Index,
+ statements: []const ast.Node.Index,
+) !void {
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const node_tags = tree.nodes.items(.tag);
+
+ var block_arena = std.heap.ArenaAllocator.init(gz.astgen.mod.gpa);
+ defer block_arena.deinit();
+
+ var scope = parent_scope;
+ for (statements) |statement| {
+ if (!gz.force_comptime) {
+ _ = try gz.addNode(.dbg_stmt_node, statement);
+ }
+ switch (node_tags[statement]) {
+ .global_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.globalVarDecl(statement)),
+ .local_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.localVarDecl(statement)),
+ .simple_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.simpleVarDecl(statement)),
+ .aligned_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.alignedVarDecl(statement)),
+
+ .assign => try assign(gz, scope, statement),
+ .assign_bit_and => try assignOp(gz, scope, statement, .bit_and),
+ .assign_bit_or => try assignOp(gz, scope, statement, .bit_or),
+ .assign_bit_shift_left => try assignOp(gz, scope, statement, .shl),
+ .assign_bit_shift_right => try assignOp(gz, scope, statement, .shr),
+ .assign_bit_xor => try assignOp(gz, scope, statement, .xor),
+ .assign_div => try assignOp(gz, scope, statement, .div),
+ .assign_sub => try assignOp(gz, scope, statement, .sub),
+ .assign_sub_wrap => try assignOp(gz, scope, statement, .subwrap),
+ .assign_mod => try assignOp(gz, scope, statement, .mod_rem),
+ .assign_add => try assignOp(gz, scope, statement, .add),
+ .assign_add_wrap => try assignOp(gz, scope, statement, .addwrap),
+ .assign_mul => try assignOp(gz, scope, statement, .mul),
+ .assign_mul_wrap => try assignOp(gz, scope, statement, .mulwrap),
+
+ else => {
+ // We need to emit an error if the result is not `noreturn` or `void`, but
+ // we want to avoid adding the ZIR instruction if possible for performance.
+ const maybe_unused_result = try expr(gz, scope, .none, statement);
+ const elide_check = if (gz.astgen.refToIndex(maybe_unused_result)) |inst| b: {
+ // Note that this array becomes invalid after appending more items to it
+ // in the above while loop.
+ const zir_tags = gz.astgen.instructions.items(.tag);
+ switch (zir_tags[inst]) {
+ .@"const" => {
+ const tv = gz.astgen.instructions.items(.data)[inst].@"const";
+ break :b switch (tv.ty.zigTypeTag()) {
+ .NoReturn, .Void => true,
+ else => false,
+ };
+ },
+ // For some instructions, swap in a slightly different ZIR tag
+ // so we can avoid a separate ensure_result_used instruction.
+ .call_none_chkused => unreachable,
+ .call_none => {
+ zir_tags[inst] = .call_none_chkused;
+ break :b true;
+ },
+ .call_chkused => unreachable,
+ .call => {
+ zir_tags[inst] = .call_chkused;
+ break :b true;
+ },
+
+ // ZIR instructions that might be a type other than `noreturn` or `void`.
+ .add,
+ .addwrap,
+ .alloc,
+ .alloc_mut,
+ .alloc_inferred,
+ .alloc_inferred_mut,
+ .array_cat,
+ .array_mul,
+ .array_type,
+ .array_type_sentinel,
+ .indexable_ptr_len,
+ .as,
+ .as_node,
+ .@"asm",
+ .asm_volatile,
+ .bit_and,
+ .bitcast,
+ .bitcast_result_ptr,
+ .bit_or,
+ .block,
+ .block_inline,
+ .loop,
+ .bool_br_and,
+ .bool_br_or,
+ .bool_not,
+ .bool_and,
+ .bool_or,
+ .call_compile_time,
+ .cmp_lt,
+ .cmp_lte,
+ .cmp_eq,
+ .cmp_gte,
+ .cmp_gt,
+ .cmp_neq,
+ .coerce_result_ptr,
+ .decl_ref,
+ .decl_val,
+ .load,
+ .div,
+ .elem_ptr,
+ .elem_val,
+ .elem_ptr_node,
+ .elem_val_node,
+ .floatcast,
+ .field_ptr,
+ .field_val,
+ .field_ptr_named,
+ .field_val_named,
+ .fn_type,
+ .fn_type_var_args,
+ .fn_type_cc,
+ .fn_type_cc_var_args,
+ .int,
+ .intcast,
+ .int_type,
+ .is_non_null,
+ .is_null,
+ .is_non_null_ptr,
+ .is_null_ptr,
+ .is_err,
+ .is_err_ptr,
+ .mod_rem,
+ .mul,
+ .mulwrap,
+ .param_type,
+ .ptrtoint,
+ .ref,
+ .ret_ptr,
+ .ret_type,
+ .shl,
+ .shr,
+ .str,
+ .sub,
+ .subwrap,
+ .negate,
+ .negate_wrap,
+ .typeof,
+ .typeof_elem,
+ .xor,
+ .optional_type,
+ .optional_type_from_ptr_elem,
+ .optional_payload_safe,
+ .optional_payload_unsafe,
+ .optional_payload_safe_ptr,
+ .optional_payload_unsafe_ptr,
+ .err_union_payload_safe,
+ .err_union_payload_unsafe,
+ .err_union_payload_safe_ptr,
+ .err_union_payload_unsafe_ptr,
+ .err_union_code,
+ .err_union_code_ptr,
+ .ptr_type,
+ .ptr_type_simple,
+ .enum_literal,
+ .enum_literal_small,
+ .merge_error_sets,
+ .error_union_type,
+ .bit_not,
+ .error_value,
+ .error_to_int,
+ .int_to_error,
+ .slice_start,
+ .slice_end,
+ .slice_sentinel,
+ .import,
+ .typeof_peer,
+ .switch_block,
+ .switch_block_multi,
+ .switch_block_else,
+ .switch_block_else_multi,
+ .switch_block_under,
+ .switch_block_under_multi,
+ .switch_block_ref,
+ .switch_block_ref_multi,
+ .switch_block_ref_else,
+ .switch_block_ref_else_multi,
+ .switch_block_ref_under,
+ .switch_block_ref_under_multi,
+ .switch_capture,
+ .switch_capture_ref,
+ .switch_capture_multi,
+ .switch_capture_multi_ref,
+ .switch_capture_else,
+ .switch_capture_else_ref,
+ .struct_init_empty,
+ .struct_decl,
+ .struct_decl_packed,
+ .struct_decl_extern,
+ .union_decl,
+ .enum_decl,
+ .opaque_decl,
+ => break :b false,
+
+ // ZIR instructions that are always either `noreturn` or `void`.
+ .breakpoint,
+ .dbg_stmt_node,
+ .ensure_result_used,
+ .ensure_result_non_error,
+ .set_eval_branch_quota,
+ .compile_log,
+ .ensure_err_payload_void,
+ .@"break",
+ .break_inline,
+ .condbr,
+ .condbr_inline,
+ .compile_error,
+ .ret_node,
+ .ret_tok,
+ .ret_coerce,
+ .@"unreachable",
+ .elided,
+ .store,
+ .store_node,
+ .store_to_block_ptr,
+ .store_to_inferred_ptr,
+ .resolve_inferred_alloc,
+ .repeat,
+ .repeat_inline,
+ .validate_struct_init_ptr,
+ => break :b true,
+ }
+ } else switch (maybe_unused_result) {
+ .none => unreachable,
+
+ .void_value,
+ .unreachable_value,
+ => true,
+
+ else => false,
+ };
+ if (!elide_check) {
+ _ = try gz.addUnNode(.ensure_result_used, maybe_unused_result, statement);
+ }
+ },
+ }
+ }
+}
+
+fn varDecl(
+ gz: *GenZir,
+ scope: *Scope,
+ node: ast.Node.Index,
+ block_arena: *Allocator,
+ var_decl: ast.full.VarDecl,
+) InnerError!*Scope {
+ const mod = gz.astgen.mod;
+ if (var_decl.comptime_token) |comptime_token| {
+ return mod.failTok(scope, comptime_token, "TODO implement comptime locals", .{});
+ }
+ if (var_decl.ast.align_node != 0) {
+ return mod.failNode(scope, var_decl.ast.align_node, "TODO implement alignment on locals", .{});
+ }
+ const astgen = gz.astgen;
+ const tree = gz.tree();
+ const token_tags = tree.tokens.items(.tag);
+
+ const name_token = var_decl.ast.mut_token + 1;
+ const name_src = gz.tokSrcLoc(name_token);
+ const ident_name = try mod.identifierTokenString(scope, name_token);
+
+ // Local variables shadowing detection, including function parameters.
+ {
+ var s = scope;
+ while (true) switch (s.tag) {
+ .local_val => {
+ const local_val = s.cast(Scope.LocalVal).?;
+ if (mem.eql(u8, local_val.name, ident_name)) {
+ const msg = msg: {
+ const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{
+ ident_name,
+ });
+ errdefer msg.destroy(mod.gpa);
+ try mod.errNote(scope, local_val.src, msg, "previous definition is here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(scope, msg);
+ }
+ s = local_val.parent;
+ },
+ .local_ptr => {
+ const local_ptr = s.cast(Scope.LocalPtr).?;
+ if (mem.eql(u8, local_ptr.name, ident_name)) {
+ const msg = msg: {
+ const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{
+ ident_name,
+ });
+ errdefer msg.destroy(mod.gpa);
+ try mod.errNote(scope, local_ptr.src, msg, "previous definition is here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(scope, msg);
+ }
+ s = local_ptr.parent;
+ },
+ .gen_zir => s = s.cast(GenZir).?.parent,
+ else => break,
+ };
+ }
+
+ // Namespace vars shadowing detection
+ if (mod.lookupDeclName(scope, ident_name)) |_| {
+ // TODO add note for other definition
+ return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name});
+ }
+ if (var_decl.ast.init_node == 0) {
+ return mod.fail(scope, name_src, "variables must be initialized", .{});
+ }
+
+ switch (token_tags[var_decl.ast.mut_token]) {
+ .keyword_const => {
+ // Depending on the type of AST the initialization expression is, we may need an lvalue
+ // or an rvalue as a result location. If it is an rvalue, we can use the instruction as
+ // the variable, no memory location needed.
+ if (!nodeMayNeedMemoryLocation(tree, var_decl.ast.init_node)) {
+ const result_loc: ResultLoc = if (var_decl.ast.type_node != 0) .{
+ .ty = try typeExpr(gz, scope, var_decl.ast.type_node),
+ } else .none;
+ const init_inst = try expr(gz, scope, result_loc, var_decl.ast.init_node);
+ const sub_scope = try block_arena.create(Scope.LocalVal);
+ sub_scope.* = .{
+ .parent = scope,
+ .gen_zir = gz,
+ .name = ident_name,
+ .inst = init_inst,
+ .src = name_src,
+ };
+ return &sub_scope.base;
+ }
+
+ // Detect whether the initialization expression actually uses the
+ // result location pointer.
+ var init_scope: GenZir = .{
+ .parent = scope,
+ .force_comptime = gz.force_comptime,
+ .astgen = astgen,
+ };
+ defer init_scope.instructions.deinit(mod.gpa);
+
+ var resolve_inferred_alloc: zir.Inst.Ref = .none;
+ var opt_type_inst: zir.Inst.Ref = .none;
+ if (var_decl.ast.type_node != 0) {
+ const type_inst = try typeExpr(gz, &init_scope.base, var_decl.ast.type_node);
+ opt_type_inst = type_inst;
+ init_scope.rl_ptr = try init_scope.addUnNode(.alloc, type_inst, node);
+ init_scope.rl_ty_inst = type_inst;
+ } else {
+ const alloc = try init_scope.addUnNode(.alloc_inferred, undefined, node);
+ resolve_inferred_alloc = alloc;
+ init_scope.rl_ptr = alloc;
+ }
+ const init_result_loc: ResultLoc = .{ .block_ptr = &init_scope };
+ const init_inst = try expr(&init_scope, &init_scope.base, init_result_loc, var_decl.ast.init_node);
+ const zir_tags = astgen.instructions.items(.tag);
+ const zir_datas = astgen.instructions.items(.data);
+
+ const parent_zir = &gz.instructions;
+ if (init_scope.rvalue_rl_count == 1) {
+ // Result location pointer not used. We don't need an alloc for this
+ // const local, and type inference becomes trivial.
+ // Move the init_scope instructions into the parent scope, eliding
+ // the alloc instruction and the store_to_block_ptr instruction.
+ const expected_len = parent_zir.items.len + init_scope.instructions.items.len - 2;
+ try parent_zir.ensureCapacity(mod.gpa, expected_len);
+ for (init_scope.instructions.items) |src_inst| {
+ if (astgen.indexToRef(src_inst) == init_scope.rl_ptr) continue;
+ if (zir_tags[src_inst] == .store_to_block_ptr) {
+ if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) continue;
+ }
+ parent_zir.appendAssumeCapacity(src_inst);
+ }
+ assert(parent_zir.items.len == expected_len);
+
+ const sub_scope = try block_arena.create(Scope.LocalVal);
+ sub_scope.* = .{
+ .parent = scope,
+ .gen_zir = gz,
+ .name = ident_name,
+ .inst = init_inst,
+ .src = name_src,
+ };
+ return &sub_scope.base;
+ }
+ // The initialization expression took advantage of the result location
+ // of the const local. In this case we will create an alloc and a LocalPtr for it.
+ // Move the init_scope instructions into the parent scope, swapping
+ // store_to_block_ptr for store_to_inferred_ptr.
+ const expected_len = parent_zir.items.len + init_scope.instructions.items.len;
+ try parent_zir.ensureCapacity(mod.gpa, expected_len);
+ for (init_scope.instructions.items) |src_inst| {
+ if (zir_tags[src_inst] == .store_to_block_ptr) {
+ if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) {
+ zir_tags[src_inst] = .store_to_inferred_ptr;
+ }
+ }
+ parent_zir.appendAssumeCapacity(src_inst);
+ }
+ assert(parent_zir.items.len == expected_len);
+ if (resolve_inferred_alloc != .none) {
+ _ = try gz.addUnNode(.resolve_inferred_alloc, resolve_inferred_alloc, node);
+ }
+ const sub_scope = try block_arena.create(Scope.LocalPtr);
+ sub_scope.* = .{
+ .parent = scope,
+ .gen_zir = gz,
+ .name = ident_name,
+ .ptr = init_scope.rl_ptr,
+ .src = name_src,
+ };
+ return &sub_scope.base;
+ },
+ .keyword_var => {
+ var resolve_inferred_alloc: zir.Inst.Ref = .none;
+ const var_data: struct {
+ result_loc: ResultLoc,
+ alloc: zir.Inst.Ref,
+ } = if (var_decl.ast.type_node != 0) a: {
+ const type_inst = try typeExpr(gz, scope, var_decl.ast.type_node);
+
+ const alloc = try gz.addUnNode(.alloc_mut, type_inst, node);
+ break :a .{ .alloc = alloc, .result_loc = .{ .ptr = alloc } };
+ } else a: {
+ const alloc = try gz.addUnNode(.alloc_inferred_mut, undefined, node);
+ resolve_inferred_alloc = alloc;
+ break :a .{ .alloc = alloc, .result_loc = .{ .inferred_ptr = alloc } };
+ };
+ const init_inst = try expr(gz, scope, var_data.result_loc, var_decl.ast.init_node);
+ if (resolve_inferred_alloc != .none) {
+ _ = try gz.addUnNode(.resolve_inferred_alloc, resolve_inferred_alloc, node);
+ }
+ const sub_scope = try block_arena.create(Scope.LocalPtr);
+ sub_scope.* = .{
+ .parent = scope,
+ .gen_zir = gz,
+ .name = ident_name,
+ .ptr = var_data.alloc,
+ .src = name_src,
+ };
+ return &sub_scope.base;
+ },
+ else => unreachable,
+ }
+}
+
+fn assign(gz: *GenZir, scope: *Scope, infix_node: ast.Node.Index) InnerError!void {
+ const tree = gz.tree();
+ const node_datas = tree.nodes.items(.data);
+ const main_tokens = tree.nodes.items(.main_token);
+ const node_tags = tree.nodes.items(.tag);
+
+ const lhs = node_datas[infix_node].lhs;
+ const rhs = node_datas[infix_node].rhs;
+ if (node_tags[lhs] == .identifier) {
+ // This intentionally does not support `@"_"` syntax.
+ const ident_name = tree.tokenSlice(main_tokens[lhs]);
+ if (mem.eql(u8, ident_name, "_")) {
+ _ = try expr(gz, scope, .discard, rhs);
+ return;
+ }
+ }
+ const lvalue = try lvalExpr(gz, scope, lhs);
+ _ = try expr(gz, scope, .{ .ptr = lvalue }, rhs);
+}
+
+fn assignOp(
+ gz: *GenZir,
+ scope: *Scope,
+ infix_node: ast.Node.Index,
+ op_inst_tag: zir.Inst.Tag,
+) InnerError!void {
+ const tree = gz.tree();
+ const node_datas = tree.nodes.items(.data);
+
+ const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
+ const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
+ const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
+ const rhs = try expr(gz, scope, .{ .ty = lhs_type }, node_datas[infix_node].rhs);
+
+ const result = try gz.addPlNode(op_inst_tag, infix_node, zir.Inst.Bin{
+ .lhs = lhs,
+ .rhs = rhs,
+ });
+ _ = try gz.addBin(.store, lhs_ptr, result);
+}
+
+fn boolNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const node_datas = tree.nodes.items(.data);
+
+ const operand = try expr(gz, scope, .{ .ty = .bool_type }, node_datas[node].lhs);
+ const result = try gz.addUnNode(.bool_not, operand, node);
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn bitNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const node_datas = tree.nodes.items(.data);
+
+ const operand = try expr(gz, scope, .none, node_datas[node].lhs);
+ const result = try gz.addUnNode(.bit_not, operand, node);
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn negation(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ tag: zir.Inst.Tag,
+) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const node_datas = tree.nodes.items(.data);
+
+ const operand = try expr(gz, scope, .none, node_datas[node].lhs);
+ const result = try gz.addUnNode(tag, operand, node);
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn ptrType(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ ptr_info: ast.full.PtrType,
+) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+
+ const elem_type = try typeExpr(gz, scope, ptr_info.ast.child_type);
+
+ const simple = ptr_info.ast.align_node == 0 and
+ ptr_info.ast.sentinel == 0 and
+ ptr_info.ast.bit_range_start == 0;
+
+ if (simple) {
+ const result = try gz.add(.{ .tag = .ptr_type_simple, .data = .{
+ .ptr_type_simple = .{
+ .is_allowzero = ptr_info.allowzero_token != null,
+ .is_mutable = ptr_info.const_token == null,
+ .is_volatile = ptr_info.volatile_token != null,
+ .size = ptr_info.size,
+ .elem_type = elem_type,
+ },
+ } });
+ return rvalue(gz, scope, rl, result, node);
+ }
+
+ var sentinel_ref: zir.Inst.Ref = .none;
+ var align_ref: zir.Inst.Ref = .none;
+ var bit_start_ref: zir.Inst.Ref = .none;
+ var bit_end_ref: zir.Inst.Ref = .none;
+ var trailing_count: u32 = 0;
+
+ if (ptr_info.ast.sentinel != 0) {
+ sentinel_ref = try expr(gz, scope, .{ .ty = elem_type }, ptr_info.ast.sentinel);
+ trailing_count += 1;
+ }
+ if (ptr_info.ast.align_node != 0) {
+ align_ref = try expr(gz, scope, .none, ptr_info.ast.align_node);
+ trailing_count += 1;
+ }
+ if (ptr_info.ast.bit_range_start != 0) {
+ assert(ptr_info.ast.bit_range_end != 0);
+ bit_start_ref = try expr(gz, scope, .none, ptr_info.ast.bit_range_start);
+ bit_end_ref = try expr(gz, scope, .none, ptr_info.ast.bit_range_end);
+ trailing_count += 2;
+ }
+
+ const gpa = gz.astgen.mod.gpa;
+ try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
+ try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
+ try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
+ @typeInfo(zir.Inst.PtrType).Struct.fields.len + trailing_count);
+
+ const payload_index = gz.astgen.addExtraAssumeCapacity(zir.Inst.PtrType{ .elem_type = elem_type });
+ if (sentinel_ref != .none) {
+ gz.astgen.extra.appendAssumeCapacity(@enumToInt(sentinel_ref));
+ }
+ if (align_ref != .none) {
+ gz.astgen.extra.appendAssumeCapacity(@enumToInt(align_ref));
+ }
+ if (bit_start_ref != .none) {
+ gz.astgen.extra.appendAssumeCapacity(@enumToInt(bit_start_ref));
+ gz.astgen.extra.appendAssumeCapacity(@enumToInt(bit_end_ref));
+ }
+
+ const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len);
+ const result = gz.astgen.indexToRef(new_index);
+ gz.astgen.instructions.appendAssumeCapacity(.{ .tag = .ptr_type, .data = .{
+ .ptr_type = .{
+ .flags = .{
+ .is_allowzero = ptr_info.allowzero_token != null,
+ .is_mutable = ptr_info.const_token == null,
+ .is_volatile = ptr_info.volatile_token != null,
+ .has_sentinel = sentinel_ref != .none,
+ .has_align = align_ref != .none,
+ .has_bit_range = bit_start_ref != .none,
+ },
+ .size = ptr_info.size,
+ .payload_index = payload_index,
+ },
+ } });
+ gz.instructions.appendAssumeCapacity(new_index);
+
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn arrayType(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref {
+ const tree = gz.tree();
+ const node_datas = tree.nodes.items(.data);
+
+ // TODO check for [_]T
+ const len = try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].lhs);
+ const elem_type = try typeExpr(gz, scope, node_datas[node].rhs);
+
+ const result = try gz.addBin(.array_type, len, elem_type);
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn arrayTypeSentinel(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref {
+ const tree = gz.tree();
+ const node_datas = tree.nodes.items(.data);
+ const extra = tree.extraData(node_datas[node].rhs, ast.Node.ArrayTypeSentinel);
+
+ // TODO check for [_]T
+ const len = try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].lhs);
+ const elem_type = try typeExpr(gz, scope, extra.elem_type);
+ const sentinel = try expr(gz, scope, .{ .ty = elem_type }, extra.sentinel);
+
+ const result = try gz.addArrayTypeSentinel(len, elem_type, sentinel);
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn containerDecl(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ container_decl: ast.full.ContainerDecl,
+) InnerError!zir.Inst.Ref {
+ const astgen = gz.astgen;
+ const mod = astgen.mod;
+ const gpa = mod.gpa;
+ const tree = gz.tree();
+ const token_tags = tree.tokens.items(.tag);
+ const node_tags = tree.nodes.items(.tag);
+
+ // We must not create any types until Sema. Here the goal is only to generate
+ // ZIR for all the field types, alignments, and default value expressions.
+
+ const arg_inst: zir.Inst.Ref = if (container_decl.ast.arg != 0)
+ try comptimeExpr(gz, scope, .none, container_decl.ast.arg)
+ else
+ .none;
+
+ switch (token_tags[container_decl.ast.main_token]) {
+ .keyword_struct => {
+ const tag = if (container_decl.layout_token) |t| switch (token_tags[t]) {
+ .keyword_packed => zir.Inst.Tag.struct_decl_packed,
+ .keyword_extern => zir.Inst.Tag.struct_decl_extern,
+ else => unreachable,
+ } else zir.Inst.Tag.struct_decl;
+ if (container_decl.ast.members.len == 0) {
+ const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{
+ .fields_len = 0,
+ });
+ return rvalue(gz, scope, rl, result, node);
+ }
+
+ assert(arg_inst == .none);
+ var fields_data = ArrayListUnmanaged(u32){};
+ defer fields_data.deinit(gpa);
+
+ // field_name and field_type are both mandatory
+ try fields_data.ensureCapacity(gpa, container_decl.ast.members.len * 2);
+
+ // We only need this if there are greater than 16 fields.
+ var bit_bag = ArrayListUnmanaged(u32){};
+ defer bit_bag.deinit(gpa);
+
+ var cur_bit_bag: u32 = 0;
+ var member_index: usize = 0;
+ while (true) {
+ const member_node = container_decl.ast.members[member_index];
+ const member = switch (node_tags[member_node]) {
+ .container_field_init => tree.containerFieldInit(member_node),
+ .container_field_align => tree.containerFieldAlign(member_node),
+ .container_field => tree.containerField(member_node),
+ else => unreachable,
+ };
+ if (member.comptime_token) |comptime_token| {
+ return mod.failTok(scope, comptime_token, "TODO implement comptime struct fields", .{});
+ }
+ try fields_data.ensureCapacity(gpa, fields_data.items.len + 4);
+
+ const field_name = try gz.identAsString(member.ast.name_token);
+ fields_data.appendAssumeCapacity(field_name);
+
+ const field_type = try typeExpr(gz, scope, member.ast.type_expr);
+ fields_data.appendAssumeCapacity(@enumToInt(field_type));
+
+ const have_align = member.ast.align_expr != 0;
+ const have_value = member.ast.value_expr != 0;
+ cur_bit_bag = (cur_bit_bag >> 2) |
+ (@as(u32, @boolToInt(have_align)) << 30) |
+ (@as(u32, @boolToInt(have_value)) << 31);
+
+ if (have_align) {
+ const align_inst = try comptimeExpr(gz, scope, .{ .ty = .u32_type }, member.ast.align_expr);
+ fields_data.appendAssumeCapacity(@enumToInt(align_inst));
+ }
+ if (have_value) {
+ const default_inst = try comptimeExpr(gz, scope, .{ .ty = field_type }, member.ast.value_expr);
+ fields_data.appendAssumeCapacity(@enumToInt(default_inst));
+ }
+
+ member_index += 1;
+ if (member_index < container_decl.ast.members.len) {
+ if (member_index % 16 == 0) {
+ try bit_bag.append(gpa, cur_bit_bag);
+ cur_bit_bag = 0;
+ }
+ } else {
+ break;
+ }
+ }
+ const empty_slot_count = 16 - ((member_index - 1) % 16);
+ cur_bit_bag >>= @intCast(u5, empty_slot_count * 2);
+
+ const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{
+ .fields_len = @intCast(u32, container_decl.ast.members.len),
+ });
+ try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len +
+ bit_bag.items.len + 1 + fields_data.items.len);
+ astgen.extra.appendSliceAssumeCapacity(bit_bag.items); // Likely empty.
+ astgen.extra.appendAssumeCapacity(cur_bit_bag);
+ astgen.extra.appendSliceAssumeCapacity(fields_data.items);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .keyword_union => {
+ return mod.failTok(scope, container_decl.ast.main_token, "TODO AstGen for union decl", .{});
+ },
+ .keyword_enum => {
+ return mod.failTok(scope, container_decl.ast.main_token, "TODO AstGen for enum decl", .{});
+ },
+ .keyword_opaque => {
+ const result = try gz.addNode(.opaque_decl, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ else => unreachable,
+ }
+}
+
+fn errorSetDecl(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const mod = gz.astgen.mod;
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const token_tags = tree.tokens.items(.tag);
+ const arena = gz.astgen.arena;
+
+ // Count how many fields there are.
+ const error_token = main_tokens[node];
+ const count: usize = count: {
+ var tok_i = error_token + 2;
+ var count: usize = 0;
+ while (true) : (tok_i += 1) {
+ switch (token_tags[tok_i]) {
+ .doc_comment, .comma => {},
+ .identifier => count += 1,
+ .r_brace => break :count count,
+ else => unreachable,
+ }
+ } else unreachable; // TODO should not need else unreachable here
+ };
+
+ const fields = try arena.alloc([]const u8, count);
+ {
+ var tok_i = error_token + 2;
+ var field_i: usize = 0;
+ while (true) : (tok_i += 1) {
+ switch (token_tags[tok_i]) {
+ .doc_comment, .comma => {},
+ .identifier => {
+ fields[field_i] = try mod.identifierTokenString(scope, tok_i);
+ field_i += 1;
+ },
+ .r_brace => break,
+ else => unreachable,
+ }
+ }
+ }
+ const error_set = try arena.create(Module.ErrorSet);
+ error_set.* = .{
+ .owner_decl = gz.astgen.decl,
+ .node_offset = gz.astgen.decl.nodeIndexToRelative(node),
+ .names_ptr = fields.ptr,
+ .names_len = @intCast(u32, fields.len),
+ };
+ const error_set_ty = try Type.Tag.error_set.create(arena, error_set);
+ const typed_value = try arena.create(TypedValue);
+ typed_value.* = .{
+ .ty = Type.initTag(.type),
+ .val = try Value.Tag.ty.create(arena, error_set_ty),
+ };
+ const result = try gz.addConst(typed_value);
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn orelseCatchExpr(
+ parent_gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ lhs: ast.Node.Index,
+ cond_op: zir.Inst.Tag,
+ unwrap_op: zir.Inst.Tag,
+ unwrap_code_op: zir.Inst.Tag,
+ rhs: ast.Node.Index,
+ payload_token: ?ast.TokenIndex,
+) InnerError!zir.Inst.Ref {
+ const mod = parent_gz.astgen.mod;
+ const tree = parent_gz.tree();
+
+ var block_scope: GenZir = .{
+ .parent = scope,
+ .astgen = parent_gz.astgen,
+ .force_comptime = parent_gz.force_comptime,
+ .instructions = .{},
+ };
+ block_scope.setBreakResultLoc(rl);
+ defer block_scope.instructions.deinit(mod.gpa);
+
+ // This could be a pointer or value depending on the `operand_rl` parameter.
+ // We cannot use `block_scope.break_result_loc` because that has the bare
+ // type, whereas this expression has the optional type. Later we make
+ // up for this fact by calling rvalue on the else branch.
+ block_scope.break_count += 1;
+
+ // TODO handle catch
+ const operand_rl: ResultLoc = switch (block_scope.break_result_loc) {
+ .ref => .ref,
+ .discard, .none, .block_ptr, .inferred_ptr => .none,
+ .ty => |elem_ty| blk: {
+ const wrapped_ty = try block_scope.addUnNode(.optional_type, elem_ty, node);
+ break :blk .{ .ty = wrapped_ty };
+ },
+ .ptr => |ptr_ty| blk: {
+ const wrapped_ty = try block_scope.addUnNode(.optional_type_from_ptr_elem, ptr_ty, node);
+ break :blk .{ .ty = wrapped_ty };
+ },
+ };
+ const operand = try expr(&block_scope, &block_scope.base, operand_rl, lhs);
+ const cond = try block_scope.addUnNode(cond_op, operand, node);
+ const condbr = try block_scope.addCondBr(.condbr, node);
+
+ const block = try parent_gz.addBlock(.block, node);
+ try parent_gz.instructions.append(mod.gpa, block);
+ try block_scope.setBlockBody(block);
+
+ var then_scope: GenZir = .{
+ .parent = scope,
+ .astgen = parent_gz.astgen,
+ .force_comptime = block_scope.force_comptime,
+ .instructions = .{},
+ };
+ defer then_scope.instructions.deinit(mod.gpa);
+
+ var err_val_scope: Scope.LocalVal = undefined;
+ const then_sub_scope = blk: {
+ const payload = payload_token orelse break :blk &then_scope.base;
+ if (mem.eql(u8, tree.tokenSlice(payload), "_")) {
+ return mod.failTok(&then_scope.base, payload, "discard of error capture; omit it instead", .{});
+ }
+ const err_name = try mod.identifierTokenString(scope, payload);
+ err_val_scope = .{
+ .parent = &then_scope.base,
+ .gen_zir = &then_scope,
+ .name = err_name,
+ .inst = try then_scope.addUnNode(unwrap_code_op, operand, node),
+ .src = parent_gz.tokSrcLoc(payload),
+ };
+ break :blk &err_val_scope.base;
+ };
+
+ block_scope.break_count += 1;
+ const then_result = try expr(&then_scope, then_sub_scope, block_scope.break_result_loc, rhs);
+ // We hold off on the break instructions as well as copying the then/else
+ // instructions into place until we know whether to keep store_to_block_ptr
+ // instructions or not.
+
+ var else_scope: GenZir = .{
+ .parent = scope,
+ .astgen = parent_gz.astgen,
+ .force_comptime = block_scope.force_comptime,
+ .instructions = .{},
+ };
+ defer else_scope.instructions.deinit(mod.gpa);
+
+ // This could be a pointer or value depending on `unwrap_op`.
+ const unwrapped_payload = try else_scope.addUnNode(unwrap_op, operand, node);
+ const else_result = switch (rl) {
+ .ref => unwrapped_payload,
+ else => try rvalue(&else_scope, &else_scope.base, block_scope.break_result_loc, unwrapped_payload, node),
+ };
+
+ return finishThenElseBlock(
+ parent_gz,
+ scope,
+ rl,
+ node,
+ &block_scope,
+ &then_scope,
+ &else_scope,
+ condbr,
+ cond,
+ node,
+ node,
+ then_result,
+ else_result,
+ block,
+ block,
+ .@"break",
+ );
+}
+
+fn finishThenElseBlock(
+ parent_gz: *GenZir,
+ parent_scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ block_scope: *GenZir,
+ then_scope: *GenZir,
+ else_scope: *GenZir,
+ condbr: zir.Inst.Index,
+ cond: zir.Inst.Ref,
+ then_src: ast.Node.Index,
+ else_src: ast.Node.Index,
+ then_result: zir.Inst.Ref,
+ else_result: zir.Inst.Ref,
+ main_block: zir.Inst.Index,
+ then_break_block: zir.Inst.Index,
+ break_tag: zir.Inst.Tag,
+) InnerError!zir.Inst.Ref {
+ // We now have enough information to decide whether the result instruction should
+ // be communicated via result location pointer or break instructions.
+ const strat = rl.strategy(block_scope);
+ const astgen = block_scope.astgen;
+ switch (strat.tag) {
+ .break_void => {
+ if (!astgen.refIsNoReturn(then_result)) {
+ _ = try then_scope.addBreak(break_tag, then_break_block, .void_value);
+ }
+ const elide_else = if (else_result != .none) astgen.refIsNoReturn(else_result) else false;
+ if (!elide_else) {
+ _ = try else_scope.addBreak(break_tag, main_block, .void_value);
+ }
+ assert(!strat.elide_store_to_block_ptr_instructions);
+ try setCondBrPayload(condbr, cond, then_scope, else_scope);
+ return astgen.indexToRef(main_block);
+ },
+ .break_operand => {
+ if (!astgen.refIsNoReturn(then_result)) {
+ _ = try then_scope.addBreak(break_tag, then_break_block, then_result);
+ }
+ if (else_result != .none) {
+ if (!astgen.refIsNoReturn(else_result)) {
+ _ = try else_scope.addBreak(break_tag, main_block, else_result);
+ }
+ } else {
+ _ = try else_scope.addBreak(break_tag, main_block, .void_value);
+ }
+ if (strat.elide_store_to_block_ptr_instructions) {
+ try setCondBrPayloadElideBlockStorePtr(condbr, cond, then_scope, else_scope);
+ } else {
+ try setCondBrPayload(condbr, cond, then_scope, else_scope);
+ }
+ const block_ref = astgen.indexToRef(main_block);
+ switch (rl) {
+ .ref => return block_ref,
+ else => return rvalue(parent_gz, parent_scope, rl, block_ref, node),
+ }
+ },
+ }
+}
+
+/// Return whether the identifier names of two tokens are equal. Resolves @""
+/// tokens without allocating.
+/// OK in theory it could do it without allocating. This implementation
+/// allocates when the @"" form is used.
+fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool {
+ const ident_name_1 = try mod.identifierTokenString(scope, token1);
+ const ident_name_2 = try mod.identifierTokenString(scope, token2);
+ return mem.eql(u8, ident_name_1, ident_name_2);
+}
+
+pub fn fieldAccess(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const astgen = gz.astgen;
+ const mod = astgen.mod;
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const node_datas = tree.nodes.items(.data);
+
+ const object_node = node_datas[node].lhs;
+ const dot_token = main_tokens[node];
+ const field_ident = dot_token + 1;
+ const str_index = try gz.identAsString(field_ident);
+ switch (rl) {
+ .ref => return gz.addPlNode(.field_ptr, node, zir.Inst.Field{
+ .lhs = try expr(gz, scope, .ref, object_node),
+ .field_name_start = str_index,
+ }),
+ else => return rvalue(gz, scope, rl, try gz.addPlNode(.field_val, node, zir.Inst.Field{
+ .lhs = try expr(gz, scope, .none, object_node),
+ .field_name_start = str_index,
+ }), node),
+ }
+}
+
+fn arrayAccess(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const node_datas = tree.nodes.items(.data);
+ switch (rl) {
+ .ref => return gz.addBin(
+ .elem_ptr,
+ try expr(gz, scope, .ref, node_datas[node].lhs),
+ try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs),
+ ),
+ else => return rvalue(gz, scope, rl, try gz.addBin(
+ .elem_val,
+ try expr(gz, scope, .none, node_datas[node].lhs),
+ try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs),
+ ), node),
+ }
+}
+
+fn simpleBinOp(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ op_inst_tag: zir.Inst.Tag,
+) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const node_datas = tree.nodes.items(.data);
+
+ const result = try gz.addPlNode(op_inst_tag, node, zir.Inst.Bin{
+ .lhs = try expr(gz, scope, .none, node_datas[node].lhs),
+ .rhs = try expr(gz, scope, .none, node_datas[node].rhs),
+ });
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn simpleStrTok(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ ident_token: ast.TokenIndex,
+ node: ast.Node.Index,
+ op_inst_tag: zir.Inst.Tag,
+) InnerError!zir.Inst.Ref {
+ const str_index = try gz.identAsString(ident_token);
+ const result = try gz.addStrTok(op_inst_tag, str_index, ident_token);
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn boolBinOp(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ zir_tag: zir.Inst.Tag,
+) InnerError!zir.Inst.Ref {
+ const node_datas = gz.tree().nodes.items(.data);
+
+ const lhs = try expr(gz, scope, .{ .ty = .bool_type }, node_datas[node].lhs);
+ const bool_br = try gz.addBoolBr(zir_tag, lhs);
+
+ var rhs_scope: GenZir = .{
+ .parent = scope,
+ .astgen = gz.astgen,
+ .force_comptime = gz.force_comptime,
+ };
+ defer rhs_scope.instructions.deinit(gz.astgen.mod.gpa);
+ const rhs = try expr(&rhs_scope, &rhs_scope.base, .{ .ty = .bool_type }, node_datas[node].rhs);
+ _ = try rhs_scope.addBreak(.break_inline, bool_br, rhs);
+ try rhs_scope.setBoolBrBody(bool_br);
+
+ const block_ref = gz.astgen.indexToRef(bool_br);
+ return rvalue(gz, scope, rl, block_ref, node);
+}
+
+fn ifExpr(
+ parent_gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ if_full: ast.full.If,
+) InnerError!zir.Inst.Ref {
+ const mod = parent_gz.astgen.mod;
+
+ var block_scope: GenZir = .{
+ .parent = scope,
+ .astgen = parent_gz.astgen,
+ .force_comptime = parent_gz.force_comptime,
+ .instructions = .{},
+ };
+ block_scope.setBreakResultLoc(rl);
+ defer block_scope.instructions.deinit(mod.gpa);
+
+ const cond = c: {
+ // TODO https://github.com/ziglang/zig/issues/7929
+ if (if_full.error_token) |error_token| {
+ return mod.failTok(scope, error_token, "TODO implement if error union", .{});
+ } else if (if_full.payload_token) |payload_token| {
+ return mod.failTok(scope, payload_token, "TODO implement if optional", .{});
+ } else {
+ break :c try expr(&block_scope, &block_scope.base, .{ .ty = .bool_type }, if_full.ast.cond_expr);
+ }
+ };
+
+ const condbr = try block_scope.addCondBr(.condbr, node);
+
+ const block = try parent_gz.addBlock(.block, node);
+ try parent_gz.instructions.append(mod.gpa, block);
+ try block_scope.setBlockBody(block);
+
+ var then_scope: GenZir = .{
+ .parent = scope,
+ .astgen = parent_gz.astgen,
+ .force_comptime = block_scope.force_comptime,
+ .instructions = .{},
+ };
+ defer then_scope.instructions.deinit(mod.gpa);
+
+ // declare payload to the then_scope
+ const then_sub_scope = &then_scope.base;
+
+ block_scope.break_count += 1;
+ const then_result = try expr(&then_scope, then_sub_scope, block_scope.break_result_loc, if_full.ast.then_expr);
+ // We hold off on the break instructions as well as copying the then/else
+ // instructions into place until we know whether to keep store_to_block_ptr
+ // instructions or not.
+
+ var else_scope: GenZir = .{
+ .parent = scope,
+ .astgen = parent_gz.astgen,
+ .force_comptime = block_scope.force_comptime,
+ .instructions = .{},
+ };
+ defer else_scope.instructions.deinit(mod.gpa);
+
+ const else_node = if_full.ast.else_expr;
+ const else_info: struct {
+ src: ast.Node.Index,
+ result: zir.Inst.Ref,
+ } = if (else_node != 0) blk: {
+ block_scope.break_count += 1;
+ const sub_scope = &else_scope.base;
+ break :blk .{
+ .src = else_node,
+ .result = try expr(&else_scope, sub_scope, block_scope.break_result_loc, else_node),
+ };
+ } else .{
+ .src = if_full.ast.then_expr,
+ .result = .none,
+ };
+
+ return finishThenElseBlock(
+ parent_gz,
+ scope,
+ rl,
+ node,
+ &block_scope,
+ &then_scope,
+ &else_scope,
+ condbr,
+ cond,
+ if_full.ast.then_expr,
+ else_info.src,
+ then_result,
+ else_info.result,
+ block,
+ block,
+ .@"break",
+ );
+}
+
+fn setCondBrPayload(
+ condbr: zir.Inst.Index,
+ cond: zir.Inst.Ref,
+ then_scope: *GenZir,
+ else_scope: *GenZir,
+) !void {
+ const astgen = then_scope.astgen;
+
+ try astgen.extra.ensureCapacity(astgen.mod.gpa, astgen.extra.items.len +
+ @typeInfo(zir.Inst.CondBr).Struct.fields.len +
+ then_scope.instructions.items.len + else_scope.instructions.items.len);
+
+ const zir_datas = astgen.instructions.items(.data);
+ zir_datas[condbr].pl_node.payload_index = astgen.addExtraAssumeCapacity(zir.Inst.CondBr{
+ .condition = cond,
+ .then_body_len = @intCast(u32, then_scope.instructions.items.len),
+ .else_body_len = @intCast(u32, else_scope.instructions.items.len),
+ });
+ astgen.extra.appendSliceAssumeCapacity(then_scope.instructions.items);
+ astgen.extra.appendSliceAssumeCapacity(else_scope.instructions.items);
+}
+
+/// If `elide_block_store_ptr` is set, expects to find exactly 1 .store_to_block_ptr instruction.
+fn setCondBrPayloadElideBlockStorePtr(
+ condbr: zir.Inst.Index,
+ cond: zir.Inst.Ref,
+ then_scope: *GenZir,
+ else_scope: *GenZir,
+) !void {
+ const astgen = then_scope.astgen;
+
+ try astgen.extra.ensureCapacity(astgen.mod.gpa, astgen.extra.items.len +
+ @typeInfo(zir.Inst.CondBr).Struct.fields.len +
+ then_scope.instructions.items.len + else_scope.instructions.items.len - 2);
+
+ const zir_datas = astgen.instructions.items(.data);
+ zir_datas[condbr].pl_node.payload_index = astgen.addExtraAssumeCapacity(zir.Inst.CondBr{
+ .condition = cond,
+ .then_body_len = @intCast(u32, then_scope.instructions.items.len - 1),
+ .else_body_len = @intCast(u32, else_scope.instructions.items.len - 1),
+ });
+
+ const zir_tags = astgen.instructions.items(.tag);
+ for ([_]*GenZir{ then_scope, else_scope }) |scope| {
+ for (scope.instructions.items) |src_inst| {
+ if (zir_tags[src_inst] != .store_to_block_ptr) {
+ astgen.extra.appendAssumeCapacity(src_inst);
+ }
+ }
+ }
+}
+
+fn whileExpr(
+ parent_gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ while_full: ast.full.While,
+) InnerError!zir.Inst.Ref {
+ const mod = parent_gz.astgen.mod;
+ if (while_full.label_token) |label_token| {
+ try checkLabelRedefinition(mod, scope, label_token);
+ }
+
+ const is_inline = parent_gz.force_comptime or while_full.inline_token != null;
+ const loop_tag: zir.Inst.Tag = if (is_inline) .block_inline else .loop;
+ const loop_block = try parent_gz.addBlock(loop_tag, node);
+ try parent_gz.instructions.append(mod.gpa, loop_block);
+
+ var loop_scope: GenZir = .{
+ .parent = scope,
+ .astgen = parent_gz.astgen,
+ .force_comptime = parent_gz.force_comptime,
+ .instructions = .{},
+ };
+ loop_scope.setBreakResultLoc(rl);
+ defer loop_scope.instructions.deinit(mod.gpa);
+
+ var continue_scope: GenZir = .{
+ .parent = &loop_scope.base,
+ .astgen = parent_gz.astgen,
+ .force_comptime = loop_scope.force_comptime,
+ .instructions = .{},
+ };
+ defer continue_scope.instructions.deinit(mod.gpa);
+
+ const cond = c: {
+ // TODO https://github.com/ziglang/zig/issues/7929
+ if (while_full.error_token) |error_token| {
+ return mod.failTok(scope, error_token, "TODO implement while error union", .{});
+ } else if (while_full.payload_token) |payload_token| {
+ return mod.failTok(scope, payload_token, "TODO implement while optional", .{});
+ } else {
+ const bool_type_rl: ResultLoc = .{ .ty = .bool_type };
+ break :c try expr(&continue_scope, &continue_scope.base, bool_type_rl, while_full.ast.cond_expr);
+ }
+ };
+
+ const condbr_tag: zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr;
+ const condbr = try continue_scope.addCondBr(condbr_tag, node);
+ const block_tag: zir.Inst.Tag = if (is_inline) .block_inline else .block;
+ const cond_block = try loop_scope.addBlock(block_tag, node);
+ try loop_scope.instructions.append(mod.gpa, cond_block);
+ try continue_scope.setBlockBody(cond_block);
+
+ // TODO avoid emitting the continue expr when there
+ // are no jumps to it. This happens when the last statement of a while body is noreturn
+ // and there are no `continue` statements.
+ if (while_full.ast.cont_expr != 0) {
+ _ = try expr(&loop_scope, &loop_scope.base, .{ .ty = .void_type }, while_full.ast.cont_expr);
+ }
+ const repeat_tag: zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat;
+ _ = try loop_scope.addNode(repeat_tag, node);
+
+ try loop_scope.setBlockBody(loop_block);
+ loop_scope.break_block = loop_block;
+ loop_scope.continue_block = cond_block;
+ if (while_full.label_token) |label_token| {
+ loop_scope.label = @as(?GenZir.Label, GenZir.Label{
+ .token = label_token,
+ .block_inst = loop_block,
+ });
+ }
+
+ var then_scope: GenZir = .{
+ .parent = &continue_scope.base,
+ .astgen = parent_gz.astgen,
+ .force_comptime = continue_scope.force_comptime,
+ .instructions = .{},
+ };
+ defer then_scope.instructions.deinit(mod.gpa);
+
+ const then_sub_scope = &then_scope.base;
+
+ loop_scope.break_count += 1;
+ const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr);
+
+ var else_scope: GenZir = .{
+ .parent = &continue_scope.base,
+ .astgen = parent_gz.astgen,
+ .force_comptime = continue_scope.force_comptime,
+ .instructions = .{},
+ };
+ defer else_scope.instructions.deinit(mod.gpa);
+
+ const else_node = while_full.ast.else_expr;
+ const else_info: struct {
+ src: ast.Node.Index,
+ result: zir.Inst.Ref,
+ } = if (else_node != 0) blk: {
+ loop_scope.break_count += 1;
+ const sub_scope = &else_scope.base;
+ break :blk .{
+ .src = else_node,
+ .result = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node),
+ };
+ } else .{
+ .src = while_full.ast.then_expr,
+ .result = .none,
+ };
+
+ if (loop_scope.label) |some| {
+ if (!some.used) {
+ return mod.failTok(scope, some.token, "unused while loop label", .{});
+ }
+ }
+ const break_tag: zir.Inst.Tag = if (is_inline) .break_inline else .@"break";
+ return finishThenElseBlock(
+ parent_gz,
+ scope,
+ rl,
+ node,
+ &loop_scope,
+ &then_scope,
+ &else_scope,
+ condbr,
+ cond,
+ while_full.ast.then_expr,
+ else_info.src,
+ then_result,
+ else_info.result,
+ loop_block,
+ cond_block,
+ break_tag,
+ );
+}
+
+fn forExpr(
+ parent_gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ for_full: ast.full.While,
+) InnerError!zir.Inst.Ref {
+ const mod = parent_gz.astgen.mod;
+ if (for_full.label_token) |label_token| {
+ try checkLabelRedefinition(mod, scope, label_token);
+ }
+ // Set up variables and constants.
+ const is_inline = parent_gz.force_comptime or for_full.inline_token != null;
+ const tree = parent_gz.tree();
+ const token_tags = tree.tokens.items(.tag);
+
+ const array_ptr = try expr(parent_gz, scope, .ref, for_full.ast.cond_expr);
+ const len = try parent_gz.addUnNode(.indexable_ptr_len, array_ptr, for_full.ast.cond_expr);
+
+ const index_ptr = blk: {
+ const index_ptr = try parent_gz.addUnNode(.alloc, .usize_type, node);
+ // initialize to zero
+ _ = try parent_gz.addBin(.store, index_ptr, .zero_usize);
+ break :blk index_ptr;
+ };
+
+ const loop_tag: zir.Inst.Tag = if (is_inline) .block_inline else .loop;
+ const loop_block = try parent_gz.addBlock(loop_tag, node);
+ try parent_gz.instructions.append(mod.gpa, loop_block);
+
+ var loop_scope: GenZir = .{
+ .parent = scope,
+ .astgen = parent_gz.astgen,
+ .force_comptime = parent_gz.force_comptime,
+ .instructions = .{},
+ };
+ loop_scope.setBreakResultLoc(rl);
+ defer loop_scope.instructions.deinit(mod.gpa);
+
+ var cond_scope: GenZir = .{
+ .parent = &loop_scope.base,
+ .astgen = parent_gz.astgen,
+ .force_comptime = loop_scope.force_comptime,
+ .instructions = .{},
+ };
+ defer cond_scope.instructions.deinit(mod.gpa);
+
+ // check condition i < array_expr.len
+ const index = try cond_scope.addUnNode(.load, index_ptr, for_full.ast.cond_expr);
+ const cond = try cond_scope.addPlNode(.cmp_lt, for_full.ast.cond_expr, zir.Inst.Bin{
+ .lhs = index,
+ .rhs = len,
+ });
+
+ const condbr_tag: zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr;
+ const condbr = try cond_scope.addCondBr(condbr_tag, node);
+ const block_tag: zir.Inst.Tag = if (is_inline) .block_inline else .block;
+ const cond_block = try loop_scope.addBlock(block_tag, node);
+ try loop_scope.instructions.append(mod.gpa, cond_block);
+ try cond_scope.setBlockBody(cond_block);
+
+ // Increment the index variable.
+ const index_2 = try loop_scope.addUnNode(.load, index_ptr, for_full.ast.cond_expr);
+ const index_plus_one = try loop_scope.addPlNode(.add, node, zir.Inst.Bin{
+ .lhs = index_2,
+ .rhs = .one_usize,
+ });
+ _ = try loop_scope.addBin(.store, index_ptr, index_plus_one);
+ const repeat_tag: zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat;
+ _ = try loop_scope.addNode(repeat_tag, node);
+
+ try loop_scope.setBlockBody(loop_block);
+ loop_scope.break_block = loop_block;
+ loop_scope.continue_block = cond_block;
+ if (for_full.label_token) |label_token| {
+ loop_scope.label = @as(?GenZir.Label, GenZir.Label{
+ .token = label_token,
+ .block_inst = loop_block,
+ });
+ }
+
+ var then_scope: GenZir = .{
+ .parent = &cond_scope.base,
+ .astgen = parent_gz.astgen,
+ .force_comptime = cond_scope.force_comptime,
+ .instructions = .{},
+ };
+ defer then_scope.instructions.deinit(mod.gpa);
+
+ var index_scope: Scope.LocalPtr = undefined;
+ const then_sub_scope = blk: {
+ const payload_token = for_full.payload_token.?;
+ const ident = if (token_tags[payload_token] == .asterisk)
+ payload_token + 1
+ else
+ payload_token;
+ const is_ptr = ident != payload_token;
+ const value_name = tree.tokenSlice(ident);
+ if (!mem.eql(u8, value_name, "_")) {
+ return mod.failNode(&then_scope.base, ident, "TODO implement for loop value payload", .{});
+ } else if (is_ptr) {
+ return mod.failTok(&then_scope.base, payload_token, "pointer modifier invalid on discard", .{});
+ }
+
+ const index_token = if (token_tags[ident + 1] == .comma)
+ ident + 2
+ else
+ break :blk &then_scope.base;
+ if (mem.eql(u8, tree.tokenSlice(index_token), "_")) {
+ return mod.failTok(&then_scope.base, index_token, "discard of index capture; omit it instead", .{});
+ }
+ const index_name = try mod.identifierTokenString(&then_scope.base, index_token);
+ index_scope = .{
+ .parent = &then_scope.base,
+ .gen_zir = &then_scope,
+ .name = index_name,
+ .ptr = index_ptr,
+ .src = parent_gz.tokSrcLoc(index_token),
+ };
+ break :blk &index_scope.base;
+ };
+
+ loop_scope.break_count += 1;
+ const then_result = try expr(&then_scope, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr);
+
+ var else_scope: GenZir = .{
+ .parent = &cond_scope.base,
+ .astgen = parent_gz.astgen,
+ .force_comptime = cond_scope.force_comptime,
+ .instructions = .{},
+ };
+ defer else_scope.instructions.deinit(mod.gpa);
+
+ const else_node = for_full.ast.else_expr;
+ const else_info: struct {
+ src: ast.Node.Index,
+ result: zir.Inst.Ref,
+ } = if (else_node != 0) blk: {
+ loop_scope.break_count += 1;
+ const sub_scope = &else_scope.base;
+ break :blk .{
+ .src = else_node,
+ .result = try expr(&else_scope, sub_scope, loop_scope.break_result_loc, else_node),
+ };
+ } else .{
+ .src = for_full.ast.then_expr,
+ .result = .none,
+ };
+
+ if (loop_scope.label) |some| {
+ if (!some.used) {
+ return mod.failTok(scope, some.token, "unused for loop label", .{});
+ }
+ }
+ const break_tag: zir.Inst.Tag = if (is_inline) .break_inline else .@"break";
+ return finishThenElseBlock(
+ parent_gz,
+ scope,
+ rl,
+ node,
+ &loop_scope,
+ &then_scope,
+ &else_scope,
+ condbr,
+ cond,
+ for_full.ast.then_expr,
+ else_info.src,
+ then_result,
+ else_info.result,
+ loop_block,
+ cond_block,
+ break_tag,
+ );
+}
+
+fn getRangeNode(
+ node_tags: []const ast.Node.Tag,
+ node_datas: []const ast.Node.Data,
+ node: ast.Node.Index,
+) ?ast.Node.Index {
+ switch (node_tags[node]) {
+ .switch_range => return node,
+ .grouped_expression => unreachable,
+ else => return null,
+ }
+}
+
+pub const SwitchProngSrc = union(enum) {
+ scalar: u32,
+ multi: Multi,
+ range: Multi,
+
+ pub const Multi = struct {
+ prong: u32,
+ item: u32,
+ };
+
+ pub const RangeExpand = enum { none, first, last };
+
+ /// This function is intended to be called only when it is certain that we need
+ /// the LazySrcLoc in order to emit a compile error.
+ pub fn resolve(
+ prong_src: SwitchProngSrc,
+ decl: *Decl,
+ switch_node_offset: i32,
+ range_expand: RangeExpand,
+ ) LazySrcLoc {
+ @setCold(true);
+ const switch_node = decl.relativeToNodeIndex(switch_node_offset);
+ const tree = decl.container.file_scope.base.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange);
+ const case_nodes = tree.extra_data[extra.start..extra.end];
+
+ var multi_i: u32 = 0;
+ var scalar_i: u32 = 0;
+ for (case_nodes) |case_node| {
+ const case = switch (node_tags[case_node]) {
+ .switch_case_one => tree.switchCaseOne(case_node),
+ .switch_case => tree.switchCase(case_node),
+ else => unreachable,
+ };
+ if (case.ast.values.len == 0)
+ continue;
+ if (case.ast.values.len == 1 and
+ node_tags[case.ast.values[0]] == .identifier and
+ mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
+ {
+ continue;
+ }
+ const is_multi = case.ast.values.len != 1 or
+ getRangeNode(node_tags, node_datas, case.ast.values[0]) != null;
+
+ switch (prong_src) {
+ .scalar => |i| if (!is_multi and i == scalar_i) return LazySrcLoc{
+ .node_offset = decl.nodeIndexToRelative(case.ast.values[0]),
+ },
+ .multi => |s| if (is_multi and s.prong == multi_i) {
+ var item_i: u32 = 0;
+ for (case.ast.values) |item_node| {
+ if (getRangeNode(node_tags, node_datas, item_node) != null)
+ continue;
+
+ if (item_i == s.item) return LazySrcLoc{
+ .node_offset = decl.nodeIndexToRelative(item_node),
+ };
+ item_i += 1;
+ } else unreachable;
+ },
+ .range => |s| if (is_multi and s.prong == multi_i) {
+ var range_i: u32 = 0;
+ for (case.ast.values) |item_node| {
+ const range = getRangeNode(node_tags, node_datas, item_node) orelse continue;
+
+ if (range_i == s.item) switch (range_expand) {
+ .none => return LazySrcLoc{
+ .node_offset = decl.nodeIndexToRelative(item_node),
+ },
+ .first => return LazySrcLoc{
+ .node_offset = decl.nodeIndexToRelative(node_datas[range].lhs),
+ },
+ .last => return LazySrcLoc{
+ .node_offset = decl.nodeIndexToRelative(node_datas[range].rhs),
+ },
+ };
+ range_i += 1;
+ } else unreachable;
+ },
+ }
+ if (is_multi) {
+ multi_i += 1;
+ } else {
+ scalar_i += 1;
+ }
+ } else unreachable;
+ }
+};
+
+fn switchExpr(
+ parent_gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ switch_node: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const astgen = parent_gz.astgen;
+ const mod = astgen.mod;
+ const gpa = mod.gpa;
+ const tree = parent_gz.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const main_tokens = tree.nodes.items(.main_token);
+ const token_tags = tree.tokens.items(.tag);
+ const operand_node = node_datas[switch_node].lhs;
+ const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange);
+ const case_nodes = tree.extra_data[extra.start..extra.end];
+
+ // We perform two passes over the AST. This first pass is to collect information
+ // for the following variables, make note of the special prong AST node index,
+ // and bail out with a compile error if there are multiple special prongs present.
+ var any_payload_is_ref = false;
+ var scalar_cases_len: u32 = 0;
+ var multi_cases_len: u32 = 0;
+ var special_prong: zir.SpecialProng = .none;
+ var special_node: ast.Node.Index = 0;
+ var else_src: ?LazySrcLoc = null;
+ var underscore_src: ?LazySrcLoc = null;
+ for (case_nodes) |case_node| {
+ const case = switch (node_tags[case_node]) {
+ .switch_case_one => tree.switchCaseOne(case_node),
+ .switch_case => tree.switchCase(case_node),
+ else => unreachable,
+ };
+ if (case.payload_token) |payload_token| {
+ if (token_tags[payload_token] == .asterisk) {
+ any_payload_is_ref = true;
+ }
+ }
+ // Check for else/`_` prong.
+ if (case.ast.values.len == 0) {
+ const case_src = parent_gz.tokSrcLoc(case.ast.arrow_token - 1);
+ if (else_src) |src| {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ scope,
+ case_src,
+ "multiple else prongs in switch expression",
+ .{},
+ );
+ errdefer msg.destroy(gpa);
+ try mod.errNote(scope, src, msg, "previous else prong is here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(scope, msg);
+ } else if (underscore_src) |some_underscore| {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ scope,
+ parent_gz.nodeSrcLoc(switch_node),
+ "else and '_' prong in switch expression",
+ .{},
+ );
+ errdefer msg.destroy(gpa);
+ try mod.errNote(scope, case_src, msg, "else prong is here", .{});
+ try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(scope, msg);
+ }
+ special_node = case_node;
+ special_prong = .@"else";
+ else_src = case_src;
+ continue;
+ } else if (case.ast.values.len == 1 and
+ node_tags[case.ast.values[0]] == .identifier and
+ mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
+ {
+ const case_src = parent_gz.tokSrcLoc(case.ast.arrow_token - 1);
+ if (underscore_src) |src| {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ scope,
+ case_src,
+ "multiple '_' prongs in switch expression",
+ .{},
+ );
+ errdefer msg.destroy(gpa);
+ try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(scope, msg);
+ } else if (else_src) |some_else| {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ scope,
+ parent_gz.nodeSrcLoc(switch_node),
+ "else and '_' prong in switch expression",
+ .{},
+ );
+ errdefer msg.destroy(gpa);
+ try mod.errNote(scope, some_else, msg, "else prong is here", .{});
+ try mod.errNote(scope, case_src, msg, "'_' prong is here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(scope, msg);
+ }
+ special_node = case_node;
+ special_prong = .under;
+ underscore_src = case_src;
+ continue;
+ }
+
+ if (case.ast.values.len == 1 and
+ getRangeNode(node_tags, node_datas, case.ast.values[0]) == null)
+ {
+ scalar_cases_len += 1;
+ } else {
+ multi_cases_len += 1;
+ }
+ }
+
+ const operand_rl: ResultLoc = if (any_payload_is_ref) .ref else .none;
+ const operand = try expr(parent_gz, scope, operand_rl, operand_node);
+ // We need the type of the operand to use as the result location for all the prong items.
+ const typeof_tag: zir.Inst.Tag = if (any_payload_is_ref) .typeof_elem else .typeof;
+ const operand_ty_inst = try parent_gz.addUnNode(typeof_tag, operand, operand_node);
+ const item_rl: ResultLoc = .{ .ty = operand_ty_inst };
+
+ // Contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti.
+ // This is the header as well as the optional else prong body, as well as all the
+ // scalar cases.
+ // At the end we will memcpy this into place.
+ var scalar_cases_payload = ArrayListUnmanaged(u32){};
+ defer scalar_cases_payload.deinit(gpa);
+ // Same deal, but this is only the `extra` data for the multi cases.
+ var multi_cases_payload = ArrayListUnmanaged(u32){};
+ defer multi_cases_payload.deinit(gpa);
+
+ var block_scope: GenZir = .{
+ .parent = scope,
+ .astgen = astgen,
+ .force_comptime = parent_gz.force_comptime,
+ .instructions = .{},
+ };
+ block_scope.setBreakResultLoc(rl);
+ defer block_scope.instructions.deinit(gpa);
+
+ // This gets added to the parent block later, after the item expressions.
+ const switch_block = try parent_gz.addBlock(undefined, switch_node);
+
+ // We re-use this same scope for all cases, including the special prong, if any.
+ var case_scope: GenZir = .{
+ .parent = &block_scope.base,
+ .astgen = astgen,
+ .force_comptime = parent_gz.force_comptime,
+ .instructions = .{},
+ };
+ defer case_scope.instructions.deinit(gpa);
+
+ // Do the else/`_` first because it goes first in the payload.
+ var capture_val_scope: Scope.LocalVal = undefined;
+ if (special_node != 0) {
+ const case = switch (node_tags[special_node]) {
+ .switch_case_one => tree.switchCaseOne(special_node),
+ .switch_case => tree.switchCase(special_node),
+ else => unreachable,
+ };
+ const sub_scope = blk: {
+ const payload_token = case.payload_token orelse break :blk &case_scope.base;
+ const ident = if (token_tags[payload_token] == .asterisk)
+ payload_token + 1
+ else
+ payload_token;
+ const is_ptr = ident != payload_token;
+ if (mem.eql(u8, tree.tokenSlice(ident), "_")) {
+ if (is_ptr) {
+ return mod.failTok(&case_scope.base, payload_token, "pointer modifier invalid on discard", .{});
+ }
+ break :blk &case_scope.base;
+ }
+ const capture_tag: zir.Inst.Tag = if (is_ptr)
+ .switch_capture_else_ref
+ else
+ .switch_capture_else;
+ const capture = try case_scope.add(.{
+ .tag = capture_tag,
+ .data = .{ .switch_capture = .{
+ .switch_inst = switch_block,
+ .prong_index = undefined,
+ } },
+ });
+ const capture_name = try mod.identifierTokenString(&parent_gz.base, payload_token);
+ capture_val_scope = .{
+ .parent = &case_scope.base,
+ .gen_zir = &case_scope,
+ .name = capture_name,
+ .inst = capture,
+ .src = parent_gz.tokSrcLoc(payload_token),
+ };
+ break :blk &capture_val_scope.base;
+ };
+ const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr);
+ if (!astgen.refIsNoReturn(case_result)) {
+ block_scope.break_count += 1;
+ _ = try case_scope.addBreak(.@"break", switch_block, case_result);
+ }
+ // Documentation for this: `zir.Inst.SwitchBlock` and `zir.Inst.SwitchBlockMulti`.
+ try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
+ 3 + // operand, scalar_cases_len, else body len
+ @boolToInt(multi_cases_len != 0) +
+ case_scope.instructions.items.len);
+ scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand));
+ scalar_cases_payload.appendAssumeCapacity(scalar_cases_len);
+ if (multi_cases_len != 0) {
+ scalar_cases_payload.appendAssumeCapacity(multi_cases_len);
+ }
+ scalar_cases_payload.appendAssumeCapacity(@intCast(u32, case_scope.instructions.items.len));
+ scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items);
+ } else {
+ // Documentation for this: `zir.Inst.SwitchBlock` and `zir.Inst.SwitchBlockMulti`.
+ try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
+ 2 + // operand, scalar_cases_len
+ @boolToInt(multi_cases_len != 0));
+ scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand));
+ scalar_cases_payload.appendAssumeCapacity(scalar_cases_len);
+ if (multi_cases_len != 0) {
+ scalar_cases_payload.appendAssumeCapacity(multi_cases_len);
+ }
+ }
+
+ // In this pass we generate all the item and prong expressions except the special case.
+ var multi_case_index: u32 = 0;
+ var scalar_case_index: u32 = 0;
+ for (case_nodes) |case_node| {
+ if (case_node == special_node)
+ continue;
+ const case = switch (node_tags[case_node]) {
+ .switch_case_one => tree.switchCaseOne(case_node),
+ .switch_case => tree.switchCase(case_node),
+ else => unreachable,
+ };
+
+ // Reset the scope.
+ case_scope.instructions.shrinkRetainingCapacity(0);
+
+ const is_multi_case = case.ast.values.len != 1 or
+ getRangeNode(node_tags, node_datas, case.ast.values[0]) != null;
+
+ const sub_scope = blk: {
+ const payload_token = case.payload_token orelse break :blk &case_scope.base;
+ const ident = if (token_tags[payload_token] == .asterisk)
+ payload_token + 1
+ else
+ payload_token;
+ const is_ptr = ident != payload_token;
+ if (mem.eql(u8, tree.tokenSlice(ident), "_")) {
+ if (is_ptr) {
+ return mod.failTok(&case_scope.base, payload_token, "pointer modifier invalid on discard", .{});
+ }
+ break :blk &case_scope.base;
+ }
+ const is_multi_case_bits: u2 = @boolToInt(is_multi_case);
+ const is_ptr_bits: u2 = @boolToInt(is_ptr);
+ const capture_tag: zir.Inst.Tag = switch ((is_multi_case_bits << 1) | is_ptr_bits) {
+ 0b00 => .switch_capture,
+ 0b01 => .switch_capture_ref,
+ 0b10 => .switch_capture_multi,
+ 0b11 => .switch_capture_multi_ref,
+ };
+ const capture_index = if (is_multi_case) ci: {
+ multi_case_index += 1;
+ break :ci multi_case_index - 1;
+ } else ci: {
+ scalar_case_index += 1;
+ break :ci scalar_case_index - 1;
+ };
+ const capture = try case_scope.add(.{
+ .tag = capture_tag,
+ .data = .{ .switch_capture = .{
+ .switch_inst = switch_block,
+ .prong_index = capture_index,
+ } },
+ });
+ const capture_name = try mod.identifierTokenString(&parent_gz.base, payload_token);
+ capture_val_scope = .{
+ .parent = &case_scope.base,
+ .gen_zir = &case_scope,
+ .name = capture_name,
+ .inst = capture,
+ .src = parent_gz.tokSrcLoc(payload_token),
+ };
+ break :blk &capture_val_scope.base;
+ };
+
+ if (is_multi_case) {
+ // items_len, ranges_len, body_len
+ const header_index = multi_cases_payload.items.len;
+ try multi_cases_payload.resize(gpa, multi_cases_payload.items.len + 3);
+
+ // items
+ var items_len: u32 = 0;
+ for (case.ast.values) |item_node| {
+ if (getRangeNode(node_tags, node_datas, item_node) != null) continue;
+ items_len += 1;
+
+ const item_inst = try comptimeExpr(parent_gz, scope, item_rl, item_node);
+ try multi_cases_payload.append(gpa, @enumToInt(item_inst));
+ }
+
+ // ranges
+ var ranges_len: u32 = 0;
+ for (case.ast.values) |item_node| {
+ const range = getRangeNode(node_tags, node_datas, item_node) orelse continue;
+ ranges_len += 1;
+
+ const first = try comptimeExpr(parent_gz, scope, item_rl, node_datas[range].lhs);
+ const last = try comptimeExpr(parent_gz, scope, item_rl, node_datas[range].rhs);
+ try multi_cases_payload.appendSlice(gpa, &[_]u32{
+ @enumToInt(first), @enumToInt(last),
+ });
+ }
+
+ const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr);
+ if (!astgen.refIsNoReturn(case_result)) {
+ block_scope.break_count += 1;
+ _ = try case_scope.addBreak(.@"break", switch_block, case_result);
+ }
+
+ multi_cases_payload.items[header_index + 0] = items_len;
+ multi_cases_payload.items[header_index + 1] = ranges_len;
+ multi_cases_payload.items[header_index + 2] = @intCast(u32, case_scope.instructions.items.len);
+ try multi_cases_payload.appendSlice(gpa, case_scope.instructions.items);
+ } else {
+ const item_node = case.ast.values[0];
+ const item_inst = try comptimeExpr(parent_gz, scope, item_rl, item_node);
+ const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr);
+ if (!astgen.refIsNoReturn(case_result)) {
+ block_scope.break_count += 1;
+ _ = try case_scope.addBreak(.@"break", switch_block, case_result);
+ }
+ try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
+ 2 + case_scope.instructions.items.len);
+ scalar_cases_payload.appendAssumeCapacity(@enumToInt(item_inst));
+ scalar_cases_payload.appendAssumeCapacity(@intCast(u32, case_scope.instructions.items.len));
+ scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items);
+ }
+ }
+ // Now that the item expressions are generated we can add this.
+ try parent_gz.instructions.append(gpa, switch_block);
+
+ const ref_bit: u4 = @boolToInt(any_payload_is_ref);
+ const multi_bit: u4 = @boolToInt(multi_cases_len != 0);
+ const special_prong_bits: u4 = @enumToInt(special_prong);
+ comptime {
+ assert(@enumToInt(zir.SpecialProng.none) == 0b00);
+ assert(@enumToInt(zir.SpecialProng.@"else") == 0b01);
+ assert(@enumToInt(zir.SpecialProng.under) == 0b10);
+ }
+ const zir_tags = astgen.instructions.items(.tag);
+ zir_tags[switch_block] = switch ((ref_bit << 3) | (special_prong_bits << 1) | multi_bit) {
+ 0b0_00_0 => .switch_block,
+ 0b0_00_1 => .switch_block_multi,
+ 0b0_01_0 => .switch_block_else,
+ 0b0_01_1 => .switch_block_else_multi,
+ 0b0_10_0 => .switch_block_under,
+ 0b0_10_1 => .switch_block_under_multi,
+ 0b1_00_0 => .switch_block_ref,
+ 0b1_00_1 => .switch_block_ref_multi,
+ 0b1_01_0 => .switch_block_ref_else,
+ 0b1_01_1 => .switch_block_ref_else_multi,
+ 0b1_10_0 => .switch_block_ref_under,
+ 0b1_10_1 => .switch_block_ref_under_multi,
+ else => unreachable,
+ };
+ const payload_index = astgen.extra.items.len;
+ const zir_datas = astgen.instructions.items(.data);
+ zir_datas[switch_block].pl_node.payload_index = @intCast(u32, payload_index);
+ try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len +
+ scalar_cases_payload.items.len + multi_cases_payload.items.len);
+ const strat = rl.strategy(&block_scope);
+ switch (strat.tag) {
+ .break_operand => {
+ // Switch expressions return `true` for `nodeMayNeedMemoryLocation` thus
+ // this is always true.
+ assert(strat.elide_store_to_block_ptr_instructions);
+
+ // There will necessarily be a store_to_block_ptr for
+ // all prongs, except for prongs that ended with a noreturn instruction.
+ // Elide all the `store_to_block_ptr` instructions.
+
+ // The break instructions need to have their operands coerced if the
+ // switch's result location is a `ty`. In this case we overwrite the
+ // `store_to_block_ptr` instruction with an `as` instruction and repurpose
+ // it as the break operand.
+
+ var extra_index: usize = 0;
+ extra_index += 2;
+ extra_index += @boolToInt(multi_cases_len != 0);
+ if (special_prong != .none) special_prong: {
+ const body_len_index = extra_index;
+ const body_len = scalar_cases_payload.items[extra_index];
+ extra_index += 1;
+ if (body_len < 2) {
+ extra_index += body_len;
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
+ break :special_prong;
+ }
+ extra_index += body_len - 2;
+ const store_inst = scalar_cases_payload.items[extra_index];
+ if (zir_tags[store_inst] != .store_to_block_ptr) {
+ extra_index += 2;
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
+ break :special_prong;
+ }
+ assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
+ if (block_scope.rl_ty_inst != .none) {
+ extra_index += 1;
+ const break_inst = scalar_cases_payload.items[extra_index];
+ extra_index += 1;
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
+ zir_tags[store_inst] = .as;
+ zir_datas[store_inst].bin = .{
+ .lhs = block_scope.rl_ty_inst,
+ .rhs = zir_datas[break_inst].@"break".operand,
+ };
+ zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst);
+ } else {
+ scalar_cases_payload.items[body_len_index] -= 1;
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
+ extra_index += 1;
+ astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]);
+ extra_index += 1;
+ }
+ } else {
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
+ }
+ var scalar_i: u32 = 0;
+ while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
+ const start_index = extra_index;
+ extra_index += 1;
+ const body_len_index = extra_index;
+ const body_len = scalar_cases_payload.items[extra_index];
+ extra_index += 1;
+ if (body_len < 2) {
+ extra_index += body_len;
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
+ continue;
+ }
+ extra_index += body_len - 2;
+ const store_inst = scalar_cases_payload.items[extra_index];
+ if (zir_tags[store_inst] != .store_to_block_ptr) {
+ extra_index += 2;
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
+ continue;
+ }
+ if (block_scope.rl_ty_inst != .none) {
+ extra_index += 1;
+ const break_inst = scalar_cases_payload.items[extra_index];
+ extra_index += 1;
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
+ zir_tags[store_inst] = .as;
+ zir_datas[store_inst].bin = .{
+ .lhs = block_scope.rl_ty_inst,
+ .rhs = zir_datas[break_inst].@"break".operand,
+ };
+ zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst);
+ } else {
+ assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
+ scalar_cases_payload.items[body_len_index] -= 1;
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
+ extra_index += 1;
+ astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]);
+ extra_index += 1;
+ }
+ }
+ extra_index = 0;
+ var multi_i: u32 = 0;
+ while (multi_i < multi_cases_len) : (multi_i += 1) {
+ const start_index = extra_index;
+ const items_len = multi_cases_payload.items[extra_index];
+ extra_index += 1;
+ const ranges_len = multi_cases_payload.items[extra_index];
+ extra_index += 1;
+ const body_len_index = extra_index;
+ const body_len = multi_cases_payload.items[extra_index];
+ extra_index += 1;
+ extra_index += items_len;
+ extra_index += 2 * ranges_len;
+ if (body_len < 2) {
+ extra_index += body_len;
+ astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
+ continue;
+ }
+ extra_index += body_len - 2;
+ const store_inst = multi_cases_payload.items[extra_index];
+ if (zir_tags[store_inst] != .store_to_block_ptr) {
+ extra_index += 2;
+ astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
+ continue;
+ }
+ if (block_scope.rl_ty_inst != .none) {
+ extra_index += 1;
+ const break_inst = multi_cases_payload.items[extra_index];
+ extra_index += 1;
+ astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
+ zir_tags[store_inst] = .as;
+ zir_datas[store_inst].bin = .{
+ .lhs = block_scope.rl_ty_inst,
+ .rhs = zir_datas[break_inst].@"break".operand,
+ };
+ zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst);
+ } else {
+ assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
+ multi_cases_payload.items[body_len_index] -= 1;
+ astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
+ extra_index += 1;
+ astgen.extra.appendAssumeCapacity(multi_cases_payload.items[extra_index]);
+ extra_index += 1;
+ }
+ }
+
+ const block_ref = astgen.indexToRef(switch_block);
+ switch (rl) {
+ .ref => return block_ref,
+ else => return rvalue(parent_gz, scope, rl, block_ref, switch_node),
+ }
+ },
+ .break_void => {
+ assert(!strat.elide_store_to_block_ptr_instructions);
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items);
+ astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items);
+ // Modify all the terminating instruction tags to become `break` variants.
+ var extra_index: usize = payload_index;
+ extra_index += 2;
+ extra_index += @boolToInt(multi_cases_len != 0);
+ if (special_prong != .none) {
+ const body_len = astgen.extra.items[extra_index];
+ extra_index += 1;
+ const body = astgen.extra.items[extra_index..][0..body_len];
+ extra_index += body_len;
+ const last = body[body.len - 1];
+ if (zir_tags[last] == .@"break" and
+ zir_datas[last].@"break".block_inst == switch_block)
+ {
+ zir_datas[last].@"break".operand = .void_value;
+ }
+ }
+ var scalar_i: u32 = 0;
+ while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
+ extra_index += 1;
+ const body_len = astgen.extra.items[extra_index];
+ extra_index += 1;
+ const body = astgen.extra.items[extra_index..][0..body_len];
+ extra_index += body_len;
+ const last = body[body.len - 1];
+ if (zir_tags[last] == .@"break" and
+ zir_datas[last].@"break".block_inst == switch_block)
+ {
+ zir_datas[last].@"break".operand = .void_value;
+ }
+ }
+ var multi_i: u32 = 0;
+ while (multi_i < multi_cases_len) : (multi_i += 1) {
+ const items_len = astgen.extra.items[extra_index];
+ extra_index += 1;
+ const ranges_len = astgen.extra.items[extra_index];
+ extra_index += 1;
+ const body_len = astgen.extra.items[extra_index];
+ extra_index += 1;
+ extra_index += items_len;
+ extra_index += 2 * ranges_len;
+ const body = astgen.extra.items[extra_index..][0..body_len];
+ extra_index += body_len;
+ const last = body[body.len - 1];
+ if (zir_tags[last] == .@"break" and
+ zir_datas[last].@"break".block_inst == switch_block)
+ {
+ zir_datas[last].@"break".operand = .void_value;
+ }
+ }
+
+ return astgen.indexToRef(switch_block);
+ },
+ }
+}
+
+fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const node_datas = tree.nodes.items(.data);
+ const main_tokens = tree.nodes.items(.main_token);
+
+ const operand_node = node_datas[node].lhs;
+ const operand: zir.Inst.Ref = if (operand_node != 0) operand: {
+ const rl: ResultLoc = if (nodeMayNeedMemoryLocation(tree, operand_node)) .{
+ .ptr = try gz.addNode(.ret_ptr, node),
+ } else .{
+ .ty = try gz.addNode(.ret_type, node),
+ };
+ break :operand try expr(gz, scope, rl, operand_node);
+ } else .void_value;
+ _ = try gz.addUnNode(.ret_node, operand, node);
+ return zir.Inst.Ref.unreachable_value;
+}
+
+fn identifier(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ ident: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const mod = gz.astgen.mod;
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+
+ const ident_token = main_tokens[ident];
+ const ident_name = try mod.identifierTokenString(scope, ident_token);
+ if (mem.eql(u8, ident_name, "_")) {
+ return mod.failNode(scope, ident, "TODO implement '_' identifier", .{});
+ }
+
+ if (simple_types.get(ident_name)) |zir_const_ref| {
+ return rvalue(gz, scope, rl, zir_const_ref, ident);
+ }
+
+ if (ident_name.len >= 2) integer: {
+ const first_c = ident_name[0];
+ if (first_c == 'i' or first_c == 'u') {
+ const signedness: std.builtin.Signedness = switch (first_c == 'i') {
+ true => .signed,
+ false => .unsigned,
+ };
+ const bit_count = std.fmt.parseInt(u16, ident_name[1..], 10) catch |err| switch (err) {
+ error.Overflow => return mod.failNode(
+ scope,
+ ident,
+ "primitive integer type '{s}' exceeds maximum bit width of 65535",
+ .{ident_name},
+ ),
+ error.InvalidCharacter => break :integer,
+ };
+ const result = try gz.add(.{
+ .tag = .int_type,
+ .data = .{ .int_type = .{
+ .src_node = gz.astgen.decl.nodeIndexToRelative(ident),
+ .signedness = signedness,
+ .bit_count = bit_count,
+ } },
+ });
+ return rvalue(gz, scope, rl, result, ident);
+ }
+ }
+
+ // Local variables, including function parameters.
+ {
+ var s = scope;
+ while (true) switch (s.tag) {
+ .local_val => {
+ const local_val = s.cast(Scope.LocalVal).?;
+ if (mem.eql(u8, local_val.name, ident_name)) {
+ return rvalue(gz, scope, rl, local_val.inst, ident);
+ }
+ s = local_val.parent;
+ },
+ .local_ptr => {
+ const local_ptr = s.cast(Scope.LocalPtr).?;
+ if (mem.eql(u8, local_ptr.name, ident_name)) {
+ if (rl == .ref) return local_ptr.ptr;
+ const loaded = try gz.addUnNode(.load, local_ptr.ptr, ident);
+ return rvalue(gz, scope, rl, loaded, ident);
+ }
+ s = local_ptr.parent;
+ },
+ .gen_zir => s = s.cast(GenZir).?.parent,
+ else => break,
+ };
+ }
+
+ const gop = try gz.astgen.decl_map.getOrPut(mod.gpa, ident_name);
+ if (!gop.found_existing) {
+ const decl = mod.lookupDeclName(scope, ident_name) orelse
+ return mod.failNode(scope, ident, "use of undeclared identifier '{s}'", .{ident_name});
+ try gz.astgen.decls.append(mod.gpa, decl);
+ }
+ const decl_index = @intCast(u32, gop.index);
+ switch (rl) {
+ .ref => return gz.addDecl(.decl_ref, decl_index, ident),
+ else => return rvalue(gz, scope, rl, try gz.addDecl(.decl_val, decl_index, ident), ident),
+ }
+}
+
+fn stringLiteral(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const string_bytes = &gz.astgen.string_bytes;
+ const str_index = string_bytes.items.len;
+ const str_lit_token = main_tokens[node];
+ const token_bytes = tree.tokenSlice(str_lit_token);
+ try gz.astgen.mod.parseStrLit(scope, str_lit_token, string_bytes, token_bytes, 0);
+ const str_len = string_bytes.items.len - str_index;
+ const result = try gz.add(.{
+ .tag = .str,
+ .data = .{ .str = .{
+ .start = @intCast(u32, str_index),
+ .len = @intCast(u32, str_len),
+ } },
+ });
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn multilineStringLiteral(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const node_datas = tree.nodes.items(.data);
+ const main_tokens = tree.nodes.items(.main_token);
+
+ const start = node_datas[node].lhs;
+ const end = node_datas[node].rhs;
+
+ const gpa = gz.astgen.mod.gpa;
+ const string_bytes = &gz.astgen.string_bytes;
+ const str_index = string_bytes.items.len;
+
+ // First line: do not append a newline.
+ var tok_i = start;
+ {
+ const slice = tree.tokenSlice(tok_i);
+ const line_bytes = slice[2 .. slice.len - 1];
+ try string_bytes.appendSlice(gpa, line_bytes);
+ tok_i += 1;
+ }
+ // Following lines: each line prepends a newline.
+ while (tok_i <= end) : (tok_i += 1) {
+ const slice = tree.tokenSlice(tok_i);
+ const line_bytes = slice[2 .. slice.len - 1];
+ try string_bytes.ensureCapacity(gpa, string_bytes.items.len + line_bytes.len + 1);
+ string_bytes.appendAssumeCapacity('\n');
+ string_bytes.appendSliceAssumeCapacity(line_bytes);
+ }
+ const result = try gz.add(.{
+ .tag = .str,
+ .data = .{ .str = .{
+ .start = @intCast(u32, str_index),
+ .len = @intCast(u32, string_bytes.items.len - str_index),
+ } },
+ });
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn charLiteral(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref {
+ const mod = gz.astgen.mod;
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const main_token = main_tokens[node];
+ const slice = tree.tokenSlice(main_token);
+
+ var bad_index: usize = undefined;
+ const value = std.zig.parseCharLiteral(slice, &bad_index) catch |err| switch (err) {
+ error.InvalidCharacter => {
+ const bad_byte = slice[bad_index];
+ const token_starts = tree.tokens.items(.start);
+ const src_off = @intCast(u32, token_starts[main_token] + bad_index);
+ return mod.failOff(scope, src_off, "invalid character: '{c}'\n", .{bad_byte});
+ },
+ };
+ const result = try gz.addInt(value);
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn integerLiteral(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const int_token = main_tokens[node];
+ const prefixed_bytes = tree.tokenSlice(int_token);
+ if (std.fmt.parseInt(u64, prefixed_bytes, 0)) |small_int| {
+ const result: zir.Inst.Ref = switch (small_int) {
+ 0 => .zero,
+ 1 => .one,
+ else => try gz.addInt(small_int),
+ };
+ return rvalue(gz, scope, rl, result, node);
+ } else |err| {
+ return gz.astgen.mod.failNode(scope, node, "TODO implement int literals that don't fit in a u64", .{});
+ }
+}
+
+fn floatLiteral(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const arena = gz.astgen.arena;
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+
+ const main_token = main_tokens[node];
+ const bytes = tree.tokenSlice(main_token);
+ if (bytes.len > 2 and bytes[1] == 'x') {
+ assert(bytes[0] == '0'); // validated by tokenizer
+ return gz.astgen.mod.failTok(scope, main_token, "TODO implement hex floats", .{});
+ }
+ const float_number = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) {
+ error.InvalidCharacter => unreachable, // validated by tokenizer
+ };
+ const typed_value = try arena.create(TypedValue);
+ typed_value.* = .{
+ .ty = Type.initTag(.comptime_float),
+ .val = try Value.Tag.float_128.create(arena, float_number),
+ };
+ const result = try gz.addConst(typed_value);
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn asmExpr(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ full: ast.full.Asm,
+) InnerError!zir.Inst.Ref {
+ const mod = gz.astgen.mod;
+ const arena = gz.astgen.arena;
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const node_datas = tree.nodes.items(.data);
+
+ const asm_source = try expr(gz, scope, .{ .ty = .const_slice_u8_type }, full.ast.template);
+
+ if (full.outputs.len != 0) {
+ // when implementing this be sure to add test coverage for the asm return type
+ // not resolving into a type (the node_offset_asm_ret_ty field of LazySrcLoc)
+ return mod.failTok(scope, full.ast.asm_token, "TODO implement asm with an output", .{});
+ }
+
+ const constraints = try arena.alloc(u32, full.inputs.len);
+ const args = try arena.alloc(zir.Inst.Ref, full.inputs.len);
+
+ for (full.inputs) |input, i| {
+ const constraint_token = main_tokens[input] + 2;
+ const string_bytes = &gz.astgen.string_bytes;
+ constraints[i] = @intCast(u32, string_bytes.items.len);
+ const token_bytes = tree.tokenSlice(constraint_token);
+ try mod.parseStrLit(scope, constraint_token, string_bytes, token_bytes, 0);
+ try string_bytes.append(mod.gpa, 0);
+
+ args[i] = try expr(gz, scope, .{ .ty = .usize_type }, node_datas[input].lhs);
+ }
+
+ const tag: zir.Inst.Tag = if (full.volatile_token != null) .asm_volatile else .@"asm";
+ const result = try gz.addPlNode(tag, node, zir.Inst.Asm{
+ .asm_source = asm_source,
+ .return_type = .void_type,
+ .output = .none,
+ .args_len = @intCast(u32, full.inputs.len),
+ .clobbers_len = 0, // TODO implement asm clobbers
+ });
+
+ try gz.astgen.extra.ensureCapacity(mod.gpa, gz.astgen.extra.items.len +
+ args.len + constraints.len);
+ gz.astgen.appendRefsAssumeCapacity(args);
+ gz.astgen.extra.appendSliceAssumeCapacity(constraints);
+
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn as(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ lhs: ast.Node.Index,
+ rhs: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const dest_type = try typeExpr(gz, scope, lhs);
+ switch (rl) {
+ .none, .discard, .ref, .ty => {
+ const result = try expr(gz, scope, .{ .ty = dest_type }, rhs);
+ return rvalue(gz, scope, rl, result, node);
+ },
+
+ .ptr => |result_ptr| {
+ return asRlPtr(gz, scope, rl, result_ptr, rhs, dest_type);
+ },
+ .block_ptr => |block_scope| {
+ return asRlPtr(gz, scope, rl, block_scope.rl_ptr, rhs, dest_type);
+ },
+
+ .inferred_ptr => |result_alloc| {
+ // TODO here we should be able to resolve the inference; we now have a type for the result.
+ return gz.astgen.mod.failNode(scope, node, "TODO implement @as with inferred-type result location pointer", .{});
+ },
+ }
+}
+
+fn asRlPtr(
+ parent_gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ result_ptr: zir.Inst.Ref,
+ operand_node: ast.Node.Index,
+ dest_type: zir.Inst.Ref,
+) InnerError!zir.Inst.Ref {
+ // Detect whether this expr() call goes into rvalue() to store the result into the
+ // result location. If it does, elide the coerce_result_ptr instruction
+ // as well as the store instruction, instead passing the result as an rvalue.
+ const astgen = parent_gz.astgen;
+
+ var as_scope: GenZir = .{
+ .parent = scope,
+ .astgen = astgen,
+ .force_comptime = parent_gz.force_comptime,
+ .instructions = .{},
+ };
+ defer as_scope.instructions.deinit(astgen.mod.gpa);
+
+ as_scope.rl_ptr = try as_scope.addBin(.coerce_result_ptr, dest_type, result_ptr);
+ const result = try expr(&as_scope, &as_scope.base, .{ .block_ptr = &as_scope }, operand_node);
+ const parent_zir = &parent_gz.instructions;
+ if (as_scope.rvalue_rl_count == 1) {
+ // Busted! This expression didn't actually need a pointer.
+ const zir_tags = astgen.instructions.items(.tag);
+ const zir_datas = astgen.instructions.items(.data);
+ const expected_len = parent_zir.items.len + as_scope.instructions.items.len - 2;
+ try parent_zir.ensureCapacity(astgen.mod.gpa, expected_len);
+ for (as_scope.instructions.items) |src_inst| {
+ if (astgen.indexToRef(src_inst) == as_scope.rl_ptr) continue;
+ if (zir_tags[src_inst] == .store_to_block_ptr) {
+ if (zir_datas[src_inst].bin.lhs == as_scope.rl_ptr) continue;
+ }
+ parent_zir.appendAssumeCapacity(src_inst);
+ }
+ assert(parent_zir.items.len == expected_len);
+ const casted_result = try parent_gz.addBin(.as, dest_type, result);
+ return rvalue(parent_gz, scope, rl, casted_result, operand_node);
+ } else {
+ try parent_zir.appendSlice(astgen.mod.gpa, as_scope.instructions.items);
+ return result;
+ }
+}
+
+fn bitCast(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ lhs: ast.Node.Index,
+ rhs: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const mod = gz.astgen.mod;
+ const dest_type = try typeExpr(gz, scope, lhs);
+ switch (rl) {
+ .none, .discard, .ty => {
+ const operand = try expr(gz, scope, .none, rhs);
+ const result = try gz.addPlNode(.bitcast, node, zir.Inst.Bin{
+ .lhs = dest_type,
+ .rhs = operand,
+ });
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .ref => unreachable, // `@bitCast` is not allowed as an r-value.
+ .ptr => |result_ptr| {
+ const casted_result_ptr = try gz.addUnNode(.bitcast_result_ptr, result_ptr, node);
+ return expr(gz, scope, .{ .ptr = casted_result_ptr }, rhs);
+ },
+ .block_ptr => |block_ptr| {
+ return mod.failNode(scope, node, "TODO implement @bitCast with result location inferred peer types", .{});
+ },
+ .inferred_ptr => |result_alloc| {
+ // TODO here we should be able to resolve the inference; we now have a type for the result.
+ return mod.failNode(scope, node, "TODO implement @bitCast with inferred-type result location pointer", .{});
+ },
+ }
+}
+
+fn typeOf(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ params: []const ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ if (params.len < 1) {
+ return gz.astgen.mod.failNode(scope, node, "expected at least 1 argument, found 0", .{});
+ }
+ if (params.len == 1) {
+ const result = try gz.addUnNode(.typeof, try expr(gz, scope, .none, params[0]), node);
+ return rvalue(gz, scope, rl, result, node);
+ }
+ const arena = gz.astgen.arena;
+ var items = try arena.alloc(zir.Inst.Ref, params.len);
+ for (params) |param, param_i| {
+ items[param_i] = try expr(gz, scope, .none, param);
+ }
+
+ const result = try gz.addPlNode(.typeof_peer, node, zir.Inst.MultiOp{
+ .operands_len = @intCast(u32, params.len),
+ });
+ try gz.astgen.appendRefs(items);
+
+ return rvalue(gz, scope, rl, result, node);
+}
+
+fn builtinCall(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ params: []const ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ const mod = gz.astgen.mod;
+ const tree = gz.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+
+ const builtin_token = main_tokens[node];
+ const builtin_name = tree.tokenSlice(builtin_token);
+
+ // We handle the different builtins manually because they have different semantics depending
+ // on the function. For example, `@as` and others participate in result location semantics,
+ // and `@cImport` creates a special scope that collects a .c source code text buffer.
+ // Also, some builtins have a variable number of parameters.
+
+ const info = BuiltinFn.list.get(builtin_name) orelse {
+ return mod.failNode(scope, node, "invalid builtin function: '{s}'", .{
+ builtin_name,
+ });
+ };
+ if (info.param_count) |expected| {
+ if (expected != params.len) {
+ const s = if (expected == 1) "" else "s";
+ return mod.failNode(scope, node, "expected {d} parameter{s}, found {d}", .{
+ expected, s, params.len,
+ });
+ }
+ }
+
+ switch (info.tag) {
+ .ptr_to_int => {
+ const operand = try expr(gz, scope, .none, params[0]);
+ const result = try gz.addUnNode(.ptrtoint, operand, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .float_cast => {
+ const dest_type = try typeExpr(gz, scope, params[0]);
+ const rhs = try expr(gz, scope, .none, params[1]);
+ const result = try gz.addPlNode(.floatcast, node, zir.Inst.Bin{
+ .lhs = dest_type,
+ .rhs = rhs,
+ });
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .int_cast => {
+ const dest_type = try typeExpr(gz, scope, params[0]);
+ const rhs = try expr(gz, scope, .none, params[1]);
+ const result = try gz.addPlNode(.intcast, node, zir.Inst.Bin{
+ .lhs = dest_type,
+ .rhs = rhs,
+ });
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .breakpoint => {
+ const result = try gz.add(.{
+ .tag = .breakpoint,
+ .data = .{ .node = gz.astgen.decl.nodeIndexToRelative(node) },
+ });
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .import => {
+ const target = try expr(gz, scope, .none, params[0]);
+ const result = try gz.addUnNode(.import, target, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .error_to_int => {
+ const target = try expr(gz, scope, .none, params[0]);
+ const result = try gz.addUnNode(.error_to_int, target, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .int_to_error => {
+ const target = try expr(gz, scope, .{ .ty = .u16_type }, params[0]);
+ const result = try gz.addUnNode(.int_to_error, target, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .compile_error => {
+ const target = try expr(gz, scope, .none, params[0]);
+ const result = try gz.addUnNode(.compile_error, target, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .set_eval_branch_quota => {
+ const quota = try expr(gz, scope, .{ .ty = .u32_type }, params[0]);
+ const result = try gz.addUnNode(.set_eval_branch_quota, quota, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .compile_log => {
+ const arg_refs = try mod.gpa.alloc(zir.Inst.Ref, params.len);
+ defer mod.gpa.free(arg_refs);
+
+ for (params) |param, i| arg_refs[i] = try expr(gz, scope, .none, param);
+
+ const result = try gz.addPlNode(.compile_log, node, zir.Inst.MultiOp{
+ .operands_len = @intCast(u32, params.len),
+ });
+ try gz.astgen.appendRefs(arg_refs);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .field => {
+ const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]);
+ if (rl == .ref) {
+ return try gz.addPlNode(.field_ptr_named, node, zir.Inst.FieldNamed{
+ .lhs = try expr(gz, scope, .ref, params[0]),
+ .field_name = field_name,
+ });
+ }
+ const result = try gz.addPlNode(.field_val_named, node, zir.Inst.FieldNamed{
+ .lhs = try expr(gz, scope, .none, params[0]),
+ .field_name = field_name,
+ });
+ return rvalue(gz, scope, rl, result, node);
+ },
+ .as => return as(gz, scope, rl, node, params[0], params[1]),
+ .bit_cast => return bitCast(gz, scope, rl, node, params[0], params[1]),
+ .TypeOf => return typeOf(gz, scope, rl, node, params),
+
+ .add_with_overflow,
+ .align_cast,
+ .align_of,
+ .atomic_load,
+ .atomic_rmw,
+ .atomic_store,
+ .bit_offset_of,
+ .bool_to_int,
+ .bit_size_of,
+ .mul_add,
+ .byte_swap,
+ .bit_reverse,
+ .byte_offset_of,
+ .call,
+ .c_define,
+ .c_import,
+ .c_include,
+ .clz,
+ .cmpxchg_strong,
+ .cmpxchg_weak,
+ .ctz,
+ .c_undef,
+ .div_exact,
+ .div_floor,
+ .div_trunc,
+ .embed_file,
+ .enum_to_int,
+ .error_name,
+ .error_return_trace,
+ .err_set_cast,
+ .@"export",
+ .fence,
+ .field_parent_ptr,
+ .float_to_int,
+ .has_decl,
+ .has_field,
+ .int_to_enum,
+ .int_to_float,
+ .int_to_ptr,
+ .memcpy,
+ .memset,
+ .wasm_memory_size,
+ .wasm_memory_grow,
+ .mod,
+ .mul_with_overflow,
+ .panic,
+ .pop_count,
+ .ptr_cast,
+ .rem,
+ .return_address,
+ .set_align_stack,
+ .set_cold,
+ .set_float_mode,
+ .set_runtime_safety,
+ .shl_exact,
+ .shl_with_overflow,
+ .shr_exact,
+ .shuffle,
+ .size_of,
+ .splat,
+ .reduce,
+ .src,
+ .sqrt,
+ .sin,
+ .cos,
+ .exp,
+ .exp2,
+ .log,
+ .log2,
+ .log10,
+ .fabs,
+ .floor,
+ .ceil,
+ .trunc,
+ .round,
+ .sub_with_overflow,
+ .tag_name,
+ .This,
+ .truncate,
+ .Type,
+ .type_info,
+ .type_name,
+ .union_init,
+ => return mod.failNode(scope, node, "TODO: implement builtin function {s}", .{
+ builtin_name,
+ }),
+
+ .async_call,
+ .frame,
+ .Frame,
+ .frame_address,
+ .frame_size,
+ => return mod.failNode(scope, node, "async and related features are not yet supported", .{}),
+ }
+}
+
+fn callExpr(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: ast.Node.Index,
+ call: ast.full.Call,
+) InnerError!zir.Inst.Ref {
+ const mod = gz.astgen.mod;
+ if (call.async_token) |async_token| {
+ return mod.failTok(scope, async_token, "async and related features are not yet supported", .{});
+ }
+ const lhs = try expr(gz, scope, .none, call.ast.fn_expr);
+
+ const args = try mod.gpa.alloc(zir.Inst.Ref, call.ast.params.len);
+ defer mod.gpa.free(args);
+
+ for (call.ast.params) |param_node, i| {
+ const param_type = try gz.add(.{
+ .tag = .param_type,
+ .data = .{ .param_type = .{
+ .callee = lhs,
+ .param_index = @intCast(u32, i),
+ } },
+ });
+ args[i] = try expr(gz, scope, .{ .ty = param_type }, param_node);
+ }
+
+ const modifier: std.builtin.CallOptions.Modifier = switch (call.async_token != null) {
+ true => .async_kw,
+ false => .auto,
+ };
+ const result: zir.Inst.Ref = res: {
+ const tag: zir.Inst.Tag = switch (modifier) {
+ .auto => switch (args.len == 0) {
+ true => break :res try gz.addUnNode(.call_none, lhs, node),
+ false => .call,
+ },
+ .async_kw => return mod.failNode(scope, node, "async and related features are not yet supported", .{}),
+ .never_tail => unreachable,
+ .never_inline => unreachable,
+ .no_async => return mod.failNode(scope, node, "async and related features are not yet supported", .{}),
+ .always_tail => unreachable,
+ .always_inline => unreachable,
+ .compile_time => .call_compile_time,
+ };
+ break :res try gz.addCall(tag, lhs, args, node);
+ };
+ return rvalue(gz, scope, rl, result, node); // TODO function call with result location
+}
+
+pub const simple_types = std.ComptimeStringMap(zir.Inst.Ref, .{
+ .{ "u8", .u8_type },
+ .{ "i8", .i8_type },
+ .{ "u16", .u16_type },
+ .{ "i16", .i16_type },
+ .{ "u32", .u32_type },
+ .{ "i32", .i32_type },
+ .{ "u64", .u64_type },
+ .{ "i64", .i64_type },
+ .{ "usize", .usize_type },
+ .{ "isize", .isize_type },
+ .{ "c_short", .c_short_type },
+ .{ "c_ushort", .c_ushort_type },
+ .{ "c_int", .c_int_type },
+ .{ "c_uint", .c_uint_type },
+ .{ "c_long", .c_long_type },
+ .{ "c_ulong", .c_ulong_type },
+ .{ "c_longlong", .c_longlong_type },
+ .{ "c_ulonglong", .c_ulonglong_type },
+ .{ "c_longdouble", .c_longdouble_type },
+ .{ "f16", .f16_type },
+ .{ "f32", .f32_type },
+ .{ "f64", .f64_type },
+ .{ "f128", .f128_type },
+ .{ "c_void", .c_void_type },
+ .{ "bool", .bool_type },
+ .{ "void", .void_type },
+ .{ "type", .type_type },
+ .{ "anyerror", .anyerror_type },
+ .{ "comptime_int", .comptime_int_type },
+ .{ "comptime_float", .comptime_float_type },
+ .{ "noreturn", .noreturn_type },
+ .{ "null", .null_type },
+ .{ "undefined", .undefined_type },
+ .{ "undefined", .undef },
+ .{ "null", .null_value },
+ .{ "true", .bool_true },
+ .{ "false", .bool_false },
+});
+
+fn nodeMayNeedMemoryLocation(tree: *const ast.Tree, start_node: ast.Node.Index) bool {
+ const node_tags = tree.nodes.items(.tag);
+ const node_datas = tree.nodes.items(.data);
+ const main_tokens = tree.nodes.items(.main_token);
+ const token_tags = tree.tokens.items(.tag);
+
+ var node = start_node;
+ while (true) {
+ switch (node_tags[node]) {
+ .root,
+ .@"usingnamespace",
+ .test_decl,
+ .switch_case,
+ .switch_case_one,
+ .container_field_init,
+ .container_field_align,
+ .container_field,
+ .asm_output,
+ .asm_input,
+ => unreachable,
+
+ .@"return",
+ .@"break",
+ .@"continue",
+ .bit_not,
+ .bool_not,
+ .global_var_decl,
+ .local_var_decl,
+ .simple_var_decl,
+ .aligned_var_decl,
+ .@"defer",
+ .@"errdefer",
+ .address_of,
+ .optional_type,
+ .negation,
+ .negation_wrap,
+ .@"resume",
+ .array_type,
+ .array_type_sentinel,
+ .ptr_type_aligned,
+ .ptr_type_sentinel,
+ .ptr_type,
+ .ptr_type_bit_range,
+ .@"suspend",
+ .@"anytype",
+ .fn_proto_simple,
+ .fn_proto_multi,
+ .fn_proto_one,
+ .fn_proto,
+ .fn_decl,
+ .anyframe_type,
+ .anyframe_literal,
+ .integer_literal,
+ .float_literal,
+ .enum_literal,
+ .string_literal,
+ .multiline_string_literal,
+ .char_literal,
+ .true_literal,
+ .false_literal,
+ .null_literal,
+ .undefined_literal,
+ .unreachable_literal,
+ .identifier,
+ .error_set_decl,
+ .container_decl,
+ .container_decl_trailing,
+ .container_decl_two,
+ .container_decl_two_trailing,
+ .container_decl_arg,
+ .container_decl_arg_trailing,
+ .tagged_union,
+ .tagged_union_trailing,
+ .tagged_union_two,
+ .tagged_union_two_trailing,
+ .tagged_union_enum_tag,
+ .tagged_union_enum_tag_trailing,
+ .@"asm",
+ .asm_simple,
+ .add,
+ .add_wrap,
+ .array_cat,
+ .array_mult,
+ .assign,
+ .assign_bit_and,
+ .assign_bit_or,
+ .assign_bit_shift_left,
+ .assign_bit_shift_right,
+ .assign_bit_xor,
+ .assign_div,
+ .assign_sub,
+ .assign_sub_wrap,
+ .assign_mod,
+ .assign_add,
+ .assign_add_wrap,
+ .assign_mul,
+ .assign_mul_wrap,
+ .bang_equal,
+ .bit_and,
+ .bit_or,
+ .bit_shift_left,
+ .bit_shift_right,
+ .bit_xor,
+ .bool_and,
+ .bool_or,
+ .div,
+ .equal_equal,
+ .error_union,
+ .greater_or_equal,
+ .greater_than,
+ .less_or_equal,
+ .less_than,
+ .merge_error_sets,
+ .mod,
+ .mul,
+ .mul_wrap,
+ .switch_range,
+ .field_access,
+ .sub,
+ .sub_wrap,
+ .slice,
+ .slice_open,
+ .slice_sentinel,
+ .deref,
+ .array_access,
+ .error_value,
+ .while_simple, // This variant cannot have an else expression.
+ .while_cont, // This variant cannot have an else expression.
+ .for_simple, // This variant cannot have an else expression.
+ .if_simple, // This variant cannot have an else expression.
+ => return false,
+
+ // Forward the question to the LHS sub-expression.
+ .grouped_expression,
+ .@"try",
+ .@"await",
+ .@"comptime",
+ .@"nosuspend",
+ .unwrap_optional,
+ => node = node_datas[node].lhs,
+
+ // Forward the question to the RHS sub-expression.
+ .@"catch",
+ .@"orelse",
+ => node = node_datas[node].rhs,
+
+ // True because these are exactly the expressions we need memory locations for.
+ .array_init_one,
+ .array_init_one_comma,
+ .array_init_dot_two,
+ .array_init_dot_two_comma,
+ .array_init_dot,
+ .array_init_dot_comma,
+ .array_init,
+ .array_init_comma,
+ .struct_init_one,
+ .struct_init_one_comma,
+ .struct_init_dot_two,
+ .struct_init_dot_two_comma,
+ .struct_init_dot,
+ .struct_init_dot_comma,
+ .struct_init,
+ .struct_init_comma,
+ => return true,
+
+ // True because depending on comptime conditions, sub-expressions
+ // may be the kind that need memory locations.
+ .@"while", // This variant always has an else expression.
+ .@"if", // This variant always has an else expression.
+ .@"for", // This variant always has an else expression.
+ .@"switch",
+ .switch_comma,
+ .call_one,
+ .call_one_comma,
+ .async_call_one,
+ .async_call_one_comma,
+ .call,
+ .call_comma,
+ .async_call,
+ .async_call_comma,
+ => return true,
+
+ .block_two,
+ .block_two_semicolon,
+ .block,
+ .block_semicolon,
+ => {
+ const lbrace = main_tokens[node];
+ if (token_tags[lbrace - 1] == .colon) {
+ // Labeled blocks may need a memory location to forward
+ // to their break statements.
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ .builtin_call,
+ .builtin_call_comma,
+ .builtin_call_two,
+ .builtin_call_two_comma,
+ => {
+ const builtin_token = main_tokens[node];
+ const builtin_name = tree.tokenSlice(builtin_token);
+ // If the builtin is an invalid name, we don't cause an error here; instead
+ // let it pass, and the error will be "invalid builtin function" later.
+ const builtin_info = BuiltinFn.list.get(builtin_name) orelse return false;
+ return builtin_info.needs_mem_loc;
+ },
+ }
+ }
+}
+
+/// Applies `rl` semantics to `inst`. Expressions which do not do their own handling of
+/// result locations must call this function on their result.
+/// As an example, if the `ResultLoc` is `ptr`, it will write the result to the pointer.
+/// If the `ResultLoc` is `ty`, it will coerce the result to the type.
+fn rvalue(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ result: zir.Inst.Ref,
+ src_node: ast.Node.Index,
+) InnerError!zir.Inst.Ref {
+ switch (rl) {
+ .none => return result,
+ .discard => {
+ // Emit a compile error for discarding error values.
+ _ = try gz.addUnNode(.ensure_result_non_error, result, src_node);
+ return result;
+ },
+ .ref => {
+ // We need a pointer but we have a value.
+ const tree = gz.tree();
+ const src_token = tree.firstToken(src_node);
+ return gz.addUnTok(.ref, result, src_token);
+ },
+ .ty => |ty_inst| {
+ // Quickly eliminate some common, unnecessary type coercion.
+ const as_ty = @as(u64, @enumToInt(zir.Inst.Ref.type_type)) << 32;
+ const as_comptime_int = @as(u64, @enumToInt(zir.Inst.Ref.comptime_int_type)) << 32;
+ const as_bool = @as(u64, @enumToInt(zir.Inst.Ref.bool_type)) << 32;
+ const as_usize = @as(u64, @enumToInt(zir.Inst.Ref.usize_type)) << 32;
+ const as_void = @as(u64, @enumToInt(zir.Inst.Ref.void_type)) << 32;
+ switch ((@as(u64, @enumToInt(ty_inst)) << 32) | @as(u64, @enumToInt(result))) {
+ as_ty | @enumToInt(zir.Inst.Ref.u8_type),
+ as_ty | @enumToInt(zir.Inst.Ref.i8_type),
+ as_ty | @enumToInt(zir.Inst.Ref.u16_type),
+ as_ty | @enumToInt(zir.Inst.Ref.i16_type),
+ as_ty | @enumToInt(zir.Inst.Ref.u32_type),
+ as_ty | @enumToInt(zir.Inst.Ref.i32_type),
+ as_ty | @enumToInt(zir.Inst.Ref.u64_type),
+ as_ty | @enumToInt(zir.Inst.Ref.i64_type),
+ as_ty | @enumToInt(zir.Inst.Ref.usize_type),
+ as_ty | @enumToInt(zir.Inst.Ref.isize_type),
+ as_ty | @enumToInt(zir.Inst.Ref.c_short_type),
+ as_ty | @enumToInt(zir.Inst.Ref.c_ushort_type),
+ as_ty | @enumToInt(zir.Inst.Ref.c_int_type),
+ as_ty | @enumToInt(zir.Inst.Ref.c_uint_type),
+ as_ty | @enumToInt(zir.Inst.Ref.c_long_type),
+ as_ty | @enumToInt(zir.Inst.Ref.c_ulong_type),
+ as_ty | @enumToInt(zir.Inst.Ref.c_longlong_type),
+ as_ty | @enumToInt(zir.Inst.Ref.c_ulonglong_type),
+ as_ty | @enumToInt(zir.Inst.Ref.c_longdouble_type),
+ as_ty | @enumToInt(zir.Inst.Ref.f16_type),
+ as_ty | @enumToInt(zir.Inst.Ref.f32_type),
+ as_ty | @enumToInt(zir.Inst.Ref.f64_type),
+ as_ty | @enumToInt(zir.Inst.Ref.f128_type),
+ as_ty | @enumToInt(zir.Inst.Ref.c_void_type),
+ as_ty | @enumToInt(zir.Inst.Ref.bool_type),
+ as_ty | @enumToInt(zir.Inst.Ref.void_type),
+ as_ty | @enumToInt(zir.Inst.Ref.type_type),
+ as_ty | @enumToInt(zir.Inst.Ref.anyerror_type),
+ as_ty | @enumToInt(zir.Inst.Ref.comptime_int_type),
+ as_ty | @enumToInt(zir.Inst.Ref.comptime_float_type),
+ as_ty | @enumToInt(zir.Inst.Ref.noreturn_type),
+ as_ty | @enumToInt(zir.Inst.Ref.null_type),
+ as_ty | @enumToInt(zir.Inst.Ref.undefined_type),
+ as_ty | @enumToInt(zir.Inst.Ref.fn_noreturn_no_args_type),
+ as_ty | @enumToInt(zir.Inst.Ref.fn_void_no_args_type),
+ as_ty | @enumToInt(zir.Inst.Ref.fn_naked_noreturn_no_args_type),
+ as_ty | @enumToInt(zir.Inst.Ref.fn_ccc_void_no_args_type),
+ as_ty | @enumToInt(zir.Inst.Ref.single_const_pointer_to_comptime_int_type),
+ as_ty | @enumToInt(zir.Inst.Ref.const_slice_u8_type),
+ as_ty | @enumToInt(zir.Inst.Ref.enum_literal_type),
+ as_comptime_int | @enumToInt(zir.Inst.Ref.zero),
+ as_comptime_int | @enumToInt(zir.Inst.Ref.one),
+ as_bool | @enumToInt(zir.Inst.Ref.bool_true),
+ as_bool | @enumToInt(zir.Inst.Ref.bool_false),
+ as_usize | @enumToInt(zir.Inst.Ref.zero_usize),
+ as_usize | @enumToInt(zir.Inst.Ref.one_usize),
+ as_void | @enumToInt(zir.Inst.Ref.void_value),
+ => return result, // type of result is already correct
+
+ // Need an explicit type coercion instruction.
+ else => return gz.addPlNode(.as_node, src_node, zir.Inst.As{
+ .dest_type = ty_inst,
+ .operand = result,
+ }),
+ }
+ },
+ .ptr => |ptr_inst| {
+ _ = try gz.addPlNode(.store_node, src_node, zir.Inst.Bin{
+ .lhs = ptr_inst,
+ .rhs = result,
+ });
+ return result;
+ },
+ .inferred_ptr => |alloc| {
+ _ = try gz.addBin(.store_to_inferred_ptr, alloc, result);
+ return result;
+ },
+ .block_ptr => |block_scope| {
+ block_scope.rvalue_rl_count += 1;
+ _ = try gz.addBin(.store_to_block_ptr, block_scope.rl_ptr, result);
+ return result;
+ },
+ }
+}
diff --git a/src/Compilation.zig b/src/Compilation.zig
@@ -259,7 +259,7 @@ pub const CObject = struct {
/// To support incremental compilation, errors are stored in various places
/// so that they can be created and destroyed appropriately. This structure
/// is used to collect all the errors from the various places into one
-/// convenient place for API users to consume. It is allocated into 1 heap
+/// convenient place for API users to consume. It is allocated into 1 arena
/// and freed all at once.
pub const AllErrors = struct {
arena: std.heap.ArenaAllocator.State,
@@ -267,11 +267,11 @@ pub const AllErrors = struct {
pub const Message = union(enum) {
src: struct {
- src_path: []const u8,
- line: usize,
- column: usize,
- byte_offset: usize,
msg: []const u8,
+ src_path: []const u8,
+ line: u32,
+ column: u32,
+ byte_offset: u32,
notes: []Message = &.{},
},
plain: struct {
@@ -316,29 +316,31 @@ pub const AllErrors = struct {
const notes = try arena.allocator.alloc(Message, module_err_msg.notes.len);
for (notes) |*note, i| {
const module_note = module_err_msg.notes[i];
- const source = try module_note.src_loc.file_scope.getSource(module);
- const loc = std.zig.findLineColumn(source, module_note.src_loc.byte_offset);
- const sub_file_path = module_note.src_loc.file_scope.sub_file_path;
+ const source = try module_note.src_loc.fileScope().getSource(module);
+ const byte_offset = try module_note.src_loc.byteOffset();
+ const loc = std.zig.findLineColumn(source, byte_offset);
+ const sub_file_path = module_note.src_loc.fileScope().sub_file_path;
note.* = .{
.src = .{
.src_path = try arena.allocator.dupe(u8, sub_file_path),
.msg = try arena.allocator.dupe(u8, module_note.msg),
- .byte_offset = module_note.src_loc.byte_offset,
- .line = loc.line,
- .column = loc.column,
+ .byte_offset = byte_offset,
+ .line = @intCast(u32, loc.line),
+ .column = @intCast(u32, loc.column),
},
};
}
- const source = try module_err_msg.src_loc.file_scope.getSource(module);
- const loc = std.zig.findLineColumn(source, module_err_msg.src_loc.byte_offset);
- const sub_file_path = module_err_msg.src_loc.file_scope.sub_file_path;
+ const source = try module_err_msg.src_loc.fileScope().getSource(module);
+ const byte_offset = try module_err_msg.src_loc.byteOffset();
+ const loc = std.zig.findLineColumn(source, byte_offset);
+ const sub_file_path = module_err_msg.src_loc.fileScope().sub_file_path;
try errors.append(.{
.src = .{
.src_path = try arena.allocator.dupe(u8, sub_file_path),
.msg = try arena.allocator.dupe(u8, module_err_msg.msg),
- .byte_offset = module_err_msg.src_loc.byte_offset,
- .line = loc.line,
- .column = loc.column,
+ .byte_offset = byte_offset,
+ .line = @intCast(u32, loc.line),
+ .column = @intCast(u32, loc.column),
.notes = notes,
},
});
@@ -939,6 +941,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
};
const module = try arena.create(Module);
+ errdefer module.deinit();
module.* = .{
.gpa = gpa,
.comp = comp,
@@ -946,7 +949,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
.root_scope = root_scope,
.zig_cache_artifact_directory = zig_cache_artifact_directory,
.emit_h = options.emit_h,
+ .error_name_list = try std.ArrayListUnmanaged([]const u8).initCapacity(gpa, 1),
};
+ module.error_name_list.appendAssumeCapacity("(no error)");
break :blk module;
} else blk: {
if (options.emit_h != null) return error.NoZigModuleForCHeader;
diff --git a/src/Module.zig b/src/Module.zig
@@ -1,31 +1,32 @@
-const Module = @This();
+//! Compilation of all Zig source code is represented by one `Module`.
+//! Each `Compilation` has exactly one or zero `Module`, depending on whether
+//! there is or is not any zig source code, respectively.
+
const std = @import("std");
-const Compilation = @import("Compilation.zig");
const mem = std.mem;
const Allocator = std.mem.Allocator;
const ArrayListUnmanaged = std.ArrayListUnmanaged;
-const Value = @import("value.zig").Value;
-const Type = @import("type.zig").Type;
-const TypedValue = @import("TypedValue.zig");
const assert = std.debug.assert;
const log = std.log.scoped(.module);
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const Target = std.Target;
+const ast = std.zig.ast;
+
+const Module = @This();
+const Compilation = @import("Compilation.zig");
+const Value = @import("value.zig").Value;
+const Type = @import("type.zig").Type;
+const TypedValue = @import("TypedValue.zig");
const Package = @import("Package.zig");
const link = @import("link.zig");
const ir = @import("ir.zig");
const zir = @import("zir.zig");
-const Inst = ir.Inst;
-const Body = ir.Body;
-const ast = std.zig.ast;
const trace = @import("tracy.zig").trace;
-const astgen = @import("astgen.zig");
-const zir_sema = @import("zir_sema.zig");
+const AstGen = @import("AstGen.zig");
+const Sema = @import("Sema.zig");
const target_util = @import("target.zig");
-const default_eval_branch_quota = 1000;
-
/// General-purpose allocator. Used for both temporary and long-term storage.
gpa: *Allocator,
comp: *Compilation,
@@ -77,7 +78,12 @@ next_anon_name_index: usize = 0,
deletion_set: ArrayListUnmanaged(*Decl) = .{},
/// Error tags and their values, tag names are duped with mod.gpa.
-global_error_set: std.StringHashMapUnmanaged(u16) = .{},
+/// Corresponds with `error_name_list`.
+global_error_set: std.StringHashMapUnmanaged(ErrorInt) = .{},
+
+/// ErrorInt -> []const u8 for fast lookups for @intToError at comptime
+/// Corresponds with `global_error_set`.
+error_name_list: ArrayListUnmanaged([]const u8) = .{},
/// Keys are fully qualified paths
import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{},
@@ -102,12 +108,13 @@ stage1_flags: packed struct {
emit_h: ?Compilation.EmitLoc,
-compile_log_text: std.ArrayListUnmanaged(u8) = .{},
+compile_log_text: ArrayListUnmanaged(u8) = .{},
+
+pub const ErrorInt = u32;
pub const Export = struct {
options: std.builtin.ExportOptions,
- /// Byte offset into the file that contains the export directive.
- src: usize,
+ src: LazySrcLoc,
/// Represents the position of the export, if any, in the output file.
link: link.File.Export,
/// The Decl that performs the export. Note that this is *not* the Decl being exported.
@@ -132,11 +139,12 @@ pub const DeclPlusEmitH = struct {
};
pub const Decl = struct {
- /// This name is relative to the containing namespace of the decl. It uses a null-termination
- /// to save bytes, since there can be a lot of decls in a compilation. The null byte is not allowed
- /// in symbol names, because executable file formats use null-terminated strings for symbol names.
- /// All Decls have names, even values that are not bound to a zig namespace. This is necessary for
- /// mapping them to an address in the output file.
+ /// This name is relative to the containing namespace of the decl. It uses
+ /// null-termination to save bytes, since there can be a lot of decls in a
+ /// compilation. The null byte is not allowed in symbol names, because
+ /// executable file formats use null-terminated strings for symbol names.
+ /// All Decls have names, even values that are not bound to a zig namespace.
+ /// This is necessary for mapping them to an address in the output file.
/// Memory owned by this decl, using Module's allocator.
name: [*:0]const u8,
/// The direct parent container of the Decl.
@@ -219,73 +227,102 @@ pub const Decl = struct {
/// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself`
pub const DepsTable = std.ArrayHashMapUnmanaged(*Decl, void, std.array_hash_map.getAutoHashFn(*Decl), std.array_hash_map.getAutoEqlFn(*Decl), false);
- pub fn destroy(self: *Decl, module: *Module) void {
+ pub fn destroy(decl: *Decl, module: *Module) void {
const gpa = module.gpa;
- gpa.free(mem.spanZ(self.name));
- if (self.typedValueManaged()) |tvm| {
+ gpa.free(mem.spanZ(decl.name));
+ if (decl.typedValueManaged()) |tvm| {
+ if (tvm.typed_value.val.castTag(.function)) |payload| {
+ const func = payload.data;
+ func.deinit(gpa);
+ }
tvm.deinit(gpa);
}
- self.dependants.deinit(gpa);
- self.dependencies.deinit(gpa);
+ decl.dependants.deinit(gpa);
+ decl.dependencies.deinit(gpa);
if (module.emit_h != null) {
- const decl_plus_emit_h = @fieldParentPtr(DeclPlusEmitH, "decl", self);
+ const decl_plus_emit_h = @fieldParentPtr(DeclPlusEmitH, "decl", decl);
decl_plus_emit_h.emit_h.fwd_decl.deinit(gpa);
gpa.destroy(decl_plus_emit_h);
} else {
- gpa.destroy(self);
+ gpa.destroy(decl);
}
}
- pub fn srcLoc(self: Decl) SrcLoc {
+ pub fn relativeToNodeIndex(decl: Decl, offset: i32) ast.Node.Index {
+ return @bitCast(ast.Node.Index, offset + @bitCast(i32, decl.srcNode()));
+ }
+
+ pub fn nodeIndexToRelative(decl: Decl, node_index: ast.Node.Index) i32 {
+ return @bitCast(i32, node_index) - @bitCast(i32, decl.srcNode());
+ }
+
+ pub fn tokSrcLoc(decl: Decl, token_index: ast.TokenIndex) LazySrcLoc {
+ return .{ .token_offset = token_index - decl.srcToken() };
+ }
+
+ pub fn nodeSrcLoc(decl: Decl, node_index: ast.Node.Index) LazySrcLoc {
+ return .{ .node_offset = decl.nodeIndexToRelative(node_index) };
+ }
+
+ pub fn srcLoc(decl: *Decl) SrcLoc {
return .{
- .byte_offset = self.src(),
- .file_scope = self.getFileScope(),
+ .container = .{ .decl = decl },
+ .lazy = .{ .node_offset = 0 },
};
}
- pub fn src(self: Decl) usize {
- const tree = &self.container.file_scope.tree;
- const decl_node = tree.rootDecls()[self.src_index];
- return tree.tokens.items(.start)[tree.firstToken(decl_node)];
+ pub fn srcNode(decl: Decl) u32 {
+ const tree = &decl.container.file_scope.tree;
+ return tree.rootDecls()[decl.src_index];
+ }
+
+ pub fn srcToken(decl: Decl) u32 {
+ const tree = &decl.container.file_scope.tree;
+ return tree.firstToken(decl.srcNode());
+ }
+
+ pub fn srcByteOffset(decl: Decl) u32 {
+ const tree = &decl.container.file_scope.tree;
+ return tree.tokens.items(.start)[decl.srcToken()];
}
- pub fn fullyQualifiedNameHash(self: Decl) Scope.NameHash {
- return self.container.fullyQualifiedNameHash(mem.spanZ(self.name));
+ pub fn fullyQualifiedNameHash(decl: Decl) Scope.NameHash {
+ return decl.container.fullyQualifiedNameHash(mem.spanZ(decl.name));
}
- pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue {
- const tvm = self.typedValueManaged() orelse return error.AnalysisFail;
+ pub fn typedValue(decl: *Decl) error{AnalysisFail}!TypedValue {
+ const tvm = decl.typedValueManaged() orelse return error.AnalysisFail;
return tvm.typed_value;
}
- pub fn value(self: *Decl) error{AnalysisFail}!Value {
- return (try self.typedValue()).val;
+ pub fn value(decl: *Decl) error{AnalysisFail}!Value {
+ return (try decl.typedValue()).val;
}
- pub fn dump(self: *Decl) void {
- const loc = std.zig.findLineColumn(self.scope.source.bytes, self.src);
+ pub fn dump(decl: *Decl) void {
+ const loc = std.zig.findLineColumn(decl.scope.source.bytes, decl.src);
std.debug.print("{s}:{d}:{d} name={s} status={s}", .{
- self.scope.sub_file_path,
+ decl.scope.sub_file_path,
loc.line + 1,
loc.column + 1,
- mem.spanZ(self.name),
- @tagName(self.analysis),
+ mem.spanZ(decl.name),
+ @tagName(decl.analysis),
});
- if (self.typedValueManaged()) |tvm| {
+ if (decl.typedValueManaged()) |tvm| {
std.debug.print(" ty={} val={}", .{ tvm.typed_value.ty, tvm.typed_value.val });
}
std.debug.print("\n", .{});
}
- pub fn typedValueManaged(self: *Decl) ?*TypedValue.Managed {
- switch (self.typed_value) {
+ pub fn typedValueManaged(decl: *Decl) ?*TypedValue.Managed {
+ switch (decl.typed_value) {
.most_recent => |*x| return x,
.never_succeeded => return null,
}
}
- pub fn getFileScope(self: Decl) *Scope.File {
- return self.container.file_scope;
+ pub fn getFileScope(decl: Decl) *Scope.File {
+ return decl.container.file_scope;
}
pub fn getEmitH(decl: *Decl, module: *Module) *EmitH {
@@ -294,21 +331,51 @@ pub const Decl = struct {
return &decl_plus_emit_h.emit_h;
}
- fn removeDependant(self: *Decl, other: *Decl) void {
- self.dependants.removeAssertDiscard(other);
+ fn removeDependant(decl: *Decl, other: *Decl) void {
+ decl.dependants.removeAssertDiscard(other);
}
- fn removeDependency(self: *Decl, other: *Decl) void {
- self.dependencies.removeAssertDiscard(other);
+ fn removeDependency(decl: *Decl, other: *Decl) void {
+ decl.dependencies.removeAssertDiscard(other);
}
};
/// This state is attached to every Decl when Module emit_h is non-null.
pub const EmitH = struct {
- fwd_decl: std.ArrayListUnmanaged(u8) = .{},
+ fwd_decl: ArrayListUnmanaged(u8) = .{},
};
-/// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
+/// Represents the data that an explicit error set syntax provides.
+pub const ErrorSet = struct {
+ owner_decl: *Decl,
+ /// Offset from Decl node index, points to the error set AST node.
+ node_offset: i32,
+ names_len: u32,
+ /// The string bytes are stored in the owner Decl arena.
+ /// They are in the same order they appear in the AST.
+ names_ptr: [*]const []const u8,
+};
+
+/// Represents the data that a struct declaration provides.
+pub const Struct = struct {
+ owner_decl: *Decl,
+ /// Set of field names in declaration order.
+ fields: std.StringArrayHashMapUnmanaged(Field),
+ /// Represents the declarations inside this struct.
+ container: Scope.Container,
+
+ /// Offset from Decl node index, points to the struct AST node.
+ node_offset: i32,
+
+ pub const Field = struct {
+ ty: Type,
+ abi_align: Value,
+ /// Uses `unreachable_value` to indicate no default.
+ default_val: Value,
+ };
+};
+
+/// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
/// Extern functions do not have this data structure; they are represented by
/// the `Decl` only, with a `Value` tag of `extern_fn`.
pub const Fn = struct {
@@ -316,9 +383,15 @@ pub const Fn = struct {
/// Contains un-analyzed ZIR instructions generated from Zig source AST.
/// Even after we finish analysis, the ZIR is kept in memory, so that
/// comptime and inline function calls can happen.
- zir: zir.Body,
+ /// Parameter names are stored here so that they may be referenced for debug info,
+ /// without having source code bytes loaded into memory.
+ /// The number of parameters is determined by referring to the type.
+ /// The first N elements of `extra` are indexes into `string_bytes` to
+ /// a null-terminated string.
+ /// This memory is managed with gpa, must be freed when the function is freed.
+ zir: zir.Code,
/// undefined unless analysis state is `success`.
- body: Body,
+ body: ir.Body,
state: Analysis,
pub const Analysis = enum {
@@ -336,8 +409,12 @@ pub const Fn = struct {
};
/// For debugging purposes.
- pub fn dump(self: *Fn, mod: Module) void {
- zir.dumpFn(mod, self);
+ pub fn dump(func: *Fn, mod: Module) void {
+ ir.dumpFn(mod, func);
+ }
+
+ pub fn deinit(func: *Fn, gpa: *Allocator) void {
+ func.zir.deinit(gpa);
}
};
@@ -364,103 +441,93 @@ pub const Scope = struct {
}
/// Returns the arena Allocator associated with the Decl of the Scope.
- pub fn arena(self: *Scope) *Allocator {
- switch (self.tag) {
- .block => return self.cast(Block).?.arena,
- .gen_zir => return self.cast(GenZIR).?.arena,
- .local_val => return self.cast(LocalVal).?.gen_zir.arena,
- .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena,
- .gen_suspend => return self.cast(GenZIR).?.arena,
- .gen_nosuspend => return self.cast(Nosuspend).?.gen_zir.arena,
+ pub fn arena(scope: *Scope) *Allocator {
+ switch (scope.tag) {
+ .block => return scope.cast(Block).?.sema.arena,
+ .gen_zir => return scope.cast(GenZir).?.astgen.arena,
+ .local_val => return scope.cast(LocalVal).?.gen_zir.astgen.arena,
+ .local_ptr => return scope.cast(LocalPtr).?.gen_zir.astgen.arena,
.file => unreachable,
.container => unreachable,
+ .decl_ref => unreachable,
}
}
- pub fn isComptime(self: *Scope) bool {
- return self.getGenZIR().force_comptime;
- }
-
- pub fn ownerDecl(self: *Scope) ?*Decl {
- return switch (self.tag) {
- .block => self.cast(Block).?.owner_decl,
- .gen_zir => self.cast(GenZIR).?.decl,
- .local_val => self.cast(LocalVal).?.gen_zir.decl,
- .local_ptr => self.cast(LocalPtr).?.gen_zir.decl,
- .gen_suspend => return self.cast(GenZIR).?.decl,
- .gen_nosuspend => return self.cast(Nosuspend).?.gen_zir.decl,
+ pub fn ownerDecl(scope: *Scope) ?*Decl {
+ return switch (scope.tag) {
+ .block => scope.cast(Block).?.sema.owner_decl,
+ .gen_zir => scope.cast(GenZir).?.astgen.decl,
+ .local_val => scope.cast(LocalVal).?.gen_zir.astgen.decl,
+ .local_ptr => scope.cast(LocalPtr).?.gen_zir.astgen.decl,
.file => null,
.container => null,
+ .decl_ref => scope.cast(DeclRef).?.decl,
};
}
- pub fn srcDecl(self: *Scope) ?*Decl {
- return switch (self.tag) {
- .block => self.cast(Block).?.src_decl,
- .gen_zir => self.cast(GenZIR).?.decl,
- .local_val => self.cast(LocalVal).?.gen_zir.decl,
- .local_ptr => self.cast(LocalPtr).?.gen_zir.decl,
- .gen_suspend => return self.cast(GenZIR).?.decl,
- .gen_nosuspend => return self.cast(Nosuspend).?.gen_zir.decl,
+ pub fn srcDecl(scope: *Scope) ?*Decl {
+ return switch (scope.tag) {
+ .block => scope.cast(Block).?.src_decl,
+ .gen_zir => scope.cast(GenZir).?.astgen.decl,
+ .local_val => scope.cast(LocalVal).?.gen_zir.astgen.decl,
+ .local_ptr => scope.cast(LocalPtr).?.gen_zir.astgen.decl,
.file => null,
.container => null,
+ .decl_ref => scope.cast(DeclRef).?.decl,
};
}
/// Asserts the scope has a parent which is a Container and returns it.
- pub fn namespace(self: *Scope) *Container {
- switch (self.tag) {
- .block => return self.cast(Block).?.owner_decl.container,
- .gen_zir => return self.cast(GenZIR).?.decl.container,
- .local_val => return self.cast(LocalVal).?.gen_zir.decl.container,
- .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.container,
- .file => return &self.cast(File).?.root_container,
- .container => return self.cast(Container).?,
- .gen_suspend => return self.cast(GenZIR).?.decl.container,
- .gen_nosuspend => return self.cast(Nosuspend).?.gen_zir.decl.container,
+ pub fn namespace(scope: *Scope) *Container {
+ switch (scope.tag) {
+ .block => return scope.cast(Block).?.sema.owner_decl.container,
+ .gen_zir => return scope.cast(GenZir).?.astgen.decl.container,
+ .local_val => return scope.cast(LocalVal).?.gen_zir.astgen.decl.container,
+ .local_ptr => return scope.cast(LocalPtr).?.gen_zir.astgen.decl.container,
+ .file => return &scope.cast(File).?.root_container,
+ .container => return scope.cast(Container).?,
+ .decl_ref => return scope.cast(DeclRef).?.decl.container,
}
}
/// Must generate unique bytes with no collisions with other decls.
/// The point of hashing here is only to limit the number of bytes of
/// the unique identifier to a fixed size (16 bytes).
- pub fn fullyQualifiedNameHash(self: *Scope, name: []const u8) NameHash {
- switch (self.tag) {
+ pub fn fullyQualifiedNameHash(scope: *Scope, name: []const u8) NameHash {
+ switch (scope.tag) {
.block => unreachable,
.gen_zir => unreachable,
.local_val => unreachable,
.local_ptr => unreachable,
- .gen_suspend => unreachable,
- .gen_nosuspend => unreachable,
.file => unreachable,
- .container => return self.cast(Container).?.fullyQualifiedNameHash(name),
+ .container => return scope.cast(Container).?.fullyQualifiedNameHash(name),
+ .decl_ref => unreachable,
}
}
/// Asserts the scope is a child of a File and has an AST tree and returns the tree.
- pub fn tree(self: *Scope) *const ast.Tree {
- switch (self.tag) {
- .file => return &self.cast(File).?.tree,
- .block => return &self.cast(Block).?.src_decl.container.file_scope.tree,
- .gen_zir => return &self.cast(GenZIR).?.decl.container.file_scope.tree,
- .local_val => return &self.cast(LocalVal).?.gen_zir.decl.container.file_scope.tree,
- .local_ptr => return &self.cast(LocalPtr).?.gen_zir.decl.container.file_scope.tree,
- .container => return &self.cast(Container).?.file_scope.tree,
- .gen_suspend => return &self.cast(GenZIR).?.decl.container.file_scope.tree,
- .gen_nosuspend => return &self.cast(Nosuspend).?.gen_zir.decl.container.file_scope.tree,
+ pub fn tree(scope: *Scope) *const ast.Tree {
+ switch (scope.tag) {
+ .file => return &scope.cast(File).?.tree,
+ .block => return &scope.cast(Block).?.src_decl.container.file_scope.tree,
+ .gen_zir => return scope.cast(GenZir).?.tree(),
+ .local_val => return &scope.cast(LocalVal).?.gen_zir.astgen.decl.container.file_scope.tree,
+ .local_ptr => return &scope.cast(LocalPtr).?.gen_zir.astgen.decl.container.file_scope.tree,
+ .container => return &scope.cast(Container).?.file_scope.tree,
+ .decl_ref => return &scope.cast(DeclRef).?.decl.container.file_scope.tree,
}
}
- /// Asserts the scope is a child of a `GenZIR` and returns it.
- pub fn getGenZIR(self: *Scope) *GenZIR {
- return switch (self.tag) {
+ /// Asserts the scope is a child of a `GenZir` and returns it.
+ pub fn getGenZir(scope: *Scope) *GenZir {
+ return switch (scope.tag) {
.block => unreachable,
- .gen_zir, .gen_suspend => self.cast(GenZIR).?,
- .local_val => return self.cast(LocalVal).?.gen_zir,
- .local_ptr => return self.cast(LocalPtr).?.gen_zir,
- .gen_nosuspend => return self.cast(Nosuspend).?.gen_zir,
+ .gen_zir => scope.cast(GenZir).?,
+ .local_val => return scope.cast(LocalVal).?.gen_zir,
+ .local_ptr => return scope.cast(LocalPtr).?.gen_zir,
.file => unreachable,
.container => unreachable,
+ .decl_ref => unreachable,
};
}
@@ -474,8 +541,7 @@ pub const Scope = struct {
.gen_zir => unreachable,
.local_val => unreachable,
.local_ptr => unreachable,
- .gen_suspend => unreachable,
- .gen_nosuspend => unreachable,
+ .decl_ref => unreachable,
}
}
@@ -487,8 +553,7 @@ pub const Scope = struct {
.local_val => unreachable,
.local_ptr => unreachable,
.block => unreachable,
- .gen_suspend => unreachable,
- .gen_nosuspend => unreachable,
+ .decl_ref => unreachable,
}
}
@@ -499,40 +564,11 @@ pub const Scope = struct {
cur = switch (cur.tag) {
.container => return @fieldParentPtr(Container, "base", cur).file_scope,
.file => return @fieldParentPtr(File, "base", cur),
- .gen_zir => @fieldParentPtr(GenZIR, "base", cur).parent,
+ .gen_zir => @fieldParentPtr(GenZir, "base", cur).parent,
.local_val => @fieldParentPtr(LocalVal, "base", cur).parent,
.local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent,
.block => return @fieldParentPtr(Block, "base", cur).src_decl.container.file_scope,
- .gen_suspend => @fieldParentPtr(GenZIR, "base", cur).parent,
- .gen_nosuspend => @fieldParentPtr(Nosuspend, "base", cur).parent,
- };
- }
- }
-
- pub fn getSuspend(base: *Scope) ?*Scope.GenZIR {
- var cur = base;
- while (true) {
- cur = switch (cur.tag) {
- .gen_zir => @fieldParentPtr(GenZIR, "base", cur).parent,
- .local_val => @fieldParentPtr(LocalVal, "base", cur).parent,
- .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent,
- .gen_nosuspend => @fieldParentPtr(Nosuspend, "base", cur).parent,
- .gen_suspend => return @fieldParentPtr(GenZIR, "base", cur),
- else => return null,
- };
- }
- }
-
- pub fn getNosuspend(base: *Scope) ?*Scope.Nosuspend {
- var cur = base;
- while (true) {
- cur = switch (cur.tag) {
- .gen_zir => @fieldParentPtr(GenZIR, "base", cur).parent,
- .local_val => @fieldParentPtr(LocalVal, "base", cur).parent,
- .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent,
- .gen_suspend => @fieldParentPtr(GenZIR, "base", cur).parent,
- .gen_nosuspend => return @fieldParentPtr(Nosuspend, "base", cur),
- else => return null,
+ .decl_ref => return @fieldParentPtr(DeclRef, "base", cur).decl.container.file_scope,
};
}
}
@@ -554,8 +590,10 @@ pub const Scope = struct {
gen_zir,
local_val,
local_ptr,
- gen_suspend,
- gen_nosuspend,
+ /// Used for simple error reporting. Only contains a reference to a
+ /// `Decl` for use with `srcDecl` and `ownerDecl`.
+ /// Has no parents or children.
+ decl_ref,
};
pub const Container = struct {
@@ -568,19 +606,19 @@ pub const Scope = struct {
decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
ty: Type,
- pub fn deinit(self: *Container, gpa: *Allocator) void {
- self.decls.deinit(gpa);
+ pub fn deinit(cont: *Container, gpa: *Allocator) void {
+ cont.decls.deinit(gpa);
// TODO either Container of File should have an arena for sub_file_path and ty
- gpa.destroy(self.ty.castTag(.empty_struct).?);
- gpa.free(self.file_scope.sub_file_path);
- self.* = undefined;
+ gpa.destroy(cont.ty.castTag(.empty_struct).?);
+ gpa.free(cont.file_scope.sub_file_path);
+ cont.* = undefined;
}
- pub fn removeDecl(self: *Container, child: *Decl) void {
- _ = self.decls.swapRemove(child);
+ pub fn removeDecl(cont: *Container, child: *Decl) void {
+ _ = cont.decls.swapRemove(child);
}
- pub fn fullyQualifiedNameHash(self: *Container, name: []const u8) NameHash {
+ pub fn fullyQualifiedNameHash(cont: *Container, name: []const u8) NameHash {
// TODO container scope qualified names.
return std.zig.hashSrc(name);
}
@@ -610,55 +648,55 @@ pub const Scope = struct {
root_container: Container,
- pub fn unload(self: *File, gpa: *Allocator) void {
- switch (self.status) {
+ pub fn unload(file: *File, gpa: *Allocator) void {
+ switch (file.status) {
.never_loaded,
.unloaded_parse_failure,
.unloaded_success,
=> {},
.loaded_success => {
- self.tree.deinit(gpa);
- self.status = .unloaded_success;
+ file.tree.deinit(gpa);
+ file.status = .unloaded_success;
},
}
- switch (self.source) {
+ switch (file.source) {
.bytes => |bytes| {
gpa.free(bytes);
- self.source = .{ .unloaded = {} };
+ file.source = .{ .unloaded = {} };
},
.unloaded => {},
}
}
- pub fn deinit(self: *File, gpa: *Allocator) void {
- self.root_container.deinit(gpa);
- self.unload(gpa);
- self.* = undefined;
+ pub fn deinit(file: *File, gpa: *Allocator) void {
+ file.root_container.deinit(gpa);
+ file.unload(gpa);
+ file.* = undefined;
}
- pub fn destroy(self: *File, gpa: *Allocator) void {
- self.deinit(gpa);
- gpa.destroy(self);
+ pub fn destroy(file: *File, gpa: *Allocator) void {
+ file.deinit(gpa);
+ gpa.destroy(file);
}
- pub fn dumpSrc(self: *File, src: usize) void {
- const loc = std.zig.findLineColumn(self.source.bytes, src);
- std.debug.print("{s}:{d}:{d}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
+ pub fn dumpSrc(file: *File, src: LazySrcLoc) void {
+ const loc = std.zig.findLineColumn(file.source.bytes, src);
+ std.debug.print("{s}:{d}:{d}\n", .{ file.sub_file_path, loc.line + 1, loc.column + 1 });
}
- pub fn getSource(self: *File, module: *Module) ![:0]const u8 {
- switch (self.source) {
+ pub fn getSource(file: *File, module: *Module) ![:0]const u8 {
+ switch (file.source) {
.unloaded => {
- const source = try self.pkg.root_src_directory.handle.readFileAllocOptions(
+ const source = try file.pkg.root_src_directory.handle.readFileAllocOptions(
module.gpa,
- self.sub_file_path,
+ file.sub_file_path,
std.math.maxInt(u32),
null,
1,
0,
);
- self.source = .{ .bytes = source };
+ file.source = .{ .bytes = source };
return source;
},
.bytes => |bytes| return bytes,
@@ -666,37 +704,30 @@ pub const Scope = struct {
}
};
- /// This is a temporary structure, references to it are valid only
+ /// This is the context needed to semantically analyze ZIR instructions and
+ /// produce TZIR instructions.
+ /// This is a temporary structure stored on the stack; references to it are valid only
/// during semantic analysis of the block.
pub const Block = struct {
pub const base_tag: Tag = .block;
base: Scope = Scope{ .tag = base_tag },
parent: ?*Block,
- /// Maps ZIR to TZIR. Shared to sub-blocks.
- inst_table: *InstTable,
- func: ?*Fn,
- /// When analyzing an inline function call, owner_decl is the Decl of the caller
- /// and src_decl is the Decl of the callee.
- /// This Decl owns the arena memory of this Block.
- owner_decl: *Decl,
+ /// Shared among all child blocks.
+ sema: *Sema,
/// This Decl is the Decl according to the Zig source code corresponding to this Block.
+ /// This can vary during inline or comptime function calls. See `Sema.owner_decl`
+ /// for the one that will be the same for all Block instances.
src_decl: *Decl,
- instructions: ArrayListUnmanaged(*Inst),
- /// Points to the arena allocator of the Decl.
- arena: *Allocator,
+ instructions: ArrayListUnmanaged(*ir.Inst),
label: ?Label = null,
inlining: ?*Inlining,
is_comptime: bool,
- /// Shared to sub-blocks.
- branch_quota: *u32,
-
- pub const InstTable = std.AutoHashMap(*zir.Inst, *Inst);
/// This `Block` maps a block ZIR instruction to the corresponding
/// TZIR instruction for break instruction analysis.
pub const Label = struct {
- zir_block: *zir.Inst.Block,
+ zir_block: zir.Inst.Index,
merges: Merges,
};
@@ -706,73 +737,254 @@ pub const Scope = struct {
/// It is shared among all the blocks in an inline or comptime called
/// function.
pub const Inlining = struct {
- /// Shared state among the entire inline/comptime call stack.
- shared: *Shared,
- /// We use this to count from 0 so that arg instructions know
- /// which parameter index they are, without having to store
- /// a parameter index with each arg instruction.
- param_index: usize,
- casted_args: []*Inst,
merges: Merges,
-
- pub const Shared = struct {
- caller: ?*Fn,
- branch_count: u32,
- };
};
pub const Merges = struct {
- block_inst: *Inst.Block,
+ block_inst: *ir.Inst.Block,
/// Separate array list from break_inst_list so that it can be passed directly
/// to resolvePeerTypes.
- results: ArrayListUnmanaged(*Inst),
+ results: ArrayListUnmanaged(*ir.Inst),
/// Keeps track of the break instructions so that the operand can be replaced
/// if we need to add type coercion at the end of block analysis.
/// Same indexes, capacity, length as `results`.
- br_list: ArrayListUnmanaged(*Inst.Br),
+ br_list: ArrayListUnmanaged(*ir.Inst.Br),
};
/// For debugging purposes.
- pub fn dump(self: *Block, mod: Module) void {
- zir.dumpBlock(mod, self);
+ pub fn dump(block: *Block, mod: Module) void {
+ zir.dumpBlock(mod, block);
}
pub fn makeSubBlock(parent: *Block) Block {
return .{
.parent = parent,
- .inst_table = parent.inst_table,
- .func = parent.func,
- .owner_decl = parent.owner_decl,
+ .sema = parent.sema,
.src_decl = parent.src_decl,
.instructions = .{},
- .arena = parent.arena,
.label = null,
.inlining = parent.inlining,
.is_comptime = parent.is_comptime,
- .branch_quota = parent.branch_quota,
};
}
+
+ pub fn wantSafety(block: *const Block) bool {
+ // TODO take into account scope's safety overrides
+ return switch (block.sema.mod.optimizeMode()) {
+ .Debug => true,
+ .ReleaseSafe => true,
+ .ReleaseFast => false,
+ .ReleaseSmall => false,
+ };
+ }
+
+ pub fn getFileScope(block: *Block) *Scope.File {
+ return block.src_decl.container.file_scope;
+ }
+
+ pub fn addNoOp(
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ ty: Type,
+ comptime tag: ir.Inst.Tag,
+ ) !*ir.Inst {
+ const inst = try block.sema.arena.create(tag.Type());
+ inst.* = .{
+ .base = .{
+ .tag = tag,
+ .ty = ty,
+ .src = src,
+ },
+ };
+ try block.instructions.append(block.sema.gpa, &inst.base);
+ return &inst.base;
+ }
+
+ pub fn addUnOp(
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ ty: Type,
+ tag: ir.Inst.Tag,
+ operand: *ir.Inst,
+ ) !*ir.Inst {
+ const inst = try block.sema.arena.create(ir.Inst.UnOp);
+ inst.* = .{
+ .base = .{
+ .tag = tag,
+ .ty = ty,
+ .src = src,
+ },
+ .operand = operand,
+ };
+ try block.instructions.append(block.sema.gpa, &inst.base);
+ return &inst.base;
+ }
+
+ pub fn addBinOp(
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ ty: Type,
+ tag: ir.Inst.Tag,
+ lhs: *ir.Inst,
+ rhs: *ir.Inst,
+ ) !*ir.Inst {
+ const inst = try block.sema.arena.create(ir.Inst.BinOp);
+ inst.* = .{
+ .base = .{
+ .tag = tag,
+ .ty = ty,
+ .src = src,
+ },
+ .lhs = lhs,
+ .rhs = rhs,
+ };
+ try block.instructions.append(block.sema.gpa, &inst.base);
+ return &inst.base;
+ }
+
+ pub fn addBr(
+ scope_block: *Scope.Block,
+ src: LazySrcLoc,
+ target_block: *ir.Inst.Block,
+ operand: *ir.Inst,
+ ) !*ir.Inst.Br {
+ const inst = try scope_block.sema.arena.create(ir.Inst.Br);
+ inst.* = .{
+ .base = .{
+ .tag = .br,
+ .ty = Type.initTag(.noreturn),
+ .src = src,
+ },
+ .operand = operand,
+ .block = target_block,
+ };
+ try scope_block.instructions.append(scope_block.sema.gpa, &inst.base);
+ return inst;
+ }
+
+ pub fn addCondBr(
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ condition: *ir.Inst,
+ then_body: ir.Body,
+ else_body: ir.Body,
+ ) !*ir.Inst {
+ const inst = try block.sema.arena.create(ir.Inst.CondBr);
+ inst.* = .{
+ .base = .{
+ .tag = .condbr,
+ .ty = Type.initTag(.noreturn),
+ .src = src,
+ },
+ .condition = condition,
+ .then_body = then_body,
+ .else_body = else_body,
+ };
+ try block.instructions.append(block.sema.gpa, &inst.base);
+ return &inst.base;
+ }
+
+ pub fn addCall(
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ ty: Type,
+ func: *ir.Inst,
+ args: []const *ir.Inst,
+ ) !*ir.Inst {
+ const inst = try block.sema.arena.create(ir.Inst.Call);
+ inst.* = .{
+ .base = .{
+ .tag = .call,
+ .ty = ty,
+ .src = src,
+ },
+ .func = func,
+ .args = args,
+ };
+ try block.instructions.append(block.sema.gpa, &inst.base);
+ return &inst.base;
+ }
+
+ pub fn addSwitchBr(
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ operand: *ir.Inst,
+ cases: []ir.Inst.SwitchBr.Case,
+ else_body: ir.Body,
+ ) !*ir.Inst {
+ const inst = try block.sema.arena.create(ir.Inst.SwitchBr);
+ inst.* = .{
+ .base = .{
+ .tag = .switchbr,
+ .ty = Type.initTag(.noreturn),
+ .src = src,
+ },
+ .target = operand,
+ .cases = cases,
+ .else_body = else_body,
+ };
+ try block.instructions.append(block.sema.gpa, &inst.base);
+ return &inst.base;
+ }
+
+ pub fn addDbgStmt(block: *Scope.Block, src: LazySrcLoc, abs_byte_off: u32) !*ir.Inst {
+ const inst = try block.sema.arena.create(ir.Inst.DbgStmt);
+ inst.* = .{
+ .base = .{
+ .tag = .dbg_stmt,
+ .ty = Type.initTag(.void),
+ .src = src,
+ },
+ .byte_offset = abs_byte_off,
+ };
+ try block.instructions.append(block.sema.gpa, &inst.base);
+ return &inst.base;
+ }
+
+ pub fn addStructFieldPtr(
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ ty: Type,
+ struct_ptr: *ir.Inst,
+ field_index: u32,
+ ) !*ir.Inst {
+ const inst = try block.sema.arena.create(ir.Inst.StructFieldPtr);
+ inst.* = .{
+ .base = .{
+ .tag = .struct_field_ptr,
+ .ty = ty,
+ .src = src,
+ },
+ .struct_ptr = struct_ptr,
+ .field_index = field_index,
+ };
+ try block.instructions.append(block.sema.gpa, &inst.base);
+ return &inst.base;
+ }
};
- /// This is a temporary structure, references to it are valid only
- /// during semantic analysis of the decl.
- pub const GenZIR = struct {
+ /// This is a temporary structure; references to it are valid only
+ /// while constructing a `zir.Code`.
+ pub const GenZir = struct {
pub const base_tag: Tag = .gen_zir;
base: Scope = Scope{ .tag = base_tag },
- /// Parents can be: `GenZIR`, `File`
- parent: *Scope,
- decl: *Decl,
- arena: *Allocator,
force_comptime: bool,
- /// The first N instructions in a function body ZIR are arg instructions.
- instructions: std.ArrayListUnmanaged(*zir.Inst) = .{},
+ /// Parents can be: `GenZir`, `File`
+ parent: *Scope,
+ /// All `GenZir` scopes for the same ZIR share this.
+ astgen: *AstGen,
+ /// Keeps track of the list of instructions in this scope only. Indexes
+ /// to instructions in `astgen`.
+ instructions: ArrayListUnmanaged(zir.Inst.Index) = .{},
label: ?Label = null,
- break_block: ?*zir.Inst.Block = null,
- continue_block: ?*zir.Inst.Block = null,
- /// Only valid when setBlockResultLoc is called.
- break_result_loc: astgen.ResultLoc = undefined,
+ break_block: zir.Inst.Index = 0,
+ continue_block: zir.Inst.Index = 0,
+ /// Only valid when setBreakResultLoc is called.
+ break_result_loc: AstGen.ResultLoc = undefined,
/// When a block has a pointer result location, here it is.
- rl_ptr: ?*zir.Inst = null,
+ rl_ptr: zir.Inst.Ref = .none,
+ /// When a block has a type result location, here it is.
+ rl_ty_inst: zir.Inst.Ref = .none,
/// Keeps track of how many branches of a block did not actually
/// consume the result location. astgen uses this to figure out
/// whether to rely on break instructions or writing to the result
@@ -784,19 +996,466 @@ pub const Scope = struct {
break_count: usize = 0,
/// Tracks `break :foo bar` instructions so they can possibly be elided later if
/// the labeled block ends up not needing a result location pointer.
- labeled_breaks: std.ArrayListUnmanaged(*zir.Inst.Break) = .{},
+ labeled_breaks: ArrayListUnmanaged(zir.Inst.Index) = .{},
/// Tracks `store_to_block_ptr` instructions that correspond to break instructions
/// so they can possibly be elided later if the labeled block ends up not needing
/// a result location pointer.
- labeled_store_to_block_ptr_list: std.ArrayListUnmanaged(*zir.Inst.BinOp) = .{},
- /// for suspend error notes
- src: usize = 0,
+ labeled_store_to_block_ptr_list: ArrayListUnmanaged(zir.Inst.Index) = .{},
pub const Label = struct {
token: ast.TokenIndex,
- block_inst: *zir.Inst.Block,
+ block_inst: zir.Inst.Index,
used: bool = false,
};
+
+ /// Only valid to call on the top of the `GenZir` stack. Completes the
+ /// `AstGen` into a `zir.Code`. Leaves the `AstGen` in an
+ /// initialized, but empty, state.
+ pub fn finish(gz: *GenZir) !zir.Code {
+ const gpa = gz.astgen.mod.gpa;
+ try gz.setBlockBody(0);
+ return zir.Code{
+ .instructions = gz.astgen.instructions.toOwnedSlice(),
+ .string_bytes = gz.astgen.string_bytes.toOwnedSlice(gpa),
+ .extra = gz.astgen.extra.toOwnedSlice(gpa),
+ .decls = gz.astgen.decls.toOwnedSlice(gpa),
+ };
+ }
+
+ pub fn tokSrcLoc(gz: GenZir, token_index: ast.TokenIndex) LazySrcLoc {
+ return gz.astgen.decl.tokSrcLoc(token_index);
+ }
+
+ pub fn nodeSrcLoc(gz: GenZir, node_index: ast.Node.Index) LazySrcLoc {
+ return gz.astgen.decl.nodeSrcLoc(node_index);
+ }
+
+ pub fn tree(gz: *const GenZir) *const ast.Tree {
+ return &gz.astgen.decl.container.file_scope.tree;
+ }
+
+ pub fn setBreakResultLoc(gz: *GenZir, parent_rl: AstGen.ResultLoc) void {
+ // Depending on whether the result location is a pointer or value, different
+ // ZIR needs to be generated. In the former case we rely on storing to the
+ // pointer to communicate the result, and use breakvoid; in the latter case
+ // the block break instructions will have the result values.
+ // One more complication: when the result location is a pointer, we detect
+ // the scenario where the result location is not consumed. In this case
+ // we emit ZIR for the block break instructions to have the result values,
+ // and then rvalue() on that to pass the value to the result location.
+ switch (parent_rl) {
+ .ty => |ty_inst| {
+ gz.rl_ty_inst = ty_inst;
+ gz.break_result_loc = parent_rl;
+ },
+ .discard, .none, .ptr, .ref => {
+ gz.break_result_loc = parent_rl;
+ },
+
+ .inferred_ptr => |ptr| {
+ gz.rl_ptr = ptr;
+ gz.break_result_loc = .{ .block_ptr = gz };
+ },
+
+ .block_ptr => |parent_block_scope| {
+ gz.rl_ty_inst = parent_block_scope.rl_ty_inst;
+ gz.rl_ptr = parent_block_scope.rl_ptr;
+ gz.break_result_loc = .{ .block_ptr = gz };
+ },
+ }
+ }
+
+ pub fn setBoolBrBody(gz: GenZir, inst: zir.Inst.Index) !void {
+ const gpa = gz.astgen.mod.gpa;
+ try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
+ @typeInfo(zir.Inst.Block).Struct.fields.len + gz.instructions.items.len);
+ const zir_datas = gz.astgen.instructions.items(.data);
+ zir_datas[inst].bool_br.payload_index = gz.astgen.addExtraAssumeCapacity(
+ zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) },
+ );
+ gz.astgen.extra.appendSliceAssumeCapacity(gz.instructions.items);
+ }
+
+ pub fn setBlockBody(gz: GenZir, inst: zir.Inst.Index) !void {
+ const gpa = gz.astgen.mod.gpa;
+ try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
+ @typeInfo(zir.Inst.Block).Struct.fields.len + gz.instructions.items.len);
+ const zir_datas = gz.astgen.instructions.items(.data);
+ zir_datas[inst].pl_node.payload_index = gz.astgen.addExtraAssumeCapacity(
+ zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) },
+ );
+ gz.astgen.extra.appendSliceAssumeCapacity(gz.instructions.items);
+ }
+
+ pub fn identAsString(gz: *GenZir, ident_token: ast.TokenIndex) !u32 {
+ const astgen = gz.astgen;
+ const gpa = astgen.mod.gpa;
+ const string_bytes = &astgen.string_bytes;
+ const str_index = @intCast(u32, string_bytes.items.len);
+ try astgen.mod.appendIdentStr(&gz.base, ident_token, string_bytes);
+ try string_bytes.append(gpa, 0);
+ return str_index;
+ }
+
+ pub fn addFnTypeCc(gz: *GenZir, tag: zir.Inst.Tag, args: struct {
+ src_node: ast.Node.Index,
+ param_types: []const zir.Inst.Ref,
+ ret_ty: zir.Inst.Ref,
+ cc: zir.Inst.Ref,
+ }) !zir.Inst.Ref {
+ assert(args.src_node != 0);
+ assert(args.ret_ty != .none);
+ assert(args.cc != .none);
+ const gpa = gz.astgen.mod.gpa;
+ try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
+ try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
+ try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
+ @typeInfo(zir.Inst.FnTypeCc).Struct.fields.len + args.param_types.len);
+
+ const payload_index = gz.astgen.addExtraAssumeCapacity(zir.Inst.FnTypeCc{
+ .return_type = args.ret_ty,
+ .cc = args.cc,
+ .param_types_len = @intCast(u32, args.param_types.len),
+ });
+ gz.astgen.appendRefsAssumeCapacity(args.param_types);
+
+ const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len);
+ gz.astgen.instructions.appendAssumeCapacity(.{
+ .tag = tag,
+ .data = .{ .pl_node = .{
+ .src_node = gz.astgen.decl.nodeIndexToRelative(args.src_node),
+ .payload_index = payload_index,
+ } },
+ });
+ gz.instructions.appendAssumeCapacity(new_index);
+ return gz.astgen.indexToRef(new_index);
+ }
+
+ pub fn addFnType(gz: *GenZir, tag: zir.Inst.Tag, args: struct {
+ src_node: ast.Node.Index,
+ ret_ty: zir.Inst.Ref,
+ param_types: []const zir.Inst.Ref,
+ }) !zir.Inst.Ref {
+ assert(args.src_node != 0);
+ assert(args.ret_ty != .none);
+ const gpa = gz.astgen.mod.gpa;
+ try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
+ try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
+ try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
+ @typeInfo(zir.Inst.FnType).Struct.fields.len + args.param_types.len);
+
+ const payload_index = gz.astgen.addExtraAssumeCapacity(zir.Inst.FnType{
+ .return_type = args.ret_ty,
+ .param_types_len = @intCast(u32, args.param_types.len),
+ });
+ gz.astgen.appendRefsAssumeCapacity(args.param_types);
+
+ const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len);
+ gz.astgen.instructions.appendAssumeCapacity(.{
+ .tag = tag,
+ .data = .{ .pl_node = .{
+ .src_node = gz.astgen.decl.nodeIndexToRelative(args.src_node),
+ .payload_index = payload_index,
+ } },
+ });
+ gz.instructions.appendAssumeCapacity(new_index);
+ return gz.astgen.indexToRef(new_index);
+ }
+
+ pub fn addCall(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ callee: zir.Inst.Ref,
+ args: []const zir.Inst.Ref,
+ /// Absolute node index. This function does the conversion to offset from Decl.
+ src_node: ast.Node.Index,
+ ) !zir.Inst.Ref {
+ assert(callee != .none);
+ assert(src_node != 0);
+ const gpa = gz.astgen.mod.gpa;
+ try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
+ try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
+ try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len +
+ @typeInfo(zir.Inst.Call).Struct.fields.len + args.len);
+
+ const payload_index = gz.astgen.addExtraAssumeCapacity(zir.Inst.Call{
+ .callee = callee,
+ .args_len = @intCast(u32, args.len),
+ });
+ gz.astgen.appendRefsAssumeCapacity(args);
+
+ const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len);
+ gz.astgen.instructions.appendAssumeCapacity(.{
+ .tag = tag,
+ .data = .{ .pl_node = .{
+ .src_node = gz.astgen.decl.nodeIndexToRelative(src_node),
+ .payload_index = payload_index,
+ } },
+ });
+ gz.instructions.appendAssumeCapacity(new_index);
+ return gz.astgen.indexToRef(new_index);
+ }
+
+ /// Note that this returns a `zir.Inst.Index` not a ref.
+ /// Leaves the `payload_index` field undefined.
+ pub fn addBoolBr(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ lhs: zir.Inst.Ref,
+ ) !zir.Inst.Index {
+ assert(lhs != .none);
+ const gpa = gz.astgen.mod.gpa;
+ try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
+ try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
+
+ const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len);
+ gz.astgen.instructions.appendAssumeCapacity(.{
+ .tag = tag,
+ .data = .{ .bool_br = .{
+ .lhs = lhs,
+ .payload_index = undefined,
+ } },
+ });
+ gz.instructions.appendAssumeCapacity(new_index);
+ return new_index;
+ }
+
+ pub fn addInt(gz: *GenZir, integer: u64) !zir.Inst.Ref {
+ return gz.add(.{
+ .tag = .int,
+ .data = .{ .int = integer },
+ });
+ }
+
+ pub fn addUnNode(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ operand: zir.Inst.Ref,
+ /// Absolute node index. This function does the conversion to offset from Decl.
+ src_node: ast.Node.Index,
+ ) !zir.Inst.Ref {
+ assert(operand != .none);
+ return gz.add(.{
+ .tag = tag,
+ .data = .{ .un_node = .{
+ .operand = operand,
+ .src_node = gz.astgen.decl.nodeIndexToRelative(src_node),
+ } },
+ });
+ }
+
+ pub fn addPlNode(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ /// Absolute node index. This function does the conversion to offset from Decl.
+ src_node: ast.Node.Index,
+ extra: anytype,
+ ) !zir.Inst.Ref {
+ const gpa = gz.astgen.mod.gpa;
+ try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
+ try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
+
+ const payload_index = try gz.astgen.addExtra(extra);
+ const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len);
+ gz.astgen.instructions.appendAssumeCapacity(.{
+ .tag = tag,
+ .data = .{ .pl_node = .{
+ .src_node = gz.astgen.decl.nodeIndexToRelative(src_node),
+ .payload_index = payload_index,
+ } },
+ });
+ gz.instructions.appendAssumeCapacity(new_index);
+ return gz.astgen.indexToRef(new_index);
+ }
+
+ pub fn addArrayTypeSentinel(
+ gz: *GenZir,
+ len: zir.Inst.Ref,
+ sentinel: zir.Inst.Ref,
+ elem_type: zir.Inst.Ref,
+ ) !zir.Inst.Ref {
+ const gpa = gz.astgen.mod.gpa;
+ try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
+ try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
+
+ const payload_index = try gz.astgen.addExtra(zir.Inst.ArrayTypeSentinel{
+ .sentinel = sentinel,
+ .elem_type = elem_type,
+ });
+ const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len);
+ gz.astgen.instructions.appendAssumeCapacity(.{
+ .tag = .array_type_sentinel,
+ .data = .{ .array_type_sentinel = .{
+ .len = len,
+ .payload_index = payload_index,
+ } },
+ });
+ gz.instructions.appendAssumeCapacity(new_index);
+ return gz.astgen.indexToRef(new_index);
+ }
+
+ pub fn addUnTok(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ operand: zir.Inst.Ref,
+ /// Absolute token index. This function does the conversion to Decl offset.
+ abs_tok_index: ast.TokenIndex,
+ ) !zir.Inst.Ref {
+ assert(operand != .none);
+ return gz.add(.{
+ .tag = tag,
+ .data = .{ .un_tok = .{
+ .operand = operand,
+ .src_tok = abs_tok_index - gz.astgen.decl.srcToken(),
+ } },
+ });
+ }
+
+ pub fn addStrTok(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ str_index: u32,
+ /// Absolute token index. This function does the conversion to Decl offset.
+ abs_tok_index: ast.TokenIndex,
+ ) !zir.Inst.Ref {
+ return gz.add(.{
+ .tag = tag,
+ .data = .{ .str_tok = .{
+ .start = str_index,
+ .src_tok = abs_tok_index - gz.astgen.decl.srcToken(),
+ } },
+ });
+ }
+
+ pub fn addBreak(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ break_block: zir.Inst.Index,
+ operand: zir.Inst.Ref,
+ ) !zir.Inst.Index {
+ return gz.addAsIndex(.{
+ .tag = tag,
+ .data = .{ .@"break" = .{
+ .block_inst = break_block,
+ .operand = operand,
+ } },
+ });
+ }
+
+ pub fn addBin(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ lhs: zir.Inst.Ref,
+ rhs: zir.Inst.Ref,
+ ) !zir.Inst.Ref {
+ assert(lhs != .none);
+ assert(rhs != .none);
+ return gz.add(.{
+ .tag = tag,
+ .data = .{ .bin = .{
+ .lhs = lhs,
+ .rhs = rhs,
+ } },
+ });
+ }
+
+ pub fn addDecl(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ decl_index: u32,
+ src_node: ast.Node.Index,
+ ) !zir.Inst.Ref {
+ return gz.add(.{
+ .tag = tag,
+ .data = .{ .pl_node = .{
+ .src_node = gz.astgen.decl.nodeIndexToRelative(src_node),
+ .payload_index = decl_index,
+ } },
+ });
+ }
+
+ pub fn addNode(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ /// Absolute node index. This function does the conversion to offset from Decl.
+ src_node: ast.Node.Index,
+ ) !zir.Inst.Ref {
+ return gz.add(.{
+ .tag = tag,
+ .data = .{ .node = gz.astgen.decl.nodeIndexToRelative(src_node) },
+ });
+ }
+
+ /// Asserts that `str` is 8 or fewer bytes.
+ pub fn addSmallStr(
+ gz: *GenZir,
+ tag: zir.Inst.Tag,
+ str: []const u8,
+ ) !zir.Inst.Ref {
+ var buf: [9]u8 = undefined;
+ mem.copy(u8, &buf, str);
+ buf[str.len] = 0;
+
+ return gz.add(.{
+ .tag = tag,
+ .data = .{ .small_str = .{ .bytes = buf[0..8].* } },
+ });
+ }
+
+ /// Note that this returns a `zir.Inst.Index` not a ref.
+ /// Does *not* append the block instruction to the scope.
+ /// Leaves the `payload_index` field undefined.
+ pub fn addBlock(gz: *GenZir, tag: zir.Inst.Tag, node: ast.Node.Index) !zir.Inst.Index {
+ const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len);
+ const gpa = gz.astgen.mod.gpa;
+ try gz.astgen.instructions.append(gpa, .{
+ .tag = tag,
+ .data = .{ .pl_node = .{
+ .src_node = gz.astgen.decl.nodeIndexToRelative(node),
+ .payload_index = undefined,
+ } },
+ });
+ return new_index;
+ }
+
+ /// Note that this returns a `zir.Inst.Index` not a ref.
+ /// Leaves the `payload_index` field undefined.
+ pub fn addCondBr(gz: *GenZir, tag: zir.Inst.Tag, node: ast.Node.Index) !zir.Inst.Index {
+ const gpa = gz.astgen.mod.gpa;
+ try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
+ const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len);
+ try gz.astgen.instructions.append(gpa, .{
+ .tag = tag,
+ .data = .{ .pl_node = .{
+ .src_node = gz.astgen.decl.nodeIndexToRelative(node),
+ .payload_index = undefined,
+ } },
+ });
+ gz.instructions.appendAssumeCapacity(new_index);
+ return new_index;
+ }
+
+ pub fn addConst(gz: *GenZir, typed_value: *TypedValue) !zir.Inst.Ref {
+ return gz.add(.{
+ .tag = .@"const",
+ .data = .{ .@"const" = typed_value },
+ });
+ }
+
+ pub fn add(gz: *GenZir, inst: zir.Inst) !zir.Inst.Ref {
+ return gz.astgen.indexToRef(try gz.addAsIndex(inst));
+ }
+
+ pub fn addAsIndex(gz: *GenZir, inst: zir.Inst) !zir.Inst.Index {
+ const gpa = gz.astgen.mod.gpa;
+ try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
+ try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
+
+ const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len);
+ gz.astgen.instructions.appendAssumeCapacity(inst);
+ gz.instructions.appendAssumeCapacity(new_index);
+ return new_index;
+ }
};
/// This is always a `const` local and importantly the `inst` is a value type, not a pointer.
@@ -805,11 +1464,13 @@ pub const Scope = struct {
pub const LocalVal = struct {
pub const base_tag: Tag = .local_val;
base: Scope = Scope{ .tag = base_tag },
- /// Parents can be: `LocalVal`, `LocalPtr`, `GenZIR`.
+ /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`.
parent: *Scope,
- gen_zir: *GenZIR,
+ gen_zir: *GenZir,
name: []const u8,
- inst: *zir.Inst,
+ inst: zir.Inst.Ref,
+ /// Source location of the corresponding variable declaration.
+ src: LazySrcLoc,
};
/// This could be a `const` or `var` local. It has a pointer instead of a value.
@@ -818,21 +1479,19 @@ pub const Scope = struct {
pub const LocalPtr = struct {
pub const base_tag: Tag = .local_ptr;
base: Scope = Scope{ .tag = base_tag },
- /// Parents can be: `LocalVal`, `LocalPtr`, `GenZIR`.
+ /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`.
parent: *Scope,
- gen_zir: *GenZIR,
+ gen_zir: *GenZir,
name: []const u8,
- ptr: *zir.Inst,
+ ptr: zir.Inst.Ref,
+ /// Source location of the corresponding variable declaration.
+ src: LazySrcLoc,
};
- pub const Nosuspend = struct {
- pub const base_tag: Tag = .gen_nosuspend;
-
+ pub const DeclRef = struct {
+ pub const base_tag: Tag = .decl_ref;
base: Scope = Scope{ .tag = base_tag },
- /// Parents can be: `LocalVal`, `LocalPtr`, `GenZIR`.
- parent: *Scope,
- gen_zir: *GenZIR,
- src: usize,
+ decl: *Decl,
};
};
@@ -855,17 +1514,17 @@ pub const ErrorMsg = struct {
comptime format: []const u8,
args: anytype,
) !*ErrorMsg {
- const self = try gpa.create(ErrorMsg);
- errdefer gpa.destroy(self);
- self.* = try init(gpa, src_loc, format, args);
- return self;
+ const err_msg = try gpa.create(ErrorMsg);
+ errdefer gpa.destroy(err_msg);
+ err_msg.* = try init(gpa, src_loc, format, args);
+ return err_msg;
}
/// Assumes the ErrorMsg struct and msg were both allocated with `gpa`,
/// as well as all notes.
- pub fn destroy(self: *ErrorMsg, gpa: *Allocator) void {
- self.deinit(gpa);
- gpa.destroy(self);
+ pub fn destroy(err_msg: *ErrorMsg, gpa: *Allocator) void {
+ err_msg.deinit(gpa);
+ gpa.destroy(err_msg);
}
pub fn init(
@@ -880,84 +1539,715 @@ pub const ErrorMsg = struct {
};
}
- pub fn deinit(self: *ErrorMsg, gpa: *Allocator) void {
- for (self.notes) |*note| {
+ pub fn deinit(err_msg: *ErrorMsg, gpa: *Allocator) void {
+ for (err_msg.notes) |*note| {
note.deinit(gpa);
}
- gpa.free(self.notes);
- gpa.free(self.msg);
- self.* = undefined;
+ gpa.free(err_msg.notes);
+ gpa.free(err_msg.msg);
+ err_msg.* = undefined;
}
};
/// Canonical reference to a position within a source file.
pub const SrcLoc = struct {
- file_scope: *Scope.File,
- byte_offset: usize,
-};
-
-pub const InnerError = error{ OutOfMemory, AnalysisFail };
-
-pub fn deinit(self: *Module) void {
- const gpa = self.gpa;
-
- self.compile_log_text.deinit(gpa);
-
- self.zig_cache_artifact_directory.handle.close();
-
- self.deletion_set.deinit(gpa);
-
- for (self.decl_table.items()) |entry| {
- entry.value.destroy(self);
- }
- self.decl_table.deinit(gpa);
-
- for (self.failed_decls.items()) |entry| {
- entry.value.destroy(gpa);
- }
- self.failed_decls.deinit(gpa);
-
- for (self.emit_h_failed_decls.items()) |entry| {
- entry.value.destroy(gpa);
- }
- self.emit_h_failed_decls.deinit(gpa);
-
- for (self.failed_files.items()) |entry| {
- entry.value.destroy(gpa);
- }
- self.failed_files.deinit(gpa);
-
- for (self.failed_exports.items()) |entry| {
- entry.value.destroy(gpa);
- }
- self.failed_exports.deinit(gpa);
-
- self.compile_log_decls.deinit(gpa);
-
- for (self.decl_exports.items()) |entry| {
- const export_list = entry.value;
- gpa.free(export_list);
- }
- self.decl_exports.deinit(gpa);
-
- for (self.export_owners.items()) |entry| {
- freeExportList(gpa, entry.value);
+ /// The active field is determined by tag of `lazy`.
+ container: union {
+ /// The containing `Decl` according to the source code.
+ decl: *Decl,
+ file_scope: *Scope.File,
+ },
+ /// Relative to `decl`.
+ lazy: LazySrcLoc,
+
+ pub fn fileScope(src_loc: SrcLoc) *Scope.File {
+ return switch (src_loc.lazy) {
+ .unneeded => unreachable,
+
+ .byte_abs,
+ .token_abs,
+ .node_abs,
+ => src_loc.container.file_scope,
+
+ .byte_offset,
+ .token_offset,
+ .node_offset,
+ .node_offset_var_decl_ty,
+ .node_offset_for_cond,
+ .node_offset_builtin_call_arg0,
+ .node_offset_builtin_call_arg1,
+ .node_offset_array_access_index,
+ .node_offset_slice_sentinel,
+ .node_offset_call_func,
+ .node_offset_field_name,
+ .node_offset_deref_ptr,
+ .node_offset_asm_source,
+ .node_offset_asm_ret_ty,
+ .node_offset_if_cond,
+ .node_offset_bin_op,
+ .node_offset_bin_lhs,
+ .node_offset_bin_rhs,
+ .node_offset_switch_operand,
+ .node_offset_switch_special_prong,
+ .node_offset_switch_range,
+ .node_offset_fn_type_cc,
+ .node_offset_fn_type_ret_ty,
+ => src_loc.container.decl.container.file_scope,
+ };
}
- self.export_owners.deinit(gpa);
- self.symbol_exports.deinit(gpa);
- self.root_scope.destroy(gpa);
+ pub fn byteOffset(src_loc: SrcLoc) !u32 {
+ switch (src_loc.lazy) {
+ .unneeded => unreachable,
- var it = self.global_error_set.iterator();
- while (it.next()) |entry| {
- gpa.free(entry.key);
- }
- self.global_error_set.deinit(gpa);
+ .byte_abs => |byte_index| return byte_index,
- for (self.import_table.items()) |entry| {
+ .token_abs => |tok_index| {
+ const tree = src_loc.container.file_scope.base.tree();
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_abs => |node| {
+ const tree = src_loc.container.file_scope.base.tree();
+ const token_starts = tree.tokens.items(.start);
+ const tok_index = tree.firstToken(node);
+ return token_starts[tok_index];
+ },
+ .byte_offset => |byte_off| {
+ const decl = src_loc.container.decl;
+ return decl.srcByteOffset() + byte_off;
+ },
+ .token_offset => |tok_off| {
+ const decl = src_loc.container.decl;
+ const tok_index = decl.srcToken() + tok_off;
+ const tree = decl.container.file_scope.base.tree();
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset, .node_offset_bin_op => |node_off| {
+ const decl = src_loc.container.decl;
+ const node = decl.relativeToNodeIndex(node_off);
+ const tree = decl.container.file_scope.base.tree();
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[node];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_var_decl_ty => |node_off| {
+ const decl = src_loc.container.decl;
+ const node = decl.relativeToNodeIndex(node_off);
+ const tree = decl.container.file_scope.base.tree();
+ const node_tags = tree.nodes.items(.tag);
+ const full = switch (node_tags[node]) {
+ .global_var_decl => tree.globalVarDecl(node),
+ .local_var_decl => tree.localVarDecl(node),
+ .simple_var_decl => tree.simpleVarDecl(node),
+ .aligned_var_decl => tree.alignedVarDecl(node),
+ else => unreachable,
+ };
+ const tok_index = if (full.ast.type_node != 0) blk: {
+ const main_tokens = tree.nodes.items(.main_token);
+ break :blk main_tokens[full.ast.type_node];
+ } else blk: {
+ break :blk full.ast.mut_token + 1; // the name token
+ };
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_builtin_call_arg0 => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ const param = switch (node_tags[node]) {
+ .builtin_call_two, .builtin_call_two_comma => node_datas[node].lhs,
+ .builtin_call, .builtin_call_comma => tree.extra_data[node_datas[node].lhs],
+ else => unreachable,
+ };
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[param];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_builtin_call_arg1 => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ const param = switch (node_tags[node]) {
+ .builtin_call_two, .builtin_call_two_comma => node_datas[node].rhs,
+ .builtin_call, .builtin_call_comma => tree.extra_data[node_datas[node].lhs + 1],
+ else => unreachable,
+ };
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[param];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_array_access_index => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[node_datas[node].rhs];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_slice_sentinel => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ const full = switch (node_tags[node]) {
+ .slice_open => tree.sliceOpen(node),
+ .slice => tree.slice(node),
+ .slice_sentinel => tree.sliceSentinel(node),
+ else => unreachable,
+ };
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[full.ast.sentinel];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_call_func => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ var params: [1]ast.Node.Index = undefined;
+ const full = switch (node_tags[node]) {
+ .call_one,
+ .call_one_comma,
+ .async_call_one,
+ .async_call_one_comma,
+ => tree.callOne(¶ms, node),
+
+ .call,
+ .call_comma,
+ .async_call,
+ .async_call_comma,
+ => tree.callFull(node),
+
+ else => unreachable,
+ };
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[full.ast.fn_expr];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_field_name => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ const tok_index = node_datas[node].rhs;
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_deref_ptr => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ const tok_index = node_datas[node].lhs;
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_asm_source => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ const full = switch (node_tags[node]) {
+ .asm_simple => tree.asmSimple(node),
+ .@"asm" => tree.asmFull(node),
+ else => unreachable,
+ };
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[full.ast.template];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_asm_ret_ty => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ const full = switch (node_tags[node]) {
+ .asm_simple => tree.asmSimple(node),
+ .@"asm" => tree.asmFull(node),
+ else => unreachable,
+ };
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[full.outputs[0]];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+
+ .node_offset_for_cond, .node_offset_if_cond => |node_off| {
+ const decl = src_loc.container.decl;
+ const node = decl.relativeToNodeIndex(node_off);
+ const tree = decl.container.file_scope.base.tree();
+ const node_tags = tree.nodes.items(.tag);
+ const src_node = switch (node_tags[node]) {
+ .if_simple => tree.ifSimple(node).ast.cond_expr,
+ .@"if" => tree.ifFull(node).ast.cond_expr,
+ .while_simple => tree.whileSimple(node).ast.cond_expr,
+ .while_cont => tree.whileCont(node).ast.cond_expr,
+ .@"while" => tree.whileFull(node).ast.cond_expr,
+ .for_simple => tree.forSimple(node).ast.cond_expr,
+ .@"for" => tree.forFull(node).ast.cond_expr,
+ else => unreachable,
+ };
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[src_node];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_bin_lhs => |node_off| {
+ const decl = src_loc.container.decl;
+ const node = decl.relativeToNodeIndex(node_off);
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const src_node = node_datas[node].lhs;
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[src_node];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ .node_offset_bin_rhs => |node_off| {
+ const decl = src_loc.container.decl;
+ const node = decl.relativeToNodeIndex(node_off);
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const src_node = node_datas[node].rhs;
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[src_node];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+
+ .node_offset_switch_operand => |node_off| {
+ const decl = src_loc.container.decl;
+ const node = decl.relativeToNodeIndex(node_off);
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const src_node = node_datas[node].lhs;
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[src_node];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+
+ .node_offset_switch_special_prong => |node_off| {
+ const decl = src_loc.container.decl;
+ const switch_node = decl.relativeToNodeIndex(node_off);
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const main_tokens = tree.nodes.items(.main_token);
+ const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange);
+ const case_nodes = tree.extra_data[extra.start..extra.end];
+ for (case_nodes) |case_node| {
+ const case = switch (node_tags[case_node]) {
+ .switch_case_one => tree.switchCaseOne(case_node),
+ .switch_case => tree.switchCase(case_node),
+ else => unreachable,
+ };
+ const is_special = (case.ast.values.len == 0) or
+ (case.ast.values.len == 1 and
+ node_tags[case.ast.values[0]] == .identifier and
+ mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"));
+ if (!is_special) continue;
+
+ const tok_index = main_tokens[case_node];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ } else unreachable;
+ },
+
+ .node_offset_switch_range => |node_off| {
+ const decl = src_loc.container.decl;
+ const switch_node = decl.relativeToNodeIndex(node_off);
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const main_tokens = tree.nodes.items(.main_token);
+ const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange);
+ const case_nodes = tree.extra_data[extra.start..extra.end];
+ for (case_nodes) |case_node| {
+ const case = switch (node_tags[case_node]) {
+ .switch_case_one => tree.switchCaseOne(case_node),
+ .switch_case => tree.switchCase(case_node),
+ else => unreachable,
+ };
+ const is_special = (case.ast.values.len == 0) or
+ (case.ast.values.len == 1 and
+ node_tags[case.ast.values[0]] == .identifier and
+ mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"));
+ if (is_special) continue;
+
+ for (case.ast.values) |item_node| {
+ if (node_tags[item_node] == .switch_range) {
+ const tok_index = main_tokens[item_node];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ }
+ }
+ } else unreachable;
+ },
+
+ .node_offset_fn_type_cc => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ var params: [1]ast.Node.Index = undefined;
+ const full = switch (node_tags[node]) {
+ .fn_proto_simple => tree.fnProtoSimple(¶ms, node),
+ .fn_proto_multi => tree.fnProtoMulti(node),
+ .fn_proto_one => tree.fnProtoOne(¶ms, node),
+ .fn_proto => tree.fnProto(node),
+ else => unreachable,
+ };
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[full.ast.callconv_expr];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+
+ .node_offset_fn_type_ret_ty => |node_off| {
+ const decl = src_loc.container.decl;
+ const tree = decl.container.file_scope.base.tree();
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const node = decl.relativeToNodeIndex(node_off);
+ var params: [1]ast.Node.Index = undefined;
+ const full = switch (node_tags[node]) {
+ .fn_proto_simple => tree.fnProtoSimple(¶ms, node),
+ .fn_proto_multi => tree.fnProtoMulti(node),
+ .fn_proto_one => tree.fnProtoOne(¶ms, node),
+ .fn_proto => tree.fnProto(node),
+ else => unreachable,
+ };
+ const main_tokens = tree.nodes.items(.main_token);
+ const tok_index = main_tokens[full.ast.return_type];
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
+ }
+ }
+};
+
+/// Resolving a source location into a byte offset may require doing work
+/// that we would rather not do unless the error actually occurs.
+/// Therefore we need a data structure that contains the information necessary
+/// to lazily produce a `SrcLoc` as required.
+/// Most of the offsets in this data structure are relative to the containing Decl.
+/// This makes the source location resolve properly even when a Decl gets
+/// shifted up or down in the file, as long as the Decl's contents itself
+/// do not change.
+pub const LazySrcLoc = union(enum) {
+ /// When this tag is set, the code that constructed this `LazySrcLoc` is asserting
+ /// that all code paths which would need to resolve the source location are
+ /// unreachable. If you are debugging this tag incorrectly being this value,
+ /// look into using reverse-continue with a memory watchpoint to see where the
+ /// value is being set to this tag.
+ unneeded,
+ /// The source location points to a byte offset within a source file,
+ /// offset from 0. The source file is determined contextually.
+ /// Inside a `SrcLoc`, the `file_scope` union field will be active.
+ byte_abs: u32,
+ /// The source location points to a token within a source file,
+ /// offset from 0. The source file is determined contextually.
+ /// Inside a `SrcLoc`, the `file_scope` union field will be active.
+ token_abs: u32,
+ /// The source location points to an AST node within a source file,
+ /// offset from 0. The source file is determined contextually.
+ /// Inside a `SrcLoc`, the `file_scope` union field will be active.
+ node_abs: u32,
+ /// The source location points to a byte offset within a source file,
+ /// offset from the byte offset of the Decl within the file.
+ /// The Decl is determined contextually.
+ byte_offset: u32,
+ /// This data is the offset into the token list from the Decl token.
+ /// The Decl is determined contextually.
+ token_offset: u32,
+ /// The source location points to an AST node, which is this value offset
+ /// from its containing Decl node AST index.
+ /// The Decl is determined contextually.
+ node_offset: i32,
+ /// The source location points to a variable declaration type expression,
+ /// found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to a variable declaration AST node. Next, navigate
+ /// to the type expression.
+ /// The Decl is determined contextually.
+ node_offset_var_decl_ty: i32,
+ /// The source location points to a for loop condition expression,
+ /// found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to a for loop AST node. Next, navigate
+ /// to the condition expression.
+ /// The Decl is determined contextually.
+ node_offset_for_cond: i32,
+ /// The source location points to the first parameter of a builtin
+ /// function call, found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to a builtin call AST node. Next, navigate
+ /// to the first parameter.
+ /// The Decl is determined contextually.
+ node_offset_builtin_call_arg0: i32,
+ /// Same as `node_offset_builtin_call_arg0` except arg index 1.
+ node_offset_builtin_call_arg1: i32,
+ /// The source location points to the index expression of an array access
+ /// expression, found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to an array access AST node. Next, navigate
+ /// to the index expression.
+ /// The Decl is determined contextually.
+ node_offset_array_access_index: i32,
+ /// The source location points to the sentinel expression of a slice
+ /// expression, found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to a slice AST node. Next, navigate
+ /// to the sentinel expression.
+ /// The Decl is determined contextually.
+ node_offset_slice_sentinel: i32,
+ /// The source location points to the callee expression of a function
+ /// call expression, found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to a function call AST node. Next, navigate
+ /// to the callee expression.
+ /// The Decl is determined contextually.
+ node_offset_call_func: i32,
+ /// The source location points to the field name of a field access expression,
+ /// found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to a field access AST node. Next, navigate
+ /// to the field name token.
+ /// The Decl is determined contextually.
+ node_offset_field_name: i32,
+ /// The source location points to the pointer of a pointer deref expression,
+ /// found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to a pointer deref AST node. Next, navigate
+ /// to the pointer expression.
+ /// The Decl is determined contextually.
+ node_offset_deref_ptr: i32,
+ /// The source location points to the assembly source code of an inline assembly
+ /// expression, found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to inline assembly AST node. Next, navigate
+ /// to the asm template source code.
+ /// The Decl is determined contextually.
+ node_offset_asm_source: i32,
+ /// The source location points to the return type of an inline assembly
+ /// expression, found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to inline assembly AST node. Next, navigate
+ /// to the return type expression.
+ /// The Decl is determined contextually.
+ node_offset_asm_ret_ty: i32,
+ /// The source location points to the condition expression of an if
+ /// expression, found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to an if expression AST node. Next, navigate
+ /// to the condition expression.
+ /// The Decl is determined contextually.
+ node_offset_if_cond: i32,
+ /// The source location points to a binary expression, such as `a + b`, found
+ /// by taking this AST node index offset from the containing Decl AST node.
+ /// The Decl is determined contextually.
+ node_offset_bin_op: i32,
+ /// The source location points to the LHS of a binary expression, found
+ /// by taking this AST node index offset from the containing Decl AST node,
+ /// which points to a binary expression AST node. Next, nagivate to the LHS.
+ /// The Decl is determined contextually.
+ node_offset_bin_lhs: i32,
+ /// The source location points to the RHS of a binary expression, found
+ /// by taking this AST node index offset from the containing Decl AST node,
+ /// which points to a binary expression AST node. Next, nagivate to the RHS.
+ /// The Decl is determined contextually.
+ node_offset_bin_rhs: i32,
+ /// The source location points to the operand of a switch expression, found
+ /// by taking this AST node index offset from the containing Decl AST node,
+ /// which points to a switch expression AST node. Next, nagivate to the operand.
+ /// The Decl is determined contextually.
+ node_offset_switch_operand: i32,
+ /// The source location points to the else/`_` prong of a switch expression, found
+ /// by taking this AST node index offset from the containing Decl AST node,
+ /// which points to a switch expression AST node. Next, nagivate to the else/`_` prong.
+ /// The Decl is determined contextually.
+ node_offset_switch_special_prong: i32,
+ /// The source location points to all the ranges of a switch expression, found
+ /// by taking this AST node index offset from the containing Decl AST node,
+ /// which points to a switch expression AST node. Next, nagivate to any of the
+ /// range nodes. The error applies to all of them.
+ /// The Decl is determined contextually.
+ node_offset_switch_range: i32,
+ /// The source location points to the calling convention of a function type
+ /// expression, found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to a function type AST node. Next, nagivate to
+ /// the calling convention node.
+ /// The Decl is determined contextually.
+ node_offset_fn_type_cc: i32,
+ /// The source location points to the return type of a function type
+ /// expression, found by taking this AST node index offset from the containing
+ /// Decl AST node, which points to a function type AST node. Next, nagivate to
+ /// the return type node.
+ /// The Decl is determined contextually.
+ node_offset_fn_type_ret_ty: i32,
+
+ /// Upgrade to a `SrcLoc` based on the `Decl` or file in the provided scope.
+ pub fn toSrcLoc(lazy: LazySrcLoc, scope: *Scope) SrcLoc {
+ return switch (lazy) {
+ .unneeded,
+ .byte_abs,
+ .token_abs,
+ .node_abs,
+ => .{
+ .container = .{ .file_scope = scope.getFileScope() },
+ .lazy = lazy,
+ },
+
+ .byte_offset,
+ .token_offset,
+ .node_offset,
+ .node_offset_var_decl_ty,
+ .node_offset_for_cond,
+ .node_offset_builtin_call_arg0,
+ .node_offset_builtin_call_arg1,
+ .node_offset_array_access_index,
+ .node_offset_slice_sentinel,
+ .node_offset_call_func,
+ .node_offset_field_name,
+ .node_offset_deref_ptr,
+ .node_offset_asm_source,
+ .node_offset_asm_ret_ty,
+ .node_offset_if_cond,
+ .node_offset_bin_op,
+ .node_offset_bin_lhs,
+ .node_offset_bin_rhs,
+ .node_offset_switch_operand,
+ .node_offset_switch_special_prong,
+ .node_offset_switch_range,
+ .node_offset_fn_type_cc,
+ .node_offset_fn_type_ret_ty,
+ => .{
+ .container = .{ .decl = scope.srcDecl().? },
+ .lazy = lazy,
+ },
+ };
+ }
+
+ /// Upgrade to a `SrcLoc` based on the `Decl` provided.
+ pub fn toSrcLocWithDecl(lazy: LazySrcLoc, decl: *Decl) SrcLoc {
+ return switch (lazy) {
+ .unneeded,
+ .byte_abs,
+ .token_abs,
+ .node_abs,
+ => .{
+ .container = .{ .file_scope = decl.getFileScope() },
+ .lazy = lazy,
+ },
+
+ .byte_offset,
+ .token_offset,
+ .node_offset,
+ .node_offset_var_decl_ty,
+ .node_offset_for_cond,
+ .node_offset_builtin_call_arg0,
+ .node_offset_builtin_call_arg1,
+ .node_offset_array_access_index,
+ .node_offset_slice_sentinel,
+ .node_offset_call_func,
+ .node_offset_field_name,
+ .node_offset_deref_ptr,
+ .node_offset_asm_source,
+ .node_offset_asm_ret_ty,
+ .node_offset_if_cond,
+ .node_offset_bin_op,
+ .node_offset_bin_lhs,
+ .node_offset_bin_rhs,
+ .node_offset_switch_operand,
+ .node_offset_switch_special_prong,
+ .node_offset_switch_range,
+ .node_offset_fn_type_cc,
+ .node_offset_fn_type_ret_ty,
+ => .{
+ .container = .{ .decl = decl },
+ .lazy = lazy,
+ },
+ };
+ }
+};
+
+pub const InnerError = error{ OutOfMemory, AnalysisFail };
+
+pub fn deinit(mod: *Module) void {
+ const gpa = mod.gpa;
+
+ mod.compile_log_text.deinit(gpa);
+
+ mod.zig_cache_artifact_directory.handle.close();
+
+ mod.deletion_set.deinit(gpa);
+
+ for (mod.decl_table.items()) |entry| {
+ entry.value.destroy(mod);
+ }
+ mod.decl_table.deinit(gpa);
+
+ for (mod.failed_decls.items()) |entry| {
+ entry.value.destroy(gpa);
+ }
+ mod.failed_decls.deinit(gpa);
+
+ for (mod.emit_h_failed_decls.items()) |entry| {
+ entry.value.destroy(gpa);
+ }
+ mod.emit_h_failed_decls.deinit(gpa);
+
+ for (mod.failed_files.items()) |entry| {
+ entry.value.destroy(gpa);
+ }
+ mod.failed_files.deinit(gpa);
+
+ for (mod.failed_exports.items()) |entry| {
+ entry.value.destroy(gpa);
+ }
+ mod.failed_exports.deinit(gpa);
+
+ mod.compile_log_decls.deinit(gpa);
+
+ for (mod.decl_exports.items()) |entry| {
+ const export_list = entry.value;
+ gpa.free(export_list);
+ }
+ mod.decl_exports.deinit(gpa);
+
+ for (mod.export_owners.items()) |entry| {
+ freeExportList(gpa, entry.value);
+ }
+ mod.export_owners.deinit(gpa);
+
+ mod.symbol_exports.deinit(gpa);
+ mod.root_scope.destroy(gpa);
+
+ var it = mod.global_error_set.iterator();
+ while (it.next()) |entry| {
+ gpa.free(entry.key);
+ }
+ mod.global_error_set.deinit(gpa);
+
+ mod.error_name_list.deinit(gpa);
+
+ for (mod.import_table.items()) |entry| {
entry.value.destroy(gpa);
}
- self.import_table.deinit(gpa);
+ mod.import_table.deinit(gpa);
}
fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
@@ -1102,42 +2392,51 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
// A comptime decl does not store any value so we can just deinit this arena after analysis is done.
var analysis_arena = std.heap.ArenaAllocator.init(mod.gpa);
defer analysis_arena.deinit();
- var gen_scope: Scope.GenZIR = .{
- .decl = decl,
- .arena = &analysis_arena.allocator,
- .parent = &decl.container.base,
- .force_comptime = true,
- };
- defer gen_scope.instructions.deinit(mod.gpa);
- const block_expr = node_datas[decl_node].lhs;
- _ = try astgen.comptimeExpr(mod, &gen_scope.base, .none, block_expr);
- if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
- zir.dumpZir(mod.gpa, "comptime_block", decl.name, gen_scope.instructions.items) catch {};
- }
+ var code: zir.Code = blk: {
+ var astgen = try AstGen.init(mod, decl, &analysis_arena.allocator);
+ defer astgen.deinit();
+
+ var gen_scope: Scope.GenZir = .{
+ .force_comptime = true,
+ .parent = &decl.container.base,
+ .astgen = &astgen,
+ };
+ defer gen_scope.instructions.deinit(mod.gpa);
- var inst_table = Scope.Block.InstTable.init(mod.gpa);
- defer inst_table.deinit();
+ const block_expr = node_datas[decl_node].lhs;
+ _ = try AstGen.comptimeExpr(&gen_scope, &gen_scope.base, .none, block_expr);
- var branch_quota: u32 = default_eval_branch_quota;
+ const code = try gen_scope.finish();
+ if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
+ code.dump(mod.gpa, "comptime_block", &gen_scope.base, 0) catch {};
+ }
+ break :blk code;
+ };
+ defer code.deinit(mod.gpa);
+ var sema: Sema = .{
+ .mod = mod,
+ .gpa = mod.gpa,
+ .arena = &analysis_arena.allocator,
+ .code = code,
+ .inst_map = try analysis_arena.allocator.alloc(*ir.Inst, code.instructions.len),
+ .owner_decl = decl,
+ .func = null,
+ .owner_func = null,
+ .param_inst_list = &.{},
+ };
var block_scope: Scope.Block = .{
.parent = null,
- .inst_table = &inst_table,
- .func = null,
- .owner_decl = decl,
+ .sema = &sema,
.src_decl = decl,
.instructions = .{},
- .arena = &analysis_arena.allocator,
.inlining = null,
.is_comptime = true,
- .branch_quota = &branch_quota,
};
defer block_scope.instructions.deinit(mod.gpa);
- _ = try zir_sema.analyzeBody(mod, &block_scope, .{
- .instructions = gen_scope.instructions.items,
- });
+ _ = try sema.root(&block_scope);
decl.analysis = .complete;
decl.generation = mod.generation;
@@ -1160,7 +2459,6 @@ fn astgenAndSemaFn(
decl.analysis = .in_progress;
- const token_starts = tree.tokens.items(.start);
const token_tags = tree.tokens.items(.tag);
// This arena allocator's memory is discarded at the end of this function. It is used
@@ -1168,11 +2466,14 @@ fn astgenAndSemaFn(
// to complete the Decl analysis.
var fn_type_scope_arena = std.heap.ArenaAllocator.init(mod.gpa);
defer fn_type_scope_arena.deinit();
- var fn_type_scope: Scope.GenZIR = .{
- .decl = decl,
- .arena = &fn_type_scope_arena.allocator,
- .parent = &decl.container.base,
+
+ var fn_type_astgen = try AstGen.init(mod, decl, &fn_type_scope_arena.allocator);
+ defer fn_type_astgen.deinit();
+
+ var fn_type_scope: Scope.GenZir = .{
.force_comptime = true,
+ .parent = &decl.container.base,
+ .astgen = &fn_type_astgen,
};
defer fn_type_scope.instructions.deinit(mod.gpa);
@@ -1189,13 +2490,7 @@ fn astgenAndSemaFn(
}
break :blk count;
};
- const param_types = try fn_type_scope.arena.alloc(*zir.Inst, param_count);
- const fn_src = token_starts[fn_proto.ast.fn_token];
- const type_type = try astgen.addZIRInstConst(mod, &fn_type_scope.base, fn_src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.type_type),
- });
- const type_type_rl: astgen.ResultLoc = .{ .ty = type_type };
+ const param_types = try fn_type_scope_arena.allocator.alloc(zir.Inst.Ref, param_count);
var is_var_args = false;
{
@@ -1220,7 +2515,7 @@ fn astgenAndSemaFn(
const param_type_node = param.type_expr;
assert(param_type_node != 0);
param_types[param_type_i] =
- try astgen.expr(mod, &fn_type_scope.base, type_type_rl, param_type_node);
+ try AstGen.expr(&fn_type_scope, &fn_type_scope.base, .{ .ty = .type_type }, param_type_node);
}
assert(param_type_i == param_count);
}
@@ -1289,10 +2584,10 @@ fn astgenAndSemaFn(
if (token_tags[maybe_bang] == .bang) {
return mod.failTok(&fn_type_scope.base, maybe_bang, "TODO implement inferred error sets", .{});
}
- const return_type_inst = try astgen.expr(
- mod,
+ const return_type_inst = try AstGen.expr(
+ &fn_type_scope,
&fn_type_scope.base,
- type_type_rl,
+ .{ .ty = .type_type },
fn_proto.ast.return_type,
);
@@ -1301,73 +2596,72 @@ fn astgenAndSemaFn(
else
false;
- const cc_inst = if (fn_proto.ast.callconv_expr != 0) cc: {
+ const cc: zir.Inst.Ref = if (fn_proto.ast.callconv_expr != 0)
// TODO instead of enum literal type, this needs to be the
// std.builtin.CallingConvention enum. We need to implement importing other files
// and enums in order to fix this.
- const src = token_starts[tree.firstToken(fn_proto.ast.callconv_expr)];
- const enum_lit_ty = try astgen.addZIRInstConst(mod, &fn_type_scope.base, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.enum_literal_type),
- });
- break :cc try astgen.comptimeExpr(mod, &fn_type_scope.base, .{
- .ty = enum_lit_ty,
- }, fn_proto.ast.callconv_expr);
- } else if (is_extern) cc: {
- // note: https://github.com/ziglang/zig/issues/5269
- const src = token_starts[fn_proto.extern_export_token.?];
- break :cc try astgen.addZIRInst(mod, &fn_type_scope.base, src, zir.Inst.EnumLiteral, .{ .name = "C" }, .{});
- } else null;
-
- const fn_type_inst = if (cc_inst) |cc| fn_type: {
- var fn_type = try astgen.addZirInstTag(mod, &fn_type_scope.base, fn_src, .fn_type_cc, .{
- .return_type = return_type_inst,
+ try AstGen.comptimeExpr(
+ &fn_type_scope,
+ &fn_type_scope.base,
+ .{ .ty = .enum_literal_type },
+ fn_proto.ast.callconv_expr,
+ )
+ else if (is_extern) // note: https://github.com/ziglang/zig/issues/5269
+ try fn_type_scope.addSmallStr(.enum_literal_small, "C")
+ else
+ .none;
+
+ const fn_type_inst: zir.Inst.Ref = if (cc != .none) fn_type: {
+ const tag: zir.Inst.Tag = if (is_var_args) .fn_type_cc_var_args else .fn_type_cc;
+ break :fn_type try fn_type_scope.addFnTypeCc(tag, .{
+ .src_node = fn_proto.ast.proto_node,
+ .ret_ty = return_type_inst,
.param_types = param_types,
.cc = cc,
});
- if (is_var_args) fn_type.tag = .fn_type_cc_var_args;
- break :fn_type fn_type;
} else fn_type: {
- var fn_type = try astgen.addZirInstTag(mod, &fn_type_scope.base, fn_src, .fn_type, .{
- .return_type = return_type_inst,
+ const tag: zir.Inst.Tag = if (is_var_args) .fn_type_var_args else .fn_type;
+ break :fn_type try fn_type_scope.addFnType(tag, .{
+ .src_node = fn_proto.ast.proto_node,
+ .ret_ty = return_type_inst,
.param_types = param_types,
});
- if (is_var_args) fn_type.tag = .fn_type_var_args;
- break :fn_type fn_type;
};
-
- if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
- zir.dumpZir(mod.gpa, "fn_type", decl.name, fn_type_scope.instructions.items) catch {};
- }
+ _ = try fn_type_scope.addBreak(.break_inline, 0, fn_type_inst);
// We need the memory for the Type to go into the arena for the Decl
var decl_arena = std.heap.ArenaAllocator.init(mod.gpa);
errdefer decl_arena.deinit();
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
- var inst_table = Scope.Block.InstTable.init(mod.gpa);
- defer inst_table.deinit();
-
- var branch_quota: u32 = default_eval_branch_quota;
+ var fn_type_code = try fn_type_scope.finish();
+ defer fn_type_code.deinit(mod.gpa);
+ if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
+ fn_type_code.dump(mod.gpa, "fn_type", &fn_type_scope.base, 0) catch {};
+ }
+ var fn_type_sema: Sema = .{
+ .mod = mod,
+ .gpa = mod.gpa,
+ .arena = &decl_arena.allocator,
+ .code = fn_type_code,
+ .inst_map = try fn_type_scope_arena.allocator.alloc(*ir.Inst, fn_type_code.instructions.len),
+ .owner_decl = decl,
+ .func = null,
+ .owner_func = null,
+ .param_inst_list = &.{},
+ };
var block_scope: Scope.Block = .{
.parent = null,
- .inst_table = &inst_table,
- .func = null,
- .owner_decl = decl,
+ .sema = &fn_type_sema,
.src_decl = decl,
.instructions = .{},
- .arena = &decl_arena.allocator,
.inlining = null,
- .is_comptime = false,
- .branch_quota = &branch_quota,
+ .is_comptime = true,
};
defer block_scope.instructions.deinit(mod.gpa);
- const fn_type = try zir_sema.analyzeBodyValueAsType(mod, &block_scope, fn_type_inst, .{
- .instructions = fn_type_scope.instructions.items,
- });
-
+ const fn_type = try fn_type_sema.rootAsType(&block_scope);
if (body_node == 0) {
if (!is_extern) {
return mod.failNode(&block_scope.base, fn_proto.ast.fn_token, "non-extern function has no body", .{});
@@ -1409,63 +2703,69 @@ fn astgenAndSemaFn(
const new_func = try decl_arena.allocator.create(Fn);
const fn_payload = try decl_arena.allocator.create(Value.Payload.Function);
- const fn_zir: zir.Body = blk: {
+ const fn_zir: zir.Code = blk: {
// We put the ZIR inside the Decl arena.
- var gen_scope: Scope.GenZIR = .{
- .decl = decl,
- .arena = &decl_arena.allocator,
- .parent = &decl.container.base,
+ var astgen = try AstGen.init(mod, decl, &decl_arena.allocator);
+ astgen.ref_start_index = @intCast(u32, zir.Inst.Ref.typed_value_map.len + param_count);
+ defer astgen.deinit();
+
+ var gen_scope: Scope.GenZir = .{
.force_comptime = false,
+ .parent = &decl.container.base,
+ .astgen = &astgen,
};
defer gen_scope.instructions.deinit(mod.gpa);
- // We need an instruction for each parameter, and they must be first in the body.
- try gen_scope.instructions.resize(mod.gpa, param_count);
+ // Iterate over the parameters. We put the param names as the first N
+ // items inside `extra` so that debug info later can refer to the parameter names
+ // even while the respective source code is unloaded.
+ try astgen.extra.ensureCapacity(mod.gpa, param_count);
+
var params_scope = &gen_scope.base;
var i: usize = 0;
var it = fn_proto.iterate(tree);
while (it.next()) |param| : (i += 1) {
const name_token = param.name_token.?;
- const src = token_starts[name_token];
const param_name = try mod.identifierTokenString(&gen_scope.base, name_token);
- const arg = try decl_arena.allocator.create(zir.Inst.Arg);
- arg.* = .{
- .base = .{
- .tag = .arg,
- .src = src,
- },
- .positionals = .{
- .name = param_name,
- },
- .kw_args = .{},
- };
- gen_scope.instructions.items[i] = &arg.base;
const sub_scope = try decl_arena.allocator.create(Scope.LocalVal);
sub_scope.* = .{
.parent = params_scope,
.gen_zir = &gen_scope,
.name = param_name,
- .inst = &arg.base,
+ // Implicit const list first, then implicit arg list.
+ .inst = @intToEnum(zir.Inst.Ref, @intCast(u32, zir.Inst.Ref.typed_value_map.len + i)),
+ .src = decl.tokSrcLoc(name_token),
};
params_scope = &sub_scope.base;
+
+ // Additionally put the param name into `string_bytes` and reference it with
+ // `extra` so that we have access to the data in codegen, for debug info.
+ const str_index = @intCast(u32, astgen.string_bytes.items.len);
+ astgen.extra.appendAssumeCapacity(str_index);
+ const used_bytes = astgen.string_bytes.items.len;
+ try astgen.string_bytes.ensureCapacity(mod.gpa, used_bytes + param_name.len + 1);
+ astgen.string_bytes.appendSliceAssumeCapacity(param_name);
+ astgen.string_bytes.appendAssumeCapacity(0);
}
- _ = try astgen.expr(mod, params_scope, .none, body_node);
+ _ = try AstGen.expr(&gen_scope, params_scope, .none, body_node);
if (gen_scope.instructions.items.len == 0 or
- !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())
+ !astgen.instructions.items(.tag)[gen_scope.instructions.items.len - 1]
+ .isNoReturn())
{
- const src = token_starts[tree.lastToken(body_node)];
- _ = try astgen.addZIRNoOp(mod, &gen_scope.base, src, .return_void);
+ // astgen uses result location semantics to coerce return operands.
+ // Since we are adding the return instruction here, we must handle the coercion.
+ // We do this by using the `ret_coerce` instruction.
+ _ = try gen_scope.addUnTok(.ret_coerce, .void_value, tree.lastToken(body_node));
}
+ const code = try gen_scope.finish();
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
- zir.dumpZir(mod.gpa, "fn_body", decl.name, gen_scope.instructions.items) catch {};
+ code.dump(mod.gpa, "fn_body", &gen_scope.base, param_count) catch {};
}
- break :blk .{
- .instructions = try gen_scope.arena.dupe(*zir.Inst, gen_scope.instructions.items),
- };
+ break :blk code;
};
const is_inline = fn_type.fnCallingConvention() == .Inline;
@@ -1492,6 +2792,7 @@ fn astgenAndSemaFn(
if (tvm.typed_value.val.castTag(.function)) |payload| {
const prev_func = payload.data;
prev_is_inline = prev_func.state == .inline_only;
+ prev_func.deinit(mod.gpa);
}
tvm.deinit(mod.gpa);
@@ -1533,7 +2834,7 @@ fn astgenAndSemaFn(
.{},
);
}
- const export_src = token_starts[maybe_export_token];
+ const export_src = decl.tokSrcLoc(maybe_export_token);
const name = tree.tokenSlice(fn_proto.name_token.?); // TODO identifierTokenString
// The scope needs to have the decl in it.
try mod.analyzeExport(&block_scope.base, export_src, name, decl);
@@ -1552,8 +2853,8 @@ fn astgenAndSemaVarDecl(
defer tracy.end();
decl.analysis = .in_progress;
+ decl.is_pub = var_decl.visib_token != null;
- const token_starts = tree.tokens.items(.start);
const token_tags = tree.tokens.items(.tag);
// We need the memory for the Type to go into the arena for the Decl
@@ -1561,54 +2862,29 @@ fn astgenAndSemaVarDecl(
errdefer decl_arena.deinit();
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
- var decl_inst_table = Scope.Block.InstTable.init(mod.gpa);
- defer decl_inst_table.deinit();
+ // Used for simple error reporting.
+ var decl_scope: Scope.DeclRef = .{ .decl = decl };
- var branch_quota: u32 = default_eval_branch_quota;
-
- var block_scope: Scope.Block = .{
- .parent = null,
- .inst_table = &decl_inst_table,
- .func = null,
- .owner_decl = decl,
- .src_decl = decl,
- .instructions = .{},
- .arena = &decl_arena.allocator,
- .inlining = null,
- .is_comptime = true,
- .branch_quota = &branch_quota,
- };
- defer block_scope.instructions.deinit(mod.gpa);
-
- decl.is_pub = var_decl.visib_token != null;
const is_extern = blk: {
const maybe_extern_token = var_decl.extern_export_token orelse break :blk false;
- if (token_tags[maybe_extern_token] != .keyword_extern) break :blk false;
- if (var_decl.ast.init_node != 0) {
- return mod.failNode(
- &block_scope.base,
- var_decl.ast.init_node,
- "extern variables have no initializers",
- .{},
- );
- }
- break :blk true;
+ break :blk token_tags[maybe_extern_token] == .keyword_extern;
};
+
if (var_decl.lib_name) |lib_name| {
assert(is_extern);
- return mod.failTok(&block_scope.base, lib_name, "TODO implement function library name", .{});
+ return mod.failTok(&decl_scope.base, lib_name, "TODO implement function library name", .{});
}
const is_mutable = token_tags[var_decl.ast.mut_token] == .keyword_var;
const is_threadlocal = if (var_decl.threadlocal_token) |some| blk: {
if (!is_mutable) {
- return mod.failTok(&block_scope.base, some, "threadlocal variable cannot be constant", .{});
+ return mod.failTok(&decl_scope.base, some, "threadlocal variable cannot be constant", .{});
}
break :blk true;
} else false;
assert(var_decl.comptime_token == null);
if (var_decl.ast.align_node != 0) {
return mod.failNode(
- &block_scope.base,
+ &decl_scope.base,
var_decl.ast.align_node,
"TODO implement function align expression",
.{},
@@ -1616,7 +2892,7 @@ fn astgenAndSemaVarDecl(
}
if (var_decl.ast.section_node != 0) {
return mod.failNode(
- &block_scope.base,
+ &decl_scope.base,
var_decl.ast.section_node,
"TODO implement function section expression",
.{},
@@ -1624,103 +2900,136 @@ fn astgenAndSemaVarDecl(
}
const var_info: struct { ty: Type, val: ?Value } = if (var_decl.ast.init_node != 0) vi: {
+ if (is_extern) {
+ return mod.failNode(
+ &decl_scope.base,
+ var_decl.ast.init_node,
+ "extern variables have no initializers",
+ .{},
+ );
+ }
+
var gen_scope_arena = std.heap.ArenaAllocator.init(mod.gpa);
defer gen_scope_arena.deinit();
- var gen_scope: Scope.GenZIR = .{
- .decl = decl,
- .arena = &gen_scope_arena.allocator,
- .parent = &decl.container.base,
+
+ var astgen = try AstGen.init(mod, decl, &gen_scope_arena.allocator);
+ defer astgen.deinit();
+
+ var gen_scope: Scope.GenZir = .{
.force_comptime = true,
+ .parent = &decl.container.base,
+ .astgen = &astgen,
};
defer gen_scope.instructions.deinit(mod.gpa);
- const init_result_loc: astgen.ResultLoc = if (var_decl.ast.type_node != 0) rl: {
- const type_node = var_decl.ast.type_node;
- const src = token_starts[tree.firstToken(type_node)];
- const type_type = try astgen.addZIRInstConst(mod, &gen_scope.base, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.type_type),
- });
- const var_type = try astgen.expr(mod, &gen_scope.base, .{ .ty = type_type }, type_node);
- break :rl .{ .ty = var_type };
+ const init_result_loc: AstGen.ResultLoc = if (var_decl.ast.type_node != 0) .{
+ .ty = try AstGen.expr(&gen_scope, &gen_scope.base, .{ .ty = .type_type }, var_decl.ast.type_node),
} else .none;
- const init_inst = try astgen.comptimeExpr(
- mod,
+ const init_inst = try AstGen.comptimeExpr(
+ &gen_scope,
&gen_scope.base,
init_result_loc,
var_decl.ast.init_node,
);
+ _ = try gen_scope.addBreak(.break_inline, 0, init_inst);
+ var code = try gen_scope.finish();
+ defer code.deinit(mod.gpa);
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
- zir.dumpZir(mod.gpa, "var_init", decl.name, gen_scope.instructions.items) catch {};
+ code.dump(mod.gpa, "var_init", &gen_scope.base, 0) catch {};
}
- var var_inst_table = Scope.Block.InstTable.init(mod.gpa);
- defer var_inst_table.deinit();
-
- var branch_quota_vi: u32 = default_eval_branch_quota;
- var inner_block: Scope.Block = .{
- .parent = null,
- .inst_table = &var_inst_table,
- .func = null,
+ var sema: Sema = .{
+ .mod = mod,
+ .gpa = mod.gpa,
+ .arena = &gen_scope_arena.allocator,
+ .code = code,
+ .inst_map = try gen_scope_arena.allocator.alloc(*ir.Inst, code.instructions.len),
.owner_decl = decl,
+ .func = null,
+ .owner_func = null,
+ .param_inst_list = &.{},
+ };
+ var block_scope: Scope.Block = .{
+ .parent = null,
+ .sema = &sema,
.src_decl = decl,
.instructions = .{},
- .arena = &gen_scope_arena.allocator,
.inlining = null,
.is_comptime = true,
- .branch_quota = &branch_quota_vi,
};
- defer inner_block.instructions.deinit(mod.gpa);
- try zir_sema.analyzeBody(mod, &inner_block, .{
- .instructions = gen_scope.instructions.items,
- });
+ defer block_scope.instructions.deinit(mod.gpa);
+ const init_inst_zir_ref = try sema.rootAsRef(&block_scope);
// The result location guarantees the type coercion.
- const analyzed_init_inst = var_inst_table.get(init_inst).?;
+ const analyzed_init_inst = try sema.resolveInst(init_inst_zir_ref);
// The is_comptime in the Scope.Block guarantees the result is comptime-known.
const val = analyzed_init_inst.value().?;
- const ty = try analyzed_init_inst.ty.copy(block_scope.arena);
break :vi .{
- .ty = ty,
- .val = try val.copy(block_scope.arena),
+ .ty = try analyzed_init_inst.ty.copy(&decl_arena.allocator),
+ .val = try val.copy(&decl_arena.allocator),
};
} else if (!is_extern) {
return mod.failTok(
- &block_scope.base,
+ &decl_scope.base,
var_decl.ast.mut_token,
"variables must be initialized",
.{},
);
} else if (var_decl.ast.type_node != 0) vi: {
- const type_node = var_decl.ast.type_node;
- // Temporary arena for the zir instructions.
var type_scope_arena = std.heap.ArenaAllocator.init(mod.gpa);
defer type_scope_arena.deinit();
- var type_scope: Scope.GenZIR = .{
- .decl = decl,
- .arena = &type_scope_arena.allocator,
- .parent = &decl.container.base,
+
+ var astgen = try AstGen.init(mod, decl, &type_scope_arena.allocator);
+ defer astgen.deinit();
+
+ var type_scope: Scope.GenZir = .{
.force_comptime = true,
+ .parent = &decl.container.base,
+ .astgen = &astgen,
};
defer type_scope.instructions.deinit(mod.gpa);
- const var_type = try astgen.typeExpr(mod, &type_scope.base, type_node);
+ const var_type = try AstGen.typeExpr(&type_scope, &type_scope.base, var_decl.ast.type_node);
+ _ = try type_scope.addBreak(.break_inline, 0, var_type);
+
+ var code = try type_scope.finish();
+ defer code.deinit(mod.gpa);
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
- zir.dumpZir(mod.gpa, "var_type", decl.name, type_scope.instructions.items) catch {};
+ code.dump(mod.gpa, "var_type", &type_scope.base, 0) catch {};
}
- const ty = try zir_sema.analyzeBodyValueAsType(mod, &block_scope, var_type, .{
- .instructions = type_scope.instructions.items,
- });
+ var sema: Sema = .{
+ .mod = mod,
+ .gpa = mod.gpa,
+ .arena = &type_scope_arena.allocator,
+ .code = code,
+ .inst_map = try type_scope_arena.allocator.alloc(*ir.Inst, code.instructions.len),
+ .owner_decl = decl,
+ .func = null,
+ .owner_func = null,
+ .param_inst_list = &.{},
+ };
+ var block_scope: Scope.Block = .{
+ .parent = null,
+ .sema = &sema,
+ .src_decl = decl,
+ .instructions = .{},
+ .inlining = null,
+ .is_comptime = true,
+ };
+ defer block_scope.instructions.deinit(mod.gpa);
+
+ const ty = try sema.rootAsType(&block_scope);
+
break :vi .{
- .ty = ty,
+ .ty = try ty.copy(&decl_arena.allocator),
.val = null,
};
} else {
return mod.failTok(
- &block_scope.base,
+ &decl_scope.base,
var_decl.ast.mut_token,
"unable to infer variable type",
.{},
@@ -1729,7 +3038,7 @@ fn astgenAndSemaVarDecl(
if (is_mutable and !var_info.ty.isValidVarType(is_extern)) {
return mod.failTok(
- &block_scope.base,
+ &decl_scope.base,
var_decl.ast.mut_token,
"variable of type '{}' must be const",
.{var_info.ty},
@@ -1768,57 +3077,57 @@ fn astgenAndSemaVarDecl(
if (var_decl.extern_export_token) |maybe_export_token| {
if (token_tags[maybe_export_token] == .keyword_export) {
- const export_src = token_starts[maybe_export_token];
+ const export_src = decl.tokSrcLoc(maybe_export_token);
const name_token = var_decl.ast.mut_token + 1;
const name = tree.tokenSlice(name_token); // TODO identifierTokenString
// The scope needs to have the decl in it.
- try mod.analyzeExport(&block_scope.base, export_src, name, decl);
+ try mod.analyzeExport(&decl_scope.base, export_src, name, decl);
}
}
return type_changed;
}
-fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void {
- try depender.dependencies.ensureCapacity(self.gpa, depender.dependencies.items().len + 1);
- try dependee.dependants.ensureCapacity(self.gpa, dependee.dependants.items().len + 1);
+pub fn declareDeclDependency(mod: *Module, depender: *Decl, dependee: *Decl) !void {
+ try depender.dependencies.ensureCapacity(mod.gpa, depender.dependencies.items().len + 1);
+ try dependee.dependants.ensureCapacity(mod.gpa, dependee.dependants.items().len + 1);
depender.dependencies.putAssumeCapacity(dependee, {});
dependee.dependants.putAssumeCapacity(depender, {});
}
-pub fn getAstTree(self: *Module, root_scope: *Scope.File) !*const ast.Tree {
+pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree {
const tracy = trace(@src());
defer tracy.end();
switch (root_scope.status) {
.never_loaded, .unloaded_success => {
- try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1);
+ try mod.failed_files.ensureCapacity(mod.gpa, mod.failed_files.items().len + 1);
- const source = try root_scope.getSource(self);
+ const source = try root_scope.getSource(mod);
var keep_tree = false;
- root_scope.tree = try std.zig.parse(self.gpa, source);
- defer if (!keep_tree) root_scope.tree.deinit(self.gpa);
+ root_scope.tree = try std.zig.parse(mod.gpa, source);
+ defer if (!keep_tree) root_scope.tree.deinit(mod.gpa);
const tree = &root_scope.tree;
if (tree.errors.len != 0) {
const parse_err = tree.errors[0];
- var msg = std.ArrayList(u8).init(self.gpa);
+ var msg = std.ArrayList(u8).init(mod.gpa);
defer msg.deinit();
try tree.renderError(parse_err, msg.writer());
- const err_msg = try self.gpa.create(ErrorMsg);
+ const err_msg = try mod.gpa.create(ErrorMsg);
err_msg.* = .{
.src_loc = .{
- .file_scope = root_scope,
- .byte_offset = tree.tokens.items(.start)[parse_err.token],
+ .container = .{ .file_scope = root_scope },
+ .lazy = .{ .token_abs = parse_err.token },
},
.msg = msg.toOwnedSlice(),
};
- self.failed_files.putAssumeCapacityNoClobber(&root_scope.base, err_msg);
+ mod.failed_files.putAssumeCapacityNoClobber(&root_scope.base, err_msg);
root_scope.status = .unloaded_parse_failure;
return error.AnalysisFail;
}
@@ -2051,11 +3360,9 @@ fn semaContainerFn(
const tracy = trace(@src());
defer tracy.end();
- const token_starts = tree.tokens.items(.start);
- const token_tags = tree.tokens.items(.tag);
-
// We will create a Decl for it regardless of analysis status.
const name_tok = fn_proto.name_token orelse {
+ // This problem will go away with #1717.
@panic("TODO missing function name");
};
const name = tree.tokenSlice(name_tok); // TODO use identifierTokenString
@@ -2068,8 +3375,8 @@ fn semaContainerFn(
if (deleted_decls.swapRemove(decl) == null) {
decl.analysis = .sema_failure;
const msg = try ErrorMsg.create(mod.gpa, .{
- .file_scope = container_scope.file_scope,
- .byte_offset = token_starts[name_tok],
+ .container = .{ .file_scope = container_scope.file_scope },
+ .lazy = .{ .token_abs = name_tok },
}, "redefinition of '{s}'", .{decl.name});
errdefer msg.destroy(mod.gpa);
try mod.failed_decls.putNoClobber(mod.gpa, decl, msg);
@@ -2098,6 +3405,7 @@ fn semaContainerFn(
const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
container_scope.decls.putAssumeCapacity(new_decl, {});
if (fn_proto.extern_export_token) |maybe_export_token| {
+ const token_tags = tree.tokens.items(.tag);
if (token_tags[maybe_export_token] == .keyword_export) {
mod.comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
}
@@ -2117,11 +3425,7 @@ fn semaContainerVar(
const tracy = trace(@src());
defer tracy.end();
- const token_starts = tree.tokens.items(.start);
- const token_tags = tree.tokens.items(.tag);
-
const name_token = var_decl.ast.mut_token + 1;
- const name_src = token_starts[name_token];
const name = tree.tokenSlice(name_token); // TODO identifierTokenString
const name_hash = container_scope.fullyQualifiedNameHash(name);
const contents_hash = std.zig.hashSrc(tree.getNodeSource(decl_node));
@@ -2132,8 +3436,8 @@ fn semaContainerVar(
if (deleted_decls.swapRemove(decl) == null) {
decl.analysis = .sema_failure;
const err_msg = try ErrorMsg.create(mod.gpa, .{
- .file_scope = container_scope.file_scope,
- .byte_offset = name_src,
+ .container = .{ .file_scope = container_scope.file_scope },
+ .lazy = .{ .token_abs = name_token },
}, "redefinition of '{s}'", .{decl.name});
errdefer err_msg.destroy(mod.gpa);
try mod.failed_decls.putNoClobber(mod.gpa, decl, err_msg);
@@ -2145,6 +3449,7 @@ fn semaContainerVar(
const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
container_scope.decls.putAssumeCapacity(new_decl, {});
if (var_decl.extern_export_token) |maybe_export_token| {
+ const token_tags = tree.tokens.items(.tag);
if (token_tags[maybe_export_token] == .keyword_export) {
mod.comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
}
@@ -2167,11 +3472,11 @@ fn semaContainerField(
log.err("TODO: analyze container field", .{});
}
-pub fn deleteDecl(self: *Module, decl: *Decl) !void {
+pub fn deleteDecl(mod: *Module, decl: *Decl) !void {
const tracy = trace(@src());
defer tracy.end();
- try self.deletion_set.ensureCapacity(self.gpa, self.deletion_set.items.len + decl.dependencies.items().len);
+ try mod.deletion_set.ensureCapacity(mod.gpa, mod.deletion_set.items.len + decl.dependencies.items().len);
// Remove from the namespace it resides in. In the case of an anonymous Decl it will
// not be present in the set, and this does nothing.
@@ -2179,7 +3484,7 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void {
log.debug("deleting decl '{s}'", .{decl.name});
const name_hash = decl.fullyQualifiedNameHash();
- self.decl_table.removeAssertDiscard(name_hash);
+ mod.decl_table.removeAssertDiscard(name_hash);
// Remove itself from its dependencies, because we are about to destroy the decl pointer.
for (decl.dependencies.items()) |entry| {
const dep = entry.key;
@@ -2188,7 +3493,7 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void {
// We don't recursively perform a deletion here, because during the update,
// another reference to it may turn up.
dep.deletion_flag = true;
- self.deletion_set.appendAssumeCapacity(dep);
+ mod.deletion_set.appendAssumeCapacity(dep);
}
}
// Anything that depends on this deleted decl certainly needs to be re-analyzed.
@@ -2197,29 +3502,29 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void {
dep.removeDependency(decl);
if (dep.analysis != .outdated) {
// TODO Move this failure possibility to the top of the function.
- try self.markOutdatedDecl(dep);
+ try mod.markOutdatedDecl(dep);
}
}
- if (self.failed_decls.swapRemove(decl)) |entry| {
- entry.value.destroy(self.gpa);
+ if (mod.failed_decls.swapRemove(decl)) |entry| {
+ entry.value.destroy(mod.gpa);
}
- if (self.emit_h_failed_decls.swapRemove(decl)) |entry| {
- entry.value.destroy(self.gpa);
+ if (mod.emit_h_failed_decls.swapRemove(decl)) |entry| {
+ entry.value.destroy(mod.gpa);
}
- _ = self.compile_log_decls.swapRemove(decl);
- self.deleteDeclExports(decl);
- self.comp.bin_file.freeDecl(decl);
+ _ = mod.compile_log_decls.swapRemove(decl);
+ mod.deleteDeclExports(decl);
+ mod.comp.bin_file.freeDecl(decl);
- decl.destroy(self);
+ decl.destroy(mod);
}
/// Delete all the Export objects that are caused by this Decl. Re-analysis of
/// this Decl will cause them to be re-created (or not).
-fn deleteDeclExports(self: *Module, decl: *Decl) void {
- const kv = self.export_owners.swapRemove(decl) orelse return;
+fn deleteDeclExports(mod: *Module, decl: *Decl) void {
+ const kv = mod.export_owners.swapRemove(decl) orelse return;
for (kv.value) |exp| {
- if (self.decl_exports.getEntry(exp.exported_decl)) |decl_exports_kv| {
+ if (mod.decl_exports.getEntry(exp.exported_decl)) |decl_exports_kv| {
// Remove exports with owner_decl matching the regenerating decl.
const list = decl_exports_kv.value;
var i: usize = 0;
@@ -2232,73 +3537,101 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void {
i += 1;
}
}
- decl_exports_kv.value = self.gpa.shrink(list, new_len);
+ decl_exports_kv.value = mod.gpa.shrink(list, new_len);
if (new_len == 0) {
- self.decl_exports.removeAssertDiscard(exp.exported_decl);
+ mod.decl_exports.removeAssertDiscard(exp.exported_decl);
}
}
- if (self.comp.bin_file.cast(link.File.Elf)) |elf| {
+ if (mod.comp.bin_file.cast(link.File.Elf)) |elf| {
elf.deleteExport(exp.link.elf);
}
- if (self.comp.bin_file.cast(link.File.MachO)) |macho| {
+ if (mod.comp.bin_file.cast(link.File.MachO)) |macho| {
macho.deleteExport(exp.link.macho);
}
- if (self.failed_exports.swapRemove(exp)) |entry| {
- entry.value.destroy(self.gpa);
+ if (mod.failed_exports.swapRemove(exp)) |entry| {
+ entry.value.destroy(mod.gpa);
}
- _ = self.symbol_exports.swapRemove(exp.options.name);
- self.gpa.free(exp.options.name);
- self.gpa.destroy(exp);
+ _ = mod.symbol_exports.swapRemove(exp.options.name);
+ mod.gpa.free(exp.options.name);
+ mod.gpa.destroy(exp);
}
- self.gpa.free(kv.value);
+ mod.gpa.free(kv.value);
}
-pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
+pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) !void {
const tracy = trace(@src());
defer tracy.end();
// Use the Decl's arena for function memory.
- var arena = decl.typed_value.most_recent.arena.?.promote(self.gpa);
+ var arena = decl.typed_value.most_recent.arena.?.promote(mod.gpa);
defer decl.typed_value.most_recent.arena.?.* = arena.state;
- var inst_table = Scope.Block.InstTable.init(self.gpa);
- defer inst_table.deinit();
- var branch_quota: u32 = default_eval_branch_quota;
+
+ const fn_ty = decl.typed_value.most_recent.typed_value.ty;
+ const param_inst_list = try mod.gpa.alloc(*ir.Inst, fn_ty.fnParamLen());
+ defer mod.gpa.free(param_inst_list);
+
+ for (param_inst_list) |*param_inst, param_index| {
+ const param_type = fn_ty.fnParamType(param_index);
+ const name = func.zir.nullTerminatedString(func.zir.extra[param_index]);
+ const arg_inst = try arena.allocator.create(ir.Inst.Arg);
+ arg_inst.* = .{
+ .base = .{
+ .tag = .arg,
+ .ty = param_type,
+ .src = .unneeded,
+ },
+ .name = name,
+ };
+ param_inst.* = &arg_inst.base;
+ }
+
+ var sema: Sema = .{
+ .mod = mod,
+ .gpa = mod.gpa,
+ .arena = &arena.allocator,
+ .code = func.zir,
+ .inst_map = try mod.gpa.alloc(*ir.Inst, func.zir.instructions.len),
+ .owner_decl = decl,
+ .func = func,
+ .owner_func = func,
+ .param_inst_list = param_inst_list,
+ };
+ defer mod.gpa.free(sema.inst_map);
var inner_block: Scope.Block = .{
.parent = null,
- .inst_table = &inst_table,
- .func = func,
- .owner_decl = decl,
+ .sema = &sema,
.src_decl = decl,
.instructions = .{},
- .arena = &arena.allocator,
.inlining = null,
.is_comptime = false,
- .branch_quota = &branch_quota,
};
- defer inner_block.instructions.deinit(self.gpa);
+ defer inner_block.instructions.deinit(mod.gpa);
+
+ // TZIR currently requires the arg parameters to be the first N instructions
+ try inner_block.instructions.appendSlice(mod.gpa, param_inst_list);
func.state = .in_progress;
log.debug("set {s} to in_progress", .{decl.name});
- try zir_sema.analyzeBody(self, &inner_block, func.zir);
+ _ = try sema.root(&inner_block);
- const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items);
+ const instructions = try arena.allocator.dupe(*ir.Inst, inner_block.instructions.items);
func.state = .success;
func.body = .{ .instructions = instructions };
log.debug("set {s} to success", .{decl.name});
}
-fn markOutdatedDecl(self: *Module, decl: *Decl) !void {
+fn markOutdatedDecl(mod: *Module, decl: *Decl) !void {
log.debug("mark {s} outdated", .{decl.name});
- try self.comp.work_queue.writeItem(.{ .analyze_decl = decl });
- if (self.failed_decls.swapRemove(decl)) |entry| {
- entry.value.destroy(self.gpa);
+ try mod.comp.work_queue.writeItem(.{ .analyze_decl = decl });
+ if (mod.failed_decls.swapRemove(decl)) |entry| {
+ entry.value.destroy(mod.gpa);
}
- if (self.emit_h_failed_decls.swapRemove(decl)) |entry| {
- entry.value.destroy(self.gpa);
+ if (mod.emit_h_failed_decls.swapRemove(decl)) |entry| {
+ entry.value.destroy(mod.gpa);
}
- _ = self.compile_log_decls.swapRemove(decl);
+ _ = mod.compile_log_decls.swapRemove(decl);
decl.analysis = .outdated;
}
@@ -2349,65 +3682,39 @@ fn allocateNewDecl(
}
fn createNewDecl(
- self: *Module,
+ mod: *Module,
scope: *Scope,
decl_name: []const u8,
src_index: usize,
name_hash: Scope.NameHash,
contents_hash: std.zig.SrcHash,
) !*Decl {
- try self.decl_table.ensureCapacity(self.gpa, self.decl_table.items().len + 1);
- const new_decl = try self.allocateNewDecl(scope, src_index, contents_hash);
- errdefer self.gpa.destroy(new_decl);
- new_decl.name = try mem.dupeZ(self.gpa, u8, decl_name);
- self.decl_table.putAssumeCapacityNoClobber(name_hash, new_decl);
+ try mod.decl_table.ensureCapacity(mod.gpa, mod.decl_table.items().len + 1);
+ const new_decl = try mod.allocateNewDecl(scope, src_index, contents_hash);
+ errdefer mod.gpa.destroy(new_decl);
+ new_decl.name = try mem.dupeZ(mod.gpa, u8, decl_name);
+ mod.decl_table.putAssumeCapacityNoClobber(name_hash, new_decl);
return new_decl;
}
/// Get error value for error tag `name`.
-pub fn getErrorValue(self: *Module, name: []const u8) !std.StringHashMapUnmanaged(u16).Entry {
- const gop = try self.global_error_set.getOrPut(self.gpa, name);
+pub fn getErrorValue(mod: *Module, name: []const u8) !std.StringHashMapUnmanaged(ErrorInt).Entry {
+ const gop = try mod.global_error_set.getOrPut(mod.gpa, name);
if (gop.found_existing)
return gop.entry.*;
- errdefer self.global_error_set.removeAssertDiscard(name);
- gop.entry.key = try self.gpa.dupe(u8, name);
- gop.entry.value = @intCast(u16, self.global_error_set.count() - 1);
+ errdefer mod.global_error_set.removeAssertDiscard(name);
+ try mod.error_name_list.ensureCapacity(mod.gpa, mod.error_name_list.items.len + 1);
+ gop.entry.key = try mod.gpa.dupe(u8, name);
+ gop.entry.value = @intCast(ErrorInt, mod.error_name_list.items.len);
+ mod.error_name_list.appendAssumeCapacity(gop.entry.key);
return gop.entry.*;
}
-pub fn requireFunctionBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
- return scope.cast(Scope.Block) orelse
- return self.fail(scope, src, "instruction illegal outside function body", .{});
-}
-
-pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
- const block = try self.requireFunctionBlock(scope, src);
- if (block.is_comptime) {
- return self.fail(scope, src, "unable to resolve comptime value", .{});
- }
- return block;
-}
-
-pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value {
- return (try self.resolveDefinedValue(scope, base)) orelse
- return self.fail(scope, base.src, "unable to resolve comptime value", .{});
-}
-
-pub fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value {
- if (base.value()) |val| {
- if (val.isUndef()) {
- return self.fail(scope, base.src, "use of undefined value here causes undefined behavior", .{});
- }
- return val;
- }
- return null;
-}
-
pub fn analyzeExport(
mod: *Module,
scope: *Scope,
- src: usize,
+ src: LazySrcLoc,
borrowed_symbol_name: []const u8,
exported_decl: *Decl,
) !void {
@@ -2496,178 +3803,11 @@ pub fn analyzeExport(
},
};
}
-
-pub fn addNoOp(
- self: *Module,
- block: *Scope.Block,
- src: usize,
- ty: Type,
- comptime tag: Inst.Tag,
-) !*Inst {
- const inst = try block.arena.create(tag.Type());
- inst.* = .{
- .base = .{
- .tag = tag,
- .ty = ty,
- .src = src,
- },
- };
- try block.instructions.append(self.gpa, &inst.base);
- return &inst.base;
-}
-
-pub fn addUnOp(
- self: *Module,
- block: *Scope.Block,
- src: usize,
- ty: Type,
- tag: Inst.Tag,
- operand: *Inst,
-) !*Inst {
- const inst = try block.arena.create(Inst.UnOp);
- inst.* = .{
- .base = .{
- .tag = tag,
- .ty = ty,
- .src = src,
- },
- .operand = operand,
- };
- try block.instructions.append(self.gpa, &inst.base);
- return &inst.base;
-}
-
-pub fn addBinOp(
- self: *Module,
- block: *Scope.Block,
- src: usize,
- ty: Type,
- tag: Inst.Tag,
- lhs: *Inst,
- rhs: *Inst,
-) !*Inst {
- const inst = try block.arena.create(Inst.BinOp);
- inst.* = .{
- .base = .{
- .tag = tag,
- .ty = ty,
- .src = src,
- },
- .lhs = lhs,
- .rhs = rhs,
- };
- try block.instructions.append(self.gpa, &inst.base);
- return &inst.base;
-}
-
-pub fn addArg(self: *Module, block: *Scope.Block, src: usize, ty: Type, name: [*:0]const u8) !*Inst {
- const inst = try block.arena.create(Inst.Arg);
- inst.* = .{
- .base = .{
- .tag = .arg,
- .ty = ty,
- .src = src,
- },
- .name = name,
- };
- try block.instructions.append(self.gpa, &inst.base);
- return &inst.base;
-}
-
-pub fn addBr(
- self: *Module,
- scope_block: *Scope.Block,
- src: usize,
- target_block: *Inst.Block,
- operand: *Inst,
-) !*Inst.Br {
- const inst = try scope_block.arena.create(Inst.Br);
- inst.* = .{
- .base = .{
- .tag = .br,
- .ty = Type.initTag(.noreturn),
- .src = src,
- },
- .operand = operand,
- .block = target_block,
- };
- try scope_block.instructions.append(self.gpa, &inst.base);
- return inst;
-}
-
-pub fn addCondBr(
- self: *Module,
- block: *Scope.Block,
- src: usize,
- condition: *Inst,
- then_body: ir.Body,
- else_body: ir.Body,
-) !*Inst {
- const inst = try block.arena.create(Inst.CondBr);
- inst.* = .{
- .base = .{
- .tag = .condbr,
- .ty = Type.initTag(.noreturn),
- .src = src,
- },
- .condition = condition,
- .then_body = then_body,
- .else_body = else_body,
- };
- try block.instructions.append(self.gpa, &inst.base);
- return &inst.base;
-}
-
-pub fn addCall(
- self: *Module,
- block: *Scope.Block,
- src: usize,
- ty: Type,
- func: *Inst,
- args: []const *Inst,
-) !*Inst {
- const inst = try block.arena.create(Inst.Call);
- inst.* = .{
- .base = .{
- .tag = .call,
- .ty = ty,
- .src = src,
- },
- .func = func,
- .args = args,
- };
- try block.instructions.append(self.gpa, &inst.base);
- return &inst.base;
-}
-
-pub fn addSwitchBr(
- self: *Module,
- block: *Scope.Block,
- src: usize,
- target: *Inst,
- cases: []Inst.SwitchBr.Case,
- else_body: ir.Body,
-) !*Inst {
- const inst = try block.arena.create(Inst.SwitchBr);
- inst.* = .{
- .base = .{
- .tag = .switchbr,
- .ty = Type.initTag(.noreturn),
- .src = src,
- },
- .target = target,
- .cases = cases,
- .else_body = else_body,
- };
- try block.instructions.append(self.gpa, &inst.base);
- return &inst.base;
-}
-
-pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst {
- const const_inst = try scope.arena().create(Inst.Constant);
+pub fn constInst(mod: *Module, arena: *Allocator, src: LazySrcLoc, typed_value: TypedValue) !*ir.Inst {
+ const const_inst = try arena.create(ir.Inst.Constant);
const_inst.* = .{
.base = .{
- .tag = Inst.Constant.base_tag,
+ .tag = ir.Inst.Constant.base_tag,
.ty = typed_value.ty,
.src = src,
},
@@ -2676,94 +3816,94 @@ pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedVal
return &const_inst.base;
}
-pub fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst {
- return self.constInst(scope, src, .{
+pub fn constType(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type) !*ir.Inst {
+ return mod.constInst(arena, src, .{
.ty = Type.initTag(.type),
- .val = try ty.toValue(scope.arena()),
+ .val = try ty.toValue(arena),
});
}
-pub fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst {
- return self.constInst(scope, src, .{
+pub fn constVoid(mod: *Module, arena: *Allocator, src: LazySrcLoc) !*ir.Inst {
+ return mod.constInst(arena, src, .{
.ty = Type.initTag(.void),
.val = Value.initTag(.void_value),
});
}
-pub fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst {
- return self.constInst(scope, src, .{
+pub fn constNoReturn(mod: *Module, arena: *Allocator, src: LazySrcLoc) !*ir.Inst {
+ return mod.constInst(arena, src, .{
.ty = Type.initTag(.noreturn),
.val = Value.initTag(.unreachable_value),
});
}
-pub fn constUndef(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst {
- return self.constInst(scope, src, .{
+pub fn constUndef(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type) !*ir.Inst {
+ return mod.constInst(arena, src, .{
.ty = ty,
.val = Value.initTag(.undef),
});
}
-pub fn constBool(self: *Module, scope: *Scope, src: usize, v: bool) !*Inst {
- return self.constInst(scope, src, .{
+pub fn constBool(mod: *Module, arena: *Allocator, src: LazySrcLoc, v: bool) !*ir.Inst {
+ return mod.constInst(arena, src, .{
.ty = Type.initTag(.bool),
.val = ([2]Value{ Value.initTag(.bool_false), Value.initTag(.bool_true) })[@boolToInt(v)],
});
}
-pub fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64) !*Inst {
- return self.constInst(scope, src, .{
+pub fn constIntUnsigned(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type, int: u64) !*ir.Inst {
+ return mod.constInst(arena, src, .{
.ty = ty,
- .val = try Value.Tag.int_u64.create(scope.arena(), int),
+ .val = try Value.Tag.int_u64.create(arena, int),
});
}
-pub fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) !*Inst {
- return self.constInst(scope, src, .{
+pub fn constIntSigned(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type, int: i64) !*ir.Inst {
+ return mod.constInst(arena, src, .{
.ty = ty,
- .val = try Value.Tag.int_i64.create(scope.arena(), int),
+ .val = try Value.Tag.int_i64.create(arena, int),
});
}
-pub fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst {
+pub fn constIntBig(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type, big_int: BigIntConst) !*ir.Inst {
if (big_int.positive) {
if (big_int.to(u64)) |x| {
- return self.constIntUnsigned(scope, src, ty, x);
+ return mod.constIntUnsigned(arena, src, ty, x);
} else |err| switch (err) {
error.NegativeIntoUnsigned => unreachable,
error.TargetTooSmall => {}, // handled below
}
- return self.constInst(scope, src, .{
+ return mod.constInst(arena, src, .{
.ty = ty,
- .val = try Value.Tag.int_big_positive.create(scope.arena(), big_int.limbs),
+ .val = try Value.Tag.int_big_positive.create(arena, big_int.limbs),
});
} else {
if (big_int.to(i64)) |x| {
- return self.constIntSigned(scope, src, ty, x);
+ return mod.constIntSigned(arena, src, ty, x);
} else |err| switch (err) {
error.NegativeIntoUnsigned => unreachable,
error.TargetTooSmall => {}, // handled below
}
- return self.constInst(scope, src, .{
+ return mod.constInst(arena, src, .{
.ty = ty,
- .val = try Value.Tag.int_big_negative.create(scope.arena(), big_int.limbs),
+ .val = try Value.Tag.int_big_negative.create(arena, big_int.limbs),
});
}
}
pub fn createAnonymousDecl(
- self: *Module,
+ mod: *Module,
scope: *Scope,
decl_arena: *std.heap.ArenaAllocator,
typed_value: TypedValue,
) !*Decl {
- const name_index = self.getNextAnonNameIndex();
+ const name_index = mod.getNextAnonNameIndex();
const scope_decl = scope.ownerDecl().?;
- const name = try std.fmt.allocPrint(self.gpa, "{s}__anon_{d}", .{ scope_decl.name, name_index });
- defer self.gpa.free(name);
+ const name = try std.fmt.allocPrint(mod.gpa, "{s}__anon_{d}", .{ scope_decl.name, name_index });
+ defer mod.gpa.free(name);
const name_hash = scope.namespace().fullyQualifiedNameHash(name);
const src_hash: std.zig.SrcHash = undefined;
- const new_decl = try self.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash);
+ const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash);
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
decl_arena_state.* = decl_arena.state;
@@ -2774,32 +3914,32 @@ pub fn createAnonymousDecl(
},
};
new_decl.analysis = .complete;
- new_decl.generation = self.generation;
+ new_decl.generation = mod.generation;
- // TODO: This generates the Decl into the machine code file if it is of a type that is non-zero size.
- // We should be able to further improve the compiler to not omit Decls which are only referenced at
- // compile-time and not runtime.
+ // TODO: This generates the Decl into the machine code file if it is of a
+ // type that is non-zero size. We should be able to further improve the
+ // compiler to omit Decls which are only referenced at compile-time and not runtime.
if (typed_value.ty.hasCodeGenBits()) {
- try self.comp.bin_file.allocateDeclIndexes(new_decl);
- try self.comp.work_queue.writeItem(.{ .codegen_decl = new_decl });
+ try mod.comp.bin_file.allocateDeclIndexes(new_decl);
+ try mod.comp.work_queue.writeItem(.{ .codegen_decl = new_decl });
}
return new_decl;
}
pub fn createContainerDecl(
- self: *Module,
+ mod: *Module,
scope: *Scope,
base_token: std.zig.ast.TokenIndex,
decl_arena: *std.heap.ArenaAllocator,
typed_value: TypedValue,
) !*Decl {
const scope_decl = scope.ownerDecl().?;
- const name = try self.getAnonTypeName(scope, base_token);
- defer self.gpa.free(name);
+ const name = try mod.getAnonTypeName(scope, base_token);
+ defer mod.gpa.free(name);
const name_hash = scope.namespace().fullyQualifiedNameHash(name);
const src_hash: std.zig.SrcHash = undefined;
- const new_decl = try self.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash);
+ const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash);
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
decl_arena_state.* = decl_arena.state;
@@ -2810,12 +3950,12 @@ pub fn createContainerDecl(
},
};
new_decl.analysis = .complete;
- new_decl.generation = self.generation;
+ new_decl.generation = mod.generation;
return new_decl;
}
-fn getAnonTypeName(self: *Module, scope: *Scope, base_token: std.zig.ast.TokenIndex) ![]u8 {
+fn getAnonTypeName(mod: *Module, scope: *Scope, base_token: std.zig.ast.TokenIndex) ![]u8 {
// TODO add namespaces, generic function signatrues
const tree = scope.tree();
const token_tags = tree.tokens.items(.tag);
@@ -2827,775 +3967,39 @@ fn getAnonTypeName(self: *Module, scope: *Scope, base_token: std.zig.ast.TokenIn
else => unreachable,
};
const loc = tree.tokenLocation(0, base_token);
- return std.fmt.allocPrint(self.gpa, "{s}:{d}:{d}", .{ base_name, loc.line, loc.column });
+ return std.fmt.allocPrint(mod.gpa, "{s}:{d}:{d}", .{ base_name, loc.line, loc.column });
}
-fn getNextAnonNameIndex(self: *Module) usize {
- return @atomicRmw(usize, &self.next_anon_name_index, .Add, 1, .Monotonic);
+fn getNextAnonNameIndex(mod: *Module) usize {
+ return @atomicRmw(usize, &mod.next_anon_name_index, .Add, 1, .Monotonic);
}
-pub fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*Decl {
+pub fn lookupDeclName(mod: *Module, scope: *Scope, ident_name: []const u8) ?*Decl {
const namespace = scope.namespace();
const name_hash = namespace.fullyQualifiedNameHash(ident_name);
- return self.decl_table.get(name_hash);
-}
-
-pub fn analyzeDeclVal(mod: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst {
- const decl_ref = try mod.analyzeDeclRef(scope, src, decl);
- return mod.analyzeDeref(scope, src, decl_ref, src);
+ return mod.decl_table.get(name_hash);
}
-pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst {
- const scope_decl = scope.ownerDecl().?;
- try self.declareDeclDependency(scope_decl, decl);
- self.ensureDeclAnalyzed(decl) catch |err| {
- if (scope.cast(Scope.Block)) |block| {
- if (block.func) |func| {
- func.state = .dependency_failure;
- } else {
- block.owner_decl.analysis = .dependency_failure;
- }
- } else {
- scope_decl.analysis = .dependency_failure;
- }
- return err;
- };
-
- const decl_tv = try decl.typedValue();
- if (decl_tv.val.tag() == .variable) {
- return self.analyzeVarRef(scope, src, decl_tv);
- }
- return self.constInst(scope, src, .{
- .ty = try self.simplePtrType(scope, src, decl_tv.ty, false, .One),
- .val = try Value.Tag.decl_ref.create(scope.arena(), decl),
- });
-}
-
-fn analyzeVarRef(self: *Module, scope: *Scope, src: usize, tv: TypedValue) InnerError!*Inst {
- const variable = tv.val.castTag(.variable).?.data;
-
- const ty = try self.simplePtrType(scope, src, tv.ty, variable.is_mutable, .One);
- if (!variable.is_mutable and !variable.is_extern) {
- return self.constInst(scope, src, .{
- .ty = ty,
- .val = try Value.Tag.ref_val.create(scope.arena(), variable.init),
- });
- }
-
- const b = try self.requireRuntimeBlock(scope, src);
- const inst = try b.arena.create(Inst.VarPtr);
- inst.* = .{
- .base = .{
- .tag = .varptr,
- .ty = ty,
- .src = src,
- },
- .variable = variable,
- };
- try b.instructions.append(self.gpa, &inst.base);
- return &inst.base;
-}
-
-pub fn analyzeRef(mod: *Module, scope: *Scope, src: usize, operand: *Inst) InnerError!*Inst {
- const ptr_type = try mod.simplePtrType(scope, src, operand.ty, false, .One);
-
- if (operand.value()) |val| {
- return mod.constInst(scope, src, .{
- .ty = ptr_type,
- .val = try Value.Tag.ref_val.create(scope.arena(), val),
- });
- }
-
- const b = try mod.requireRuntimeBlock(scope, src);
- return mod.addUnOp(b, src, ptr_type, .ref, operand);
-}
-
-pub fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: usize) InnerError!*Inst {
- const elem_ty = switch (ptr.ty.zigTypeTag()) {
- .Pointer => ptr.ty.elemType(),
- else => return self.fail(scope, ptr_src, "expected pointer, found '{}'", .{ptr.ty}),
- };
- if (ptr.value()) |val| {
- return self.constInst(scope, src, .{
- .ty = elem_ty,
- .val = try val.pointerDeref(scope.arena()),
- });
- }
-
- const b = try self.requireRuntimeBlock(scope, src);
- return self.addUnOp(b, src, elem_ty, .load, ptr);
-}
-
-pub fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst {
- const decl = self.lookupDeclName(scope, decl_name) orelse
- return self.fail(scope, src, "decl '{s}' not found", .{decl_name});
- return self.analyzeDeclRef(scope, src, decl);
-}
-
-pub fn wantSafety(self: *Module, scope: *Scope) bool {
- // TODO take into account scope's safety overrides
- return switch (self.optimizeMode()) {
- .Debug => true,
- .ReleaseSafe => true,
- .ReleaseFast => false,
- .ReleaseSmall => false,
- };
-}
-
-pub fn analyzeIsNull(
- self: *Module,
- scope: *Scope,
- src: usize,
- operand: *Inst,
- invert_logic: bool,
-) InnerError!*Inst {
- if (operand.value()) |opt_val| {
- const is_null = opt_val.isNull();
- const bool_value = if (invert_logic) !is_null else is_null;
- return self.constBool(scope, src, bool_value);
- }
- const b = try self.requireRuntimeBlock(scope, src);
- const inst_tag: Inst.Tag = if (invert_logic) .is_non_null else .is_null;
- return self.addUnOp(b, src, Type.initTag(.bool), inst_tag, operand);
-}
-
-pub fn analyzeIsErr(self: *Module, scope: *Scope, src: usize, operand: *Inst) InnerError!*Inst {
- const ot = operand.ty.zigTypeTag();
- if (ot != .ErrorSet and ot != .ErrorUnion) return self.constBool(scope, src, false);
- if (ot == .ErrorSet) return self.constBool(scope, src, true);
- assert(ot == .ErrorUnion);
- if (operand.value()) |err_union| {
- return self.constBool(scope, src, err_union.getError() != null);
- }
- const b = try self.requireRuntimeBlock(scope, src);
- return self.addUnOp(b, src, Type.initTag(.bool), .is_err, operand);
-}
-
-pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst, start: *Inst, end_opt: ?*Inst, sentinel_opt: ?*Inst) InnerError!*Inst {
- const ptr_child = switch (array_ptr.ty.zigTypeTag()) {
- .Pointer => array_ptr.ty.elemType(),
- else => return self.fail(scope, src, "expected pointer, found '{}'", .{array_ptr.ty}),
- };
-
- var array_type = ptr_child;
- const elem_type = switch (ptr_child.zigTypeTag()) {
- .Array => ptr_child.elemType(),
- .Pointer => blk: {
- if (ptr_child.isSinglePointer()) {
- if (ptr_child.elemType().zigTypeTag() == .Array) {
- array_type = ptr_child.elemType();
- break :blk ptr_child.elemType().elemType();
- }
-
- return self.fail(scope, src, "slice of single-item pointer", .{});
- }
- break :blk ptr_child.elemType();
- },
- else => return self.fail(scope, src, "slice of non-array type '{}'", .{ptr_child}),
- };
-
- const slice_sentinel = if (sentinel_opt) |sentinel| blk: {
- const casted = try self.coerce(scope, elem_type, sentinel);
- break :blk try self.resolveConstValue(scope, casted);
- } else null;
-
- var return_ptr_size: std.builtin.TypeInfo.Pointer.Size = .Slice;
- var return_elem_type = elem_type;
- if (end_opt) |end| {
- if (end.value()) |end_val| {
- if (start.value()) |start_val| {
- const start_u64 = start_val.toUnsignedInt();
- const end_u64 = end_val.toUnsignedInt();
- if (start_u64 > end_u64) {
- return self.fail(scope, src, "out of bounds slice", .{});
- }
-
- const len = end_u64 - start_u64;
- const array_sentinel = if (array_type.zigTypeTag() == .Array and end_u64 == array_type.arrayLen())
- array_type.sentinel()
- else
- slice_sentinel;
- return_elem_type = try self.arrayType(scope, len, array_sentinel, elem_type);
- return_ptr_size = .One;
- }
- }
- }
- const return_type = try self.ptrType(
- scope,
- src,
- return_elem_type,
- if (end_opt == null) slice_sentinel else null,
- 0, // TODO alignment
- 0,
- 0,
- !ptr_child.isConstPtr(),
- ptr_child.isAllowzeroPtr(),
- ptr_child.isVolatilePtr(),
- return_ptr_size,
- );
-
- return self.fail(scope, src, "TODO implement analysis of slice", .{});
-}
-
-pub fn analyzeImport(self: *Module, scope: *Scope, src: usize, target_string: []const u8) !*Scope.File {
- const cur_pkg = scope.getFileScope().pkg;
- const cur_pkg_dir_path = cur_pkg.root_src_directory.path orelse ".";
- const found_pkg = cur_pkg.table.get(target_string);
-
- const resolved_path = if (found_pkg) |pkg|
- try std.fs.path.resolve(self.gpa, &[_][]const u8{ pkg.root_src_directory.path orelse ".", pkg.root_src_path })
- else
- try std.fs.path.resolve(self.gpa, &[_][]const u8{ cur_pkg_dir_path, target_string });
- errdefer self.gpa.free(resolved_path);
-
- if (self.import_table.get(resolved_path)) |some| {
- self.gpa.free(resolved_path);
- return some;
- }
-
- if (found_pkg == null) {
- const resolved_root_path = try std.fs.path.resolve(self.gpa, &[_][]const u8{cur_pkg_dir_path});
- defer self.gpa.free(resolved_root_path);
-
- if (!mem.startsWith(u8, resolved_path, resolved_root_path)) {
- return error.ImportOutsidePkgPath;
- }
- }
-
- // TODO Scope.Container arena for ty and sub_file_path
- const file_scope = try self.gpa.create(Scope.File);
- errdefer self.gpa.destroy(file_scope);
- const struct_ty = try Type.Tag.empty_struct.create(self.gpa, &file_scope.root_container);
- errdefer self.gpa.destroy(struct_ty.castTag(.empty_struct).?);
-
- file_scope.* = .{
- .sub_file_path = resolved_path,
- .source = .{ .unloaded = {} },
- .tree = undefined,
- .status = .never_loaded,
- .pkg = found_pkg orelse cur_pkg,
- .root_container = .{
- .file_scope = file_scope,
- .decls = .{},
- .ty = struct_ty,
- },
- };
- self.analyzeContainer(&file_scope.root_container) catch |err| switch (err) {
- error.AnalysisFail => {
- assert(self.comp.totalErrorCount() != 0);
- },
- else => |e| return e,
- };
- try self.import_table.put(self.gpa, file_scope.sub_file_path, file_scope);
- return file_scope;
-}
-
-/// Asserts that lhs and rhs types are both numeric.
-pub fn cmpNumeric(
- self: *Module,
- scope: *Scope,
- src: usize,
- lhs: *Inst,
- rhs: *Inst,
- op: std.math.CompareOperator,
-) InnerError!*Inst {
- assert(lhs.ty.isNumeric());
- assert(rhs.ty.isNumeric());
-
- const lhs_ty_tag = lhs.ty.zigTypeTag();
- const rhs_ty_tag = rhs.ty.zigTypeTag();
-
- if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) {
- if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
- return self.fail(scope, src, "vector length mismatch: {d} and {d}", .{
- lhs.ty.arrayLen(),
- rhs.ty.arrayLen(),
- });
- }
- return self.fail(scope, src, "TODO implement support for vectors in cmpNumeric", .{});
- } else if (lhs_ty_tag == .Vector or rhs_ty_tag == .Vector) {
- return self.fail(scope, src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{
- lhs.ty,
- rhs.ty,
- });
- }
-
- if (lhs.value()) |lhs_val| {
- if (rhs.value()) |rhs_val| {
- return self.constBool(scope, src, Value.compare(lhs_val, op, rhs_val));
- }
- }
-
- // TODO handle comparisons against lazy zero values
- // Some values can be compared against zero without being runtime known or without forcing
- // a full resolution of their value, for example `@sizeOf(@Frame(function))` is known to
- // always be nonzero, and we benefit from not forcing the full evaluation and stack frame layout
- // of this function if we don't need to.
-
- // It must be a runtime comparison.
- const b = try self.requireRuntimeBlock(scope, src);
- // For floats, emit a float comparison instruction.
- const lhs_is_float = switch (lhs_ty_tag) {
- .Float, .ComptimeFloat => true,
- else => false,
- };
- const rhs_is_float = switch (rhs_ty_tag) {
- .Float, .ComptimeFloat => true,
- else => false,
- };
- if (lhs_is_float and rhs_is_float) {
- // Implicit cast the smaller one to the larger one.
- const dest_type = x: {
- if (lhs_ty_tag == .ComptimeFloat) {
- break :x rhs.ty;
- } else if (rhs_ty_tag == .ComptimeFloat) {
- break :x lhs.ty;
- }
- if (lhs.ty.floatBits(self.getTarget()) >= rhs.ty.floatBits(self.getTarget())) {
- break :x lhs.ty;
- } else {
- break :x rhs.ty;
- }
- };
- const casted_lhs = try self.coerce(scope, dest_type, lhs);
- const casted_rhs = try self.coerce(scope, dest_type, rhs);
- return self.addBinOp(b, src, dest_type, Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs);
- }
- // For mixed unsigned integer sizes, implicit cast both operands to the larger integer.
- // For mixed signed and unsigned integers, implicit cast both operands to a signed
- // integer with + 1 bit.
- // For mixed floats and integers, extract the integer part from the float, cast that to
- // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float,
- // add/subtract 1.
- const lhs_is_signed = if (lhs.value()) |lhs_val|
- lhs_val.compareWithZero(.lt)
- else
- (lhs.ty.isFloat() or lhs.ty.isSignedInt());
- const rhs_is_signed = if (rhs.value()) |rhs_val|
- rhs_val.compareWithZero(.lt)
- else
- (rhs.ty.isFloat() or rhs.ty.isSignedInt());
- const dest_int_is_signed = lhs_is_signed or rhs_is_signed;
-
- var dest_float_type: ?Type = null;
-
- var lhs_bits: usize = undefined;
- if (lhs.value()) |lhs_val| {
- if (lhs_val.isUndef())
- return self.constUndef(scope, src, Type.initTag(.bool));
- const is_unsigned = if (lhs_is_float) x: {
- var bigint_space: Value.BigIntSpace = undefined;
- var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(self.gpa);
- defer bigint.deinit();
- const zcmp = lhs_val.orderAgainstZero();
- if (lhs_val.floatHasFraction()) {
- switch (op) {
- .eq => return self.constBool(scope, src, false),
- .neq => return self.constBool(scope, src, true),
- else => {},
- }
- if (zcmp == .lt) {
- try bigint.addScalar(bigint.toConst(), -1);
- } else {
- try bigint.addScalar(bigint.toConst(), 1);
- }
- }
- lhs_bits = bigint.toConst().bitCountTwosComp();
- break :x (zcmp != .lt);
- } else x: {
- lhs_bits = lhs_val.intBitCountTwosComp();
- break :x (lhs_val.orderAgainstZero() != .lt);
- };
- lhs_bits += @boolToInt(is_unsigned and dest_int_is_signed);
- } else if (lhs_is_float) {
- dest_float_type = lhs.ty;
- } else {
- const int_info = lhs.ty.intInfo(self.getTarget());
- lhs_bits = int_info.bits + @boolToInt(int_info.signedness == .unsigned and dest_int_is_signed);
- }
-
- var rhs_bits: usize = undefined;
- if (rhs.value()) |rhs_val| {
- if (rhs_val.isUndef())
- return self.constUndef(scope, src, Type.initTag(.bool));
- const is_unsigned = if (rhs_is_float) x: {
- var bigint_space: Value.BigIntSpace = undefined;
- var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(self.gpa);
- defer bigint.deinit();
- const zcmp = rhs_val.orderAgainstZero();
- if (rhs_val.floatHasFraction()) {
- switch (op) {
- .eq => return self.constBool(scope, src, false),
- .neq => return self.constBool(scope, src, true),
- else => {},
- }
- if (zcmp == .lt) {
- try bigint.addScalar(bigint.toConst(), -1);
- } else {
- try bigint.addScalar(bigint.toConst(), 1);
- }
- }
- rhs_bits = bigint.toConst().bitCountTwosComp();
- break :x (zcmp != .lt);
- } else x: {
- rhs_bits = rhs_val.intBitCountTwosComp();
- break :x (rhs_val.orderAgainstZero() != .lt);
- };
- rhs_bits += @boolToInt(is_unsigned and dest_int_is_signed);
- } else if (rhs_is_float) {
- dest_float_type = rhs.ty;
- } else {
- const int_info = rhs.ty.intInfo(self.getTarget());
- rhs_bits = int_info.bits + @boolToInt(int_info.signedness == .unsigned and dest_int_is_signed);
- }
-
- const dest_type = if (dest_float_type) |ft| ft else blk: {
- const max_bits = std.math.max(lhs_bits, rhs_bits);
- const casted_bits = std.math.cast(u16, max_bits) catch |err| switch (err) {
- error.Overflow => return self.fail(scope, src, "{d} exceeds maximum integer bit count", .{max_bits}),
- };
- break :blk try self.makeIntType(scope, dest_int_is_signed, casted_bits);
- };
- const casted_lhs = try self.coerce(scope, dest_type, lhs);
- const casted_rhs = try self.coerce(scope, dest_type, rhs);
-
- return self.addBinOp(b, src, Type.initTag(.bool), Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs);
-}
-
-fn wrapOptional(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
- if (inst.value()) |val| {
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
- }
-
- const b = try self.requireRuntimeBlock(scope, inst.src);
- return self.addUnOp(b, inst.src, dest_type, .wrap_optional, inst);
-}
-
-fn wrapErrorUnion(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
- // TODO deal with inferred error sets
- const err_union = dest_type.castTag(.error_union).?;
- if (inst.value()) |val| {
- const to_wrap = if (inst.ty.zigTypeTag() != .ErrorSet) blk: {
- _ = try self.coerce(scope, err_union.data.payload, inst);
- break :blk val;
- } else switch (err_union.data.error_set.tag()) {
- .anyerror => val,
- .error_set_single => blk: {
- const n = err_union.data.error_set.castTag(.error_set_single).?.data;
- if (!mem.eql(u8, val.castTag(.@"error").?.data.name, n))
- return self.fail(scope, inst.src, "expected type '{}', found type '{}'", .{ err_union.data.error_set, inst.ty });
- break :blk val;
- },
- .error_set => blk: {
- const f = err_union.data.error_set.castTag(.error_set).?.data.typed_value.most_recent.typed_value.val.castTag(.error_set).?.data.fields;
- if (f.get(val.castTag(.@"error").?.data.name) == null)
- return self.fail(scope, inst.src, "expected type '{}', found type '{}'", .{ err_union.data.error_set, inst.ty });
- break :blk val;
- },
- else => unreachable,
- };
-
- return self.constInst(scope, inst.src, .{
- .ty = dest_type,
- // creating a SubValue for the error_union payload
- .val = try Value.Tag.error_union.create(
- scope.arena(),
- to_wrap,
- ),
- });
- }
-
- const b = try self.requireRuntimeBlock(scope, inst.src);
-
- // we are coercing from E to E!T
- if (inst.ty.zigTypeTag() == .ErrorSet) {
- var coerced = try self.coerce(scope, err_union.data.error_set, inst);
- return self.addUnOp(b, inst.src, dest_type, .wrap_errunion_err, coerced);
- } else {
- var coerced = try self.coerce(scope, err_union.data.payload, inst);
- return self.addUnOp(b, inst.src, dest_type, .wrap_errunion_payload, coerced);
- }
-}
-
-fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type {
- const int_payload = try scope.arena().create(Type.Payload.Bits);
+pub fn makeIntType(arena: *Allocator, signedness: std.builtin.Signedness, bits: u16) !Type {
+ const int_payload = try arena.create(Type.Payload.Bits);
int_payload.* = .{
.base = .{
- .tag = if (signed) .int_signed else .int_unsigned,
+ .tag = switch (signedness) {
+ .signed => .int_signed,
+ .unsigned => .int_unsigned,
+ },
},
.data = bits,
};
return Type.initPayload(&int_payload.base);
}
-pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type {
- if (instructions.len == 0)
- return Type.initTag(.noreturn);
-
- if (instructions.len == 1)
- return instructions[0].ty;
-
- var chosen = instructions[0];
- for (instructions[1..]) |candidate| {
- if (candidate.ty.eql(chosen.ty))
- continue;
- if (candidate.ty.zigTypeTag() == .NoReturn)
- continue;
- if (chosen.ty.zigTypeTag() == .NoReturn) {
- chosen = candidate;
- continue;
- }
- if (candidate.ty.zigTypeTag() == .Undefined)
- continue;
- if (chosen.ty.zigTypeTag() == .Undefined) {
- chosen = candidate;
- continue;
- }
- if (chosen.ty.isInt() and
- candidate.ty.isInt() and
- chosen.ty.isSignedInt() == candidate.ty.isSignedInt())
- {
- if (chosen.ty.intInfo(self.getTarget()).bits < candidate.ty.intInfo(self.getTarget()).bits) {
- chosen = candidate;
- }
- continue;
- }
- if (chosen.ty.isFloat() and candidate.ty.isFloat()) {
- if (chosen.ty.floatBits(self.getTarget()) < candidate.ty.floatBits(self.getTarget())) {
- chosen = candidate;
- }
- continue;
- }
-
- if (chosen.ty.zigTypeTag() == .ComptimeInt and candidate.ty.isInt()) {
- chosen = candidate;
- continue;
- }
-
- if (chosen.ty.isInt() and candidate.ty.zigTypeTag() == .ComptimeInt) {
- continue;
- }
-
- // TODO error notes pointing out each type
- return self.fail(scope, candidate.src, "incompatible types: '{}' and '{}'", .{ chosen.ty, candidate.ty });
- }
-
- return chosen.ty;
-}
-
-pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) InnerError!*Inst {
- if (dest_type.tag() == .var_args_param) {
- return self.coerceVarArgParam(scope, inst);
- }
- // If the types are the same, we can return the operand.
- if (dest_type.eql(inst.ty))
- return inst;
-
- const in_memory_result = coerceInMemoryAllowed(dest_type, inst.ty);
- if (in_memory_result == .ok) {
- return self.bitcast(scope, dest_type, inst);
- }
-
- // undefined to anything
- if (inst.value()) |val| {
- if (val.isUndef() or inst.ty.zigTypeTag() == .Undefined) {
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
- }
- }
- assert(inst.ty.zigTypeTag() != .Undefined);
-
- // null to ?T
- if (dest_type.zigTypeTag() == .Optional and inst.ty.zigTypeTag() == .Null) {
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = Value.initTag(.null_value) });
- }
-
- // T to ?T
- if (dest_type.zigTypeTag() == .Optional) {
- var buf: Type.Payload.ElemType = undefined;
- const child_type = dest_type.optionalChild(&buf);
- if (child_type.eql(inst.ty)) {
- return self.wrapOptional(scope, dest_type, inst);
- } else if (try self.coerceNum(scope, child_type, inst)) |some| {
- return self.wrapOptional(scope, dest_type, some);
- }
- }
-
- // T to E!T or E to E!T
- if (dest_type.tag() == .error_union) {
- return try self.wrapErrorUnion(scope, dest_type, inst);
- }
-
- // Coercions where the source is a single pointer to an array.
- src_array_ptr: {
- if (!inst.ty.isSinglePointer()) break :src_array_ptr;
- const array_type = inst.ty.elemType();
- if (array_type.zigTypeTag() != .Array) break :src_array_ptr;
- const array_elem_type = array_type.elemType();
- if (inst.ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr;
- if (inst.ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr;
-
- const dst_elem_type = dest_type.elemType();
- switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) {
- .ok => {},
- .no_match => break :src_array_ptr,
- }
-
- switch (dest_type.ptrSize()) {
- .Slice => {
- // *[N]T to []T
- return self.coerceArrayPtrToSlice(scope, dest_type, inst);
- },
- .C => {
- // *[N]T to [*c]T
- return self.coerceArrayPtrToMany(scope, dest_type, inst);
- },
- .Many => {
- // *[N]T to [*]T
- // *[N:s]T to [*:s]T
- const src_sentinel = array_type.sentinel();
- const dst_sentinel = dest_type.sentinel();
- if (src_sentinel == null and dst_sentinel == null)
- return self.coerceArrayPtrToMany(scope, dest_type, inst);
-
- if (src_sentinel) |src_s| {
- if (dst_sentinel) |dst_s| {
- if (src_s.eql(dst_s)) {
- return self.coerceArrayPtrToMany(scope, dest_type, inst);
- }
- }
- }
- },
- .One => {},
- }
- }
-
- // comptime known number to other number
- if (try self.coerceNum(scope, dest_type, inst)) |some|
- return some;
-
- // integer widening
- if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) {
- assert(inst.value() == null); // handled above
-
- const src_info = inst.ty.intInfo(self.getTarget());
- const dst_info = dest_type.intInfo(self.getTarget());
- if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or
- // small enough unsigned ints can get casted to large enough signed ints
- (src_info.signedness == .signed and dst_info.signedness == .unsigned and dst_info.bits > src_info.bits))
- {
- const b = try self.requireRuntimeBlock(scope, inst.src);
- return self.addUnOp(b, inst.src, dest_type, .intcast, inst);
- }
- }
-
- // float widening
- if (inst.ty.zigTypeTag() == .Float and dest_type.zigTypeTag() == .Float) {
- assert(inst.value() == null); // handled above
-
- const src_bits = inst.ty.floatBits(self.getTarget());
- const dst_bits = dest_type.floatBits(self.getTarget());
- if (dst_bits >= src_bits) {
- const b = try self.requireRuntimeBlock(scope, inst.src);
- return self.addUnOp(b, inst.src, dest_type, .floatcast, inst);
- }
- }
-
- return self.fail(scope, inst.src, "expected {}, found {}", .{ dest_type, inst.ty });
-}
-
-pub fn coerceNum(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) InnerError!?*Inst {
- const val = inst.value() orelse return null;
- const src_zig_tag = inst.ty.zigTypeTag();
- const dst_zig_tag = dest_type.zigTypeTag();
-
- if (dst_zig_tag == .ComptimeInt or dst_zig_tag == .Int) {
- if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
- if (val.floatHasFraction()) {
- return self.fail(scope, inst.src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst.ty });
- }
- return self.fail(scope, inst.src, "TODO float to int", .{});
- } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
- if (!val.intFitsInType(dest_type, self.getTarget())) {
- return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
- }
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
- }
- } else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) {
- if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
- const res = val.floatCast(scope.arena(), dest_type, self.getTarget()) catch |err| switch (err) {
- error.Overflow => return self.fail(
- scope,
- inst.src,
- "cast of value {} to type '{}' loses information",
- .{ val, dest_type },
- ),
- error.OutOfMemory => return error.OutOfMemory,
- };
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = res });
- } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
- return self.fail(scope, inst.src, "TODO int to float", .{});
- }
- }
- return null;
-}
-
-pub fn coerceVarArgParam(mod: *Module, scope: *Scope, inst: *Inst) !*Inst {
- switch (inst.ty.zigTypeTag()) {
- .ComptimeInt, .ComptimeFloat => return mod.fail(scope, inst.src, "integer and float literals in var args function must be casted", .{}),
- else => {},
- }
- // TODO implement more of this function.
- return inst;
-}
-
-pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_value: *Inst) !*Inst {
- if (ptr.ty.isConstPtr())
- return self.fail(scope, src, "cannot assign to constant", .{});
-
- const elem_ty = ptr.ty.elemType();
- const value = try self.coerce(scope, elem_ty, uncasted_value);
- if (elem_ty.onePossibleValue() != null)
- return self.constVoid(scope, src);
-
- // TODO handle comptime pointer writes
- // TODO handle if the element type requires comptime
-
- const b = try self.requireRuntimeBlock(scope, src);
- return self.addBinOp(b, src, Type.initTag(.void), .store, ptr, value);
-}
-
-pub fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
- if (inst.value()) |val| {
- // Keep the comptime Value representation; take the new type.
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
- }
- // TODO validate the type size and other compile errors
- const b = try self.requireRuntimeBlock(scope, inst.src);
- return self.addUnOp(b, inst.src, dest_type, .bitcast, inst);
-}
-
-fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
- if (inst.value()) |val| {
- // The comptime Value representation is compatible with both types.
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
- }
- return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{});
-}
-
-fn coerceArrayPtrToMany(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
- if (inst.value()) |val| {
- // The comptime Value representation is compatible with both types.
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
- }
- return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToMany runtime instruction", .{});
-}
-
/// We don't return a pointer to the new error note because the pointer
/// becomes invalid when you add another one.
pub fn errNote(
mod: *Module,
scope: *Scope,
- src: usize,
+ src: LazySrcLoc,
parent: *ErrorMsg,
comptime format: []const u8,
args: anytype,
@@ -3605,10 +4009,7 @@ pub fn errNote(
parent.notes = try mod.gpa.realloc(parent.notes, parent.notes.len + 1);
parent.notes[parent.notes.len - 1] = .{
- .src_loc = .{
- .file_scope = scope.getFileScope(),
- .byte_offset = src,
- },
+ .src_loc = src.toSrcLoc(scope),
.msg = msg,
};
}
@@ -3616,121 +4017,112 @@ pub fn errNote(
pub fn errMsg(
mod: *Module,
scope: *Scope,
- src_byte_offset: usize,
+ src: LazySrcLoc,
comptime format: []const u8,
args: anytype,
) error{OutOfMemory}!*ErrorMsg {
- return ErrorMsg.create(mod.gpa, .{
- .file_scope = scope.getFileScope(),
- .byte_offset = src_byte_offset,
- }, format, args);
+ return ErrorMsg.create(mod.gpa, src.toSrcLoc(scope), format, args);
}
pub fn fail(
mod: *Module,
scope: *Scope,
- src_byte_offset: usize,
+ src: LazySrcLoc,
comptime format: []const u8,
args: anytype,
) InnerError {
- const err_msg = try mod.errMsg(scope, src_byte_offset, format, args);
+ const err_msg = try mod.errMsg(scope, src, format, args);
return mod.failWithOwnedErrorMsg(scope, err_msg);
}
+/// Same as `fail`, except given an absolute byte offset, and the function sets up the `LazySrcLoc`
+/// for pointing at it relatively by subtracting from the containing `Decl`.
+pub fn failOff(
+ mod: *Module,
+ scope: *Scope,
+ byte_offset: u32,
+ comptime format: []const u8,
+ args: anytype,
+) InnerError {
+ const decl_byte_offset = scope.srcDecl().?.srcByteOffset();
+ const src: LazySrcLoc = .{ .byte_offset = byte_offset - decl_byte_offset };
+ return mod.fail(scope, src, format, args);
+}
+
+/// Same as `fail`, except given a token index, and the function sets up the `LazySrcLoc`
+/// for pointing at it relatively by subtracting from the containing `Decl`.
pub fn failTok(
- self: *Module,
+ mod: *Module,
scope: *Scope,
token_index: ast.TokenIndex,
comptime format: []const u8,
args: anytype,
) InnerError {
- const src = scope.tree().tokens.items(.start)[token_index];
- return self.fail(scope, src, format, args);
+ const src = scope.srcDecl().?.tokSrcLoc(token_index);
+ return mod.fail(scope, src, format, args);
}
+/// Same as `fail`, except given an AST node index, and the function sets up the `LazySrcLoc`
+/// for pointing at it relatively by subtracting from the containing `Decl`.
pub fn failNode(
- self: *Module,
+ mod: *Module,
scope: *Scope,
- ast_node: ast.Node.Index,
+ node_index: ast.Node.Index,
comptime format: []const u8,
args: anytype,
) InnerError {
- const tree = scope.tree();
- const src = tree.tokens.items(.start)[tree.firstToken(ast_node)];
- return self.fail(scope, src, format, args);
+ const src = scope.srcDecl().?.nodeSrcLoc(node_index);
+ return mod.fail(scope, src, format, args);
}
-pub fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, err_msg: *ErrorMsg) InnerError {
+pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) InnerError {
@setCold(true);
{
- errdefer err_msg.destroy(self.gpa);
- try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1);
- try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1);
+ errdefer err_msg.destroy(mod.gpa);
+ try mod.failed_decls.ensureCapacity(mod.gpa, mod.failed_decls.items().len + 1);
+ try mod.failed_files.ensureCapacity(mod.gpa, mod.failed_files.items().len + 1);
}
switch (scope.tag) {
.block => {
const block = scope.cast(Scope.Block).?;
- if (block.inlining) |inlining| {
- if (inlining.shared.caller) |func| {
- func.state = .sema_failure;
- } else {
- block.owner_decl.analysis = .sema_failure;
- block.owner_decl.generation = self.generation;
- }
+ if (block.sema.owner_func) |func| {
+ func.state = .sema_failure;
} else {
- if (block.func) |func| {
- func.state = .sema_failure;
- } else {
- block.owner_decl.analysis = .sema_failure;
- block.owner_decl.generation = self.generation;
- }
+ block.sema.owner_decl.analysis = .sema_failure;
+ block.sema.owner_decl.generation = mod.generation;
}
- self.failed_decls.putAssumeCapacityNoClobber(block.owner_decl, err_msg);
+ mod.failed_decls.putAssumeCapacityNoClobber(block.sema.owner_decl, err_msg);
},
- .gen_zir, .gen_suspend => {
- const gen_zir = scope.cast(Scope.GenZIR).?;
- gen_zir.decl.analysis = .sema_failure;
- gen_zir.decl.generation = self.generation;
- self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg);
+ .gen_zir => {
+ const gen_zir = scope.cast(Scope.GenZir).?;
+ gen_zir.astgen.decl.analysis = .sema_failure;
+ gen_zir.astgen.decl.generation = mod.generation;
+ mod.failed_decls.putAssumeCapacityNoClobber(gen_zir.astgen.decl, err_msg);
},
.local_val => {
const gen_zir = scope.cast(Scope.LocalVal).?.gen_zir;
- gen_zir.decl.analysis = .sema_failure;
- gen_zir.decl.generation = self.generation;
- self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg);
+ gen_zir.astgen.decl.analysis = .sema_failure;
+ gen_zir.astgen.decl.generation = mod.generation;
+ mod.failed_decls.putAssumeCapacityNoClobber(gen_zir.astgen.decl, err_msg);
},
.local_ptr => {
const gen_zir = scope.cast(Scope.LocalPtr).?.gen_zir;
- gen_zir.decl.analysis = .sema_failure;
- gen_zir.decl.generation = self.generation;
- self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg);
- },
- .gen_nosuspend => {
- const gen_zir = scope.cast(Scope.Nosuspend).?.gen_zir;
- gen_zir.decl.analysis = .sema_failure;
- gen_zir.decl.generation = self.generation;
- self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg);
+ gen_zir.astgen.decl.analysis = .sema_failure;
+ gen_zir.astgen.decl.generation = mod.generation;
+ mod.failed_decls.putAssumeCapacityNoClobber(gen_zir.astgen.decl, err_msg);
},
.file => unreachable,
.container => unreachable,
+ .decl_ref => {
+ const decl_ref = scope.cast(Scope.DeclRef).?;
+ decl_ref.decl.analysis = .sema_failure;
+ decl_ref.decl.generation = mod.generation;
+ mod.failed_decls.putAssumeCapacityNoClobber(decl_ref.decl, err_msg);
+ },
}
return error.AnalysisFail;
}
-const InMemoryCoercionResult = enum {
- ok,
- no_match,
-};
-
-fn coerceInMemoryAllowed(dest_type: Type, src_type: Type) InMemoryCoercionResult {
- if (dest_type.eql(src_type))
- return .ok;
-
- // TODO: implement more of this function
-
- return .no_match;
-}
-
fn srcHashEql(a: std.zig.SrcHash, b: std.zig.SrcHash) bool {
return @bitCast(u128, a) == @bitCast(u128, b);
}
@@ -3780,14 +4172,12 @@ pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
}
pub fn floatAdd(
- self: *Module,
- scope: *Scope,
+ arena: *Allocator,
float_type: Type,
- src: usize,
+ src: LazySrcLoc,
lhs: Value,
rhs: Value,
) !Value {
- const arena = scope.arena();
switch (float_type.tag()) {
.f16 => {
@panic("TODO add __trunctfhf2 to compiler-rt");
@@ -3815,14 +4205,12 @@ pub fn floatAdd(
}
pub fn floatSub(
- self: *Module,
- scope: *Scope,
+ arena: *Allocator,
float_type: Type,
- src: usize,
+ src: LazySrcLoc,
lhs: Value,
rhs: Value,
) !Value {
- const arena = scope.arena();
switch (float_type.tag()) {
.f16 => {
@panic("TODO add __trunctfhf2 to compiler-rt");
@@ -3850,9 +4238,8 @@ pub fn floatSub(
}
pub fn simplePtrType(
- self: *Module,
- scope: *Scope,
- src: usize,
+ mod: *Module,
+ arena: *Allocator,
elem_ty: Type,
mutable: bool,
size: std.builtin.TypeInfo.Pointer.Size,
@@ -3863,7 +4250,7 @@ pub fn simplePtrType(
// TODO stage1 type inference bug
const T = Type.Tag;
- const type_payload = try scope.arena().create(Type.Payload.ElemType);
+ const type_payload = try arena.create(Type.Payload.ElemType);
type_payload.* = .{
.base = .{
.tag = switch (size) {
@@ -3879,9 +4266,8 @@ pub fn simplePtrType(
}
pub fn ptrType(
- self: *Module,
- scope: *Scope,
- src: usize,
+ mod: *Module,
+ arena: *Allocator,
elem_ty: Type,
sentinel: ?Value,
@"align": u32,
@@ -3895,7 +4281,7 @@ pub fn ptrType(
assert(host_size == 0 or bit_offset < host_size * 8);
// TODO check if type can be represented by simplePtrType
- return Type.Tag.pointer.create(scope.arena(), .{
+ return Type.Tag.pointer.create(arena, .{
.pointee_type = elem_ty,
.sentinel = sentinel,
.@"align" = @"align",
@@ -3908,23 +4294,23 @@ pub fn ptrType(
});
}
-pub fn optionalType(self: *Module, scope: *Scope, child_type: Type) Allocator.Error!Type {
+pub fn optionalType(mod: *Module, arena: *Allocator, child_type: Type) Allocator.Error!Type {
switch (child_type.tag()) {
.single_const_pointer => return Type.Tag.optional_single_const_pointer.create(
- scope.arena(),
+ arena,
child_type.elemType(),
),
.single_mut_pointer => return Type.Tag.optional_single_mut_pointer.create(
- scope.arena(),
+ arena,
child_type.elemType(),
),
- else => return Type.Tag.optional.create(scope.arena(), child_type),
+ else => return Type.Tag.optional.create(arena, child_type),
}
}
pub fn arrayType(
- self: *Module,
- scope: *Scope,
+ mod: *Module,
+ arena: *Allocator,
len: u64,
sentinel: ?Value,
elem_type: Type,
@@ -3932,30 +4318,30 @@ pub fn arrayType(
if (elem_type.eql(Type.initTag(.u8))) {
if (sentinel) |some| {
if (some.eql(Value.initTag(.zero))) {
- return Type.Tag.array_u8_sentinel_0.create(scope.arena(), len);
+ return Type.Tag.array_u8_sentinel_0.create(arena, len);
}
} else {
- return Type.Tag.array_u8.create(scope.arena(), len);
+ return Type.Tag.array_u8.create(arena, len);
}
}
if (sentinel) |some| {
- return Type.Tag.array_sentinel.create(scope.arena(), .{
+ return Type.Tag.array_sentinel.create(arena, .{
.len = len,
.sentinel = some,
.elem_type = elem_type,
});
}
- return Type.Tag.array.create(scope.arena(), .{
+ return Type.Tag.array.create(arena, .{
.len = len,
.elem_type = elem_type,
});
}
pub fn errorUnionType(
- self: *Module,
- scope: *Scope,
+ mod: *Module,
+ arena: *Allocator,
error_set: Type,
payload: Type,
) Allocator.Error!Type {
@@ -3964,19 +4350,15 @@ pub fn errorUnionType(
return Type.initTag(.anyerror_void_error_union);
}
- return Type.Tag.error_union.create(scope.arena(), .{
+ return Type.Tag.error_union.create(arena, .{
.error_set = error_set,
.payload = payload,
});
}
-pub fn anyframeType(self: *Module, scope: *Scope, return_type: Type) Allocator.Error!Type {
- return Type.Tag.anyframe_T.create(scope.arena(), return_type);
-}
-
-pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void {
+pub fn dumpInst(mod: *Module, scope: *Scope, inst: *ir.Inst) void {
const zir_module = scope.namespace();
- const source = zir_module.getSource(self) catch @panic("dumpInst failed to get source");
+ const source = zir_module.getSource(mod) catch @panic("dumpInst failed to get source");
const loc = std.zig.findLineColumn(source, inst.src);
if (inst.tag == .constant) {
std.debug.print("constant ty={} val={} src={s}:{d}:{d}\n", .{
@@ -4006,267 +4388,117 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void {
}
}
-pub const PanicId = enum {
- unreach,
- unwrap_null,
- unwrap_errunion,
-};
-
-pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic_id: PanicId) !void {
- const block_inst = try parent_block.arena.create(Inst.Block);
- block_inst.* = .{
- .base = .{
- .tag = Inst.Block.base_tag,
- .ty = Type.initTag(.void),
- .src = ok.src,
- },
- .body = .{
- .instructions = try parent_block.arena.alloc(*Inst, 1), // Only need space for the condbr.
- },
- };
-
- const ok_body: ir.Body = .{
- .instructions = try parent_block.arena.alloc(*Inst, 1), // Only need space for the br_void.
- };
- const br_void = try parent_block.arena.create(Inst.BrVoid);
- br_void.* = .{
- .base = .{
- .tag = .br_void,
- .ty = Type.initTag(.noreturn),
- .src = ok.src,
- },
- .block = block_inst,
- };
- ok_body.instructions[0] = &br_void.base;
-
- var fail_block: Scope.Block = .{
- .parent = parent_block,
- .inst_table = parent_block.inst_table,
- .func = parent_block.func,
- .owner_decl = parent_block.owner_decl,
- .src_decl = parent_block.src_decl,
- .instructions = .{},
- .arena = parent_block.arena,
- .inlining = parent_block.inlining,
- .is_comptime = parent_block.is_comptime,
- .branch_quota = parent_block.branch_quota,
- };
-
- defer fail_block.instructions.deinit(mod.gpa);
-
- _ = try mod.safetyPanic(&fail_block, ok.src, panic_id);
-
- const fail_body: ir.Body = .{ .instructions = try parent_block.arena.dupe(*Inst, fail_block.instructions.items) };
-
- const condbr = try parent_block.arena.create(Inst.CondBr);
- condbr.* = .{
- .base = .{
- .tag = .condbr,
- .ty = Type.initTag(.noreturn),
- .src = ok.src,
- },
- .condition = ok,
- .then_body = ok_body,
- .else_body = fail_body,
- };
- block_inst.body.instructions[0] = &condbr.base;
-
- try parent_block.instructions.append(mod.gpa, &block_inst.base);
-}
-
-pub fn safetyPanic(mod: *Module, block: *Scope.Block, src: usize, panic_id: PanicId) !*Inst {
- // TODO Once we have a panic function to call, call it here instead of breakpoint.
- _ = try mod.addNoOp(block, src, Type.initTag(.void), .breakpoint);
- return mod.addNoOp(block, src, Type.initTag(.noreturn), .unreach);
-}
-
-pub fn getTarget(self: Module) Target {
- return self.comp.bin_file.options.target;
+pub fn getTarget(mod: Module) Target {
+ return mod.comp.bin_file.options.target;
}
-pub fn optimizeMode(self: Module) std.builtin.Mode {
- return self.comp.bin_file.options.optimize_mode;
+pub fn optimizeMode(mod: Module) std.builtin.Mode {
+ return mod.comp.bin_file.options.optimize_mode;
}
-pub fn validateVarType(mod: *Module, scope: *Scope, src: usize, ty: Type) !void {
- if (!ty.isValidVarType(false)) {
- return mod.fail(scope, src, "variable of type '{}' must be const or comptime", .{ty});
- }
-}
-
-/// Identifier token -> String (allocated in scope.arena())
+/// Given an identifier token, obtain the string for it.
+/// If the token uses @"" syntax, parses as a string, reports errors if applicable,
+/// and allocates the result within `scope.arena()`.
+/// Otherwise, returns a reference to the source code bytes directly.
+/// See also `appendIdentStr` and `parseStrLit`.
pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 {
const tree = scope.tree();
const token_tags = tree.tokens.items(.tag);
- const token_starts = tree.tokens.items(.start);
assert(token_tags[token] == .identifier);
-
const ident_name = tree.tokenSlice(token);
- if (mem.startsWith(u8, ident_name, "@")) {
- const raw_string = ident_name[1..];
- var bad_index: usize = undefined;
- return std.zig.parseStringLiteral(scope.arena(), raw_string, &bad_index) catch |err| switch (err) {
- error.InvalidCharacter => {
- const bad_byte = raw_string[bad_index];
- const src = token_starts[token];
- return mod.fail(scope, src + 1 + bad_index, "invalid string literal character: '{c}'\n", .{bad_byte});
- },
- else => |e| return e,
- };
+ if (!mem.startsWith(u8, ident_name, "@")) {
+ return ident_name;
}
- return ident_name;
+ var buf: ArrayListUnmanaged(u8) = .{};
+ defer buf.deinit(mod.gpa);
+ try parseStrLit(mod, scope, token, &buf, ident_name, 1);
+ return buf.toOwnedSlice(mod.gpa);
}
-pub fn emitBackwardBranch(mod: *Module, block: *Scope.Block, src: usize) !void {
- const shared = block.inlining.?.shared;
- shared.branch_count += 1;
- if (shared.branch_count > block.branch_quota.*) {
- // TODO show the "called from here" stack
- return mod.fail(&block.base, src, "evaluation exceeded {d} backwards branches", .{
- block.branch_quota.*,
- });
+/// Given an identifier token, obtain the string for it (possibly parsing as a string
+/// literal if it is @"" syntax), and append the string to `buf`.
+/// See also `identifierTokenString` and `parseStrLit`.
+pub fn appendIdentStr(
+ mod: *Module,
+ scope: *Scope,
+ token: ast.TokenIndex,
+ buf: *ArrayListUnmanaged(u8),
+) InnerError!void {
+ const tree = scope.tree();
+ const token_tags = tree.tokens.items(.tag);
+ assert(token_tags[token] == .identifier);
+ const ident_name = tree.tokenSlice(token);
+ if (!mem.startsWith(u8, ident_name, "@")) {
+ return buf.appendSlice(mod.gpa, ident_name);
+ } else {
+ return mod.parseStrLit(scope, token, buf, ident_name, 1);
}
}
-pub fn namedFieldPtr(
+/// Appends the result to `buf`.
+pub fn parseStrLit(
mod: *Module,
scope: *Scope,
- src: usize,
- object_ptr: *Inst,
- field_name: []const u8,
- field_name_src: usize,
-) InnerError!*Inst {
- const elem_ty = switch (object_ptr.ty.zigTypeTag()) {
- .Pointer => object_ptr.ty.elemType(),
- else => return mod.fail(scope, object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
- };
- switch (elem_ty.zigTypeTag()) {
- .Array => {
- if (mem.eql(u8, field_name, "len")) {
- return mod.constInst(scope, src, .{
- .ty = Type.initTag(.single_const_pointer_to_comptime_int),
- .val = try Value.Tag.ref_val.create(
- scope.arena(),
- try Value.Tag.int_u64.create(scope.arena(), elem_ty.arrayLen()),
- ),
- });
- } else {
- return mod.fail(
- scope,
- field_name_src,
- "no member named '{s}' in '{}'",
- .{ field_name, elem_ty },
- );
- }
+ token: ast.TokenIndex,
+ buf: *ArrayListUnmanaged(u8),
+ bytes: []const u8,
+ offset: u32,
+) InnerError!void {
+ const tree = scope.tree();
+ const token_starts = tree.tokens.items(.start);
+ const raw_string = bytes[offset..];
+ var buf_managed = buf.toManaged(mod.gpa);
+ const result = std.zig.string_literal.parseAppend(&buf_managed, raw_string);
+ buf.* = buf_managed.toUnmanaged();
+ switch (try result) {
+ .success => return,
+ .invalid_character => |bad_index| {
+ return mod.failOff(
+ scope,
+ token_starts[token] + offset + @intCast(u32, bad_index),
+ "invalid string literal character: '{c}'",
+ .{raw_string[bad_index]},
+ );
},
- .Pointer => {
- const ptr_child = elem_ty.elemType();
- switch (ptr_child.zigTypeTag()) {
- .Array => {
- if (mem.eql(u8, field_name, "len")) {
- return mod.constInst(scope, src, .{
- .ty = Type.initTag(.single_const_pointer_to_comptime_int),
- .val = try Value.Tag.ref_val.create(
- scope.arena(),
- try Value.Tag.int_u64.create(scope.arena(), ptr_child.arrayLen()),
- ),
- });
- } else {
- return mod.fail(
- scope,
- field_name_src,
- "no member named '{s}' in '{}'",
- .{ field_name, elem_ty },
- );
- }
- },
- else => {},
- }
+ .expected_hex_digits => |bad_index| {
+ return mod.failOff(
+ scope,
+ token_starts[token] + offset + @intCast(u32, bad_index),
+ "expected hex digits after '\\x'",
+ .{},
+ );
},
- .Type => {
- _ = try mod.resolveConstValue(scope, object_ptr);
- const result = try mod.analyzeDeref(scope, src, object_ptr, object_ptr.src);
- const val = result.value().?;
- const child_type = try val.toType(scope.arena());
- switch (child_type.zigTypeTag()) {
- .ErrorSet => {
- var name: []const u8 = undefined;
- // TODO resolve inferred error sets
- if (val.castTag(.error_set)) |payload|
- name = (payload.data.fields.getEntry(field_name) orelse return mod.fail(scope, src, "no error named '{s}' in '{}'", .{ field_name, child_type })).key
- else
- name = (try mod.getErrorValue(field_name)).key;
-
- const result_type = if (child_type.tag() == .anyerror)
- try Type.Tag.error_set_single.create(scope.arena(), name)
- else
- child_type;
-
- return mod.constInst(scope, src, .{
- .ty = try mod.simplePtrType(scope, src, result_type, false, .One),
- .val = try Value.Tag.ref_val.create(
- scope.arena(),
- try Value.Tag.@"error".create(scope.arena(), .{
- .name = name,
- }),
- ),
- });
- },
- .Struct => {
- const container_scope = child_type.getContainerScope();
- if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| {
- // TODO if !decl.is_pub and inDifferentFiles() "{} is private"
- return mod.analyzeDeclRef(scope, src, decl);
- }
-
- if (container_scope.file_scope == mod.root_scope) {
- return mod.fail(scope, src, "root source file has no member called '{s}'", .{field_name});
- } else {
- return mod.fail(scope, src, "container '{}' has no member called '{s}'", .{ child_type, field_name });
- }
- },
- else => return mod.fail(scope, src, "type '{}' does not support field access", .{child_type}),
- }
+ .invalid_hex_escape => |bad_index| {
+ return mod.failOff(
+ scope,
+ token_starts[token] + offset + @intCast(u32, bad_index),
+ "invalid hex digit: '{c}'",
+ .{raw_string[bad_index]},
+ );
+ },
+ .invalid_unicode_escape => |bad_index| {
+ return mod.failOff(
+ scope,
+ token_starts[token] + offset + @intCast(u32, bad_index),
+ "invalid unicode digit: '{c}'",
+ .{raw_string[bad_index]},
+ );
+ },
+ .missing_matching_rbrace => |bad_index| {
+ return mod.failOff(
+ scope,
+ token_starts[token] + offset + @intCast(u32, bad_index),
+ "missing matching '}}' character",
+ .{},
+ );
+ },
+ .expected_unicode_digits => |bad_index| {
+ return mod.failOff(
+ scope,
+ token_starts[token] + offset + @intCast(u32, bad_index),
+ "expected unicode digits after '\\u'",
+ .{},
+ );
},
- else => {},
- }
- return mod.fail(scope, src, "type '{}' does not support field access", .{elem_ty});
-}
-
-pub fn elemPtr(
- mod: *Module,
- scope: *Scope,
- src: usize,
- array_ptr: *Inst,
- elem_index: *Inst,
-) InnerError!*Inst {
- const elem_ty = switch (array_ptr.ty.zigTypeTag()) {
- .Pointer => array_ptr.ty.elemType(),
- else => return mod.fail(scope, array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}),
- };
- if (!elem_ty.isIndexable()) {
- return mod.fail(scope, src, "array access of non-array type '{}'", .{elem_ty});
- }
-
- if (elem_ty.isSinglePointer() and elem_ty.elemType().zigTypeTag() == .Array) {
- // we have to deref the ptr operand to get the actual array pointer
- const array_ptr_deref = try mod.analyzeDeref(scope, src, array_ptr, array_ptr.src);
- if (array_ptr_deref.value()) |array_ptr_val| {
- if (elem_index.value()) |index_val| {
- // Both array pointer and index are compile-time known.
- const index_u64 = index_val.toUnsignedInt();
- // @intCast here because it would have been impossible to construct a value that
- // required a larger index.
- const elem_ptr = try array_ptr_val.elemPtr(scope.arena(), @intCast(usize, index_u64));
- const pointee_type = elem_ty.elemType().elemType();
-
- return mod.constInst(scope, src, .{
- .ty = try Type.Tag.single_const_pointer.create(scope.arena(), pointee_type),
- .val = elem_ptr,
- });
- }
- }
}
-
- return mod.fail(scope, src, "TODO implement more analyze elemptr", .{});
}
diff --git a/src/RangeSet.zig b/src/RangeSet.zig
@@ -2,13 +2,14 @@ const std = @import("std");
const Order = std.math.Order;
const Value = @import("value.zig").Value;
const RangeSet = @This();
+const SwitchProngSrc = @import("AstGen.zig").SwitchProngSrc;
ranges: std.ArrayList(Range),
pub const Range = struct {
- start: Value,
- end: Value,
- src: usize,
+ first: Value,
+ last: Value,
+ src: SwitchProngSrc,
};
pub fn init(allocator: *std.mem.Allocator) RangeSet {
@@ -21,18 +22,15 @@ pub fn deinit(self: *RangeSet) void {
self.ranges.deinit();
}
-pub fn add(self: *RangeSet, start: Value, end: Value, src: usize) !?usize {
+pub fn add(self: *RangeSet, first: Value, last: Value, src: SwitchProngSrc) !?SwitchProngSrc {
for (self.ranges.items) |range| {
- if ((start.compare(.gte, range.start) and start.compare(.lte, range.end)) or
- (end.compare(.gte, range.start) and end.compare(.lte, range.end)))
- {
- // ranges overlap
- return range.src;
+ if (last.compare(.gte, range.first) and first.compare(.lte, range.last)) {
+ return range.src; // They overlap.
}
}
try self.ranges.append(.{
- .start = start,
- .end = end,
+ .first = first,
+ .last = last,
.src = src,
});
return null;
@@ -40,14 +38,17 @@ pub fn add(self: *RangeSet, start: Value, end: Value, src: usize) !?usize {
/// Assumes a and b do not overlap
fn lessThan(_: void, a: Range, b: Range) bool {
- return a.start.compare(.lt, b.start);
+ return a.first.compare(.lt, b.first);
}
-pub fn spans(self: *RangeSet, start: Value, end: Value) !bool {
+pub fn spans(self: *RangeSet, first: Value, last: Value) !bool {
+ if (self.ranges.items.len == 0)
+ return false;
+
std.sort.sort(Range, self.ranges.items, {}, lessThan);
- if (!self.ranges.items[0].start.eql(start) or
- !self.ranges.items[self.ranges.items.len - 1].end.eql(end))
+ if (!self.ranges.items[0].first.eql(first) or
+ !self.ranges.items[self.ranges.items.len - 1].last.eql(last))
{
return false;
}
@@ -62,11 +63,11 @@ pub fn spans(self: *RangeSet, start: Value, end: Value) !bool {
// i starts counting from the second item.
const prev = self.ranges.items[i];
- // prev.end + 1 == cur.start
- try counter.copy(prev.end.toBigInt(&space));
+ // prev.last + 1 == cur.first
+ try counter.copy(prev.last.toBigInt(&space));
try counter.addScalar(counter.toConst(), 1);
- const cur_start_int = cur.start.toBigInt(&space);
+ const cur_start_int = cur.first.toBigInt(&space);
if (!cur_start_int.eq(counter.toConst())) {
return false;
}
diff --git a/src/Sema.zig b/src/Sema.zig
@@ -0,0 +1,5091 @@
+//! Semantic analysis of ZIR instructions.
+//! Shared to every Block. Stored on the stack.
+//! State used for compiling a `zir.Code` into TZIR.
+//! Transforms untyped ZIR instructions into semantically-analyzed TZIR instructions.
+//! Does type checking, comptime control flow, and safety-check generation.
+//! This is the the heart of the Zig compiler.
+
+mod: *Module,
+/// Alias to `mod.gpa`.
+gpa: *Allocator,
+/// Points to the arena allocator of the Decl.
+arena: *Allocator,
+code: zir.Code,
+/// Maps ZIR to TZIR.
+inst_map: []*Inst,
+/// When analyzing an inline function call, owner_decl is the Decl of the caller
+/// and `src_decl` of `Scope.Block` is the `Decl` of the callee.
+/// This `Decl` owns the arena memory of this `Sema`.
+owner_decl: *Decl,
+/// For an inline or comptime function call, this will be the root parent function
+/// which contains the callsite. Corresponds to `owner_decl`.
+owner_func: ?*Module.Fn,
+/// The function this ZIR code is the body of, according to the source code.
+/// This starts out the same as `owner_func` and then diverges in the case of
+/// an inline or comptime function call.
+func: ?*Module.Fn,
+/// For now, TZIR requires arg instructions to be the first N instructions in the
+/// TZIR code. We store references here for the purpose of `resolveInst`.
+/// This can get reworked with TZIR memory layout changes, into simply:
+/// > Denormalized data to make `resolveInst` faster. This is 0 if not inside a function,
+/// > otherwise it is the number of parameters of the function.
+/// > param_count: u32
+param_inst_list: []const *ir.Inst,
+branch_quota: u32 = 1000,
+branch_count: u32 = 0,
+/// This field is updated when a new source location becomes active, so that
+/// instructions which do not have explicitly mapped source locations still have
+/// access to the source location set by the previous instruction which did
+/// contain a mapped source location.
+src: LazySrcLoc = .{ .token_offset = 0 },
+
+const std = @import("std");
+const mem = std.mem;
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
+const log = std.log.scoped(.sema);
+
+const Sema = @This();
+const Value = @import("value.zig").Value;
+const Type = @import("type.zig").Type;
+const TypedValue = @import("TypedValue.zig");
+const ir = @import("ir.zig");
+const zir = @import("zir.zig");
+const Module = @import("Module.zig");
+const Inst = ir.Inst;
+const Body = ir.Body;
+const trace = @import("tracy.zig").trace;
+const Scope = Module.Scope;
+const InnerError = Module.InnerError;
+const Decl = Module.Decl;
+const LazySrcLoc = Module.LazySrcLoc;
+const RangeSet = @import("RangeSet.zig");
+const AstGen = @import("AstGen.zig");
+
+pub fn root(sema: *Sema, root_block: *Scope.Block) !zir.Inst.Index {
+ const inst_data = sema.code.instructions.items(.data)[0].pl_node;
+ const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+ const root_body = sema.code.extra[extra.end..][0..extra.data.body_len];
+ return sema.analyzeBody(root_block, root_body);
+}
+
+pub fn rootAsRef(sema: *Sema, root_block: *Scope.Block) !zir.Inst.Ref {
+ const break_inst = try sema.root(root_block);
+ return sema.code.instructions.items(.data)[break_inst].@"break".operand;
+}
+
+/// Assumes that `root_block` ends with `break_inline`.
+pub fn rootAsType(sema: *Sema, root_block: *Scope.Block) !Type {
+ assert(root_block.is_comptime);
+ const zir_inst_ref = try sema.rootAsRef(root_block);
+ // Source location is unneeded because resolveConstValue must have already
+ // been successfully called when coercing the value to a type, from the
+ // result location.
+ return sema.resolveType(root_block, .unneeded, zir_inst_ref);
+}
+
+/// Returns only the result from the body that is specified.
+/// Only appropriate to call when it is determined at comptime that this body
+/// has no peers.
+fn resolveBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Index) InnerError!*Inst {
+ const break_inst = try sema.analyzeBody(block, body);
+ const operand_ref = sema.code.instructions.items(.data)[break_inst].@"break".operand;
+ return sema.resolveInst(operand_ref);
+}
+
+/// ZIR instructions which are always `noreturn` return this. This matches the
+/// return type of `analyzeBody` so that we can tail call them.
+/// Only appropriate to return when the instruction is known to be NoReturn
+/// solely based on the ZIR tag.
+const always_noreturn: InnerError!zir.Inst.Index = @as(zir.Inst.Index, undefined);
+
+/// This function is the main loop of `Sema` and it can be used in two different ways:
+/// * The traditional way where there are N breaks out of the block and peer type
+/// resolution is done on the break operands. In this case, the `zir.Inst.Index`
+/// part of the return value will be `undefined`, and callsites should ignore it,
+/// finding the block result value via the block scope.
+/// * The "flat" way. There is only 1 break out of the block, and it is with a `break_inline`
+/// instruction. In this case, the `zir.Inst.Index` part of the return value will be
+/// the break instruction. This communicates both which block the break applies to, as
+/// well as the operand. No block scope needs to be created for this strategy.
+pub fn analyzeBody(
+ sema: *Sema,
+ block: *Scope.Block,
+ body: []const zir.Inst.Index,
+) InnerError!zir.Inst.Index {
+ // No tracy calls here, to avoid interfering with the tail call mechanism.
+
+ const map = block.sema.inst_map;
+ const tags = block.sema.code.instructions.items(.tag);
+ const datas = block.sema.code.instructions.items(.data);
+
+ // We use a while(true) loop here to avoid a redundant way of breaking out of
+ // the loop. The only way to break out of the loop is with a `noreturn`
+ // instruction.
+ // TODO: As an optimization, make sure the codegen for these switch prongs
+ // directly jump to the next one, rather than detouring through the loop
+ // continue expression. Related: https://github.com/ziglang/zig/issues/8220
+ var i: usize = 0;
+ while (true) : (i += 1) {
+ const inst = body[i];
+ map[inst] = switch (tags[inst]) {
+ .elided => continue,
+
+ .add => try sema.zirArithmetic(block, inst),
+ .addwrap => try sema.zirArithmetic(block, inst),
+ .alloc => try sema.zirAlloc(block, inst),
+ .alloc_inferred => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_const)),
+ .alloc_inferred_mut => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_mut)),
+ .alloc_mut => try sema.zirAllocMut(block, inst),
+ .array_cat => try sema.zirArrayCat(block, inst),
+ .array_mul => try sema.zirArrayMul(block, inst),
+ .array_type => try sema.zirArrayType(block, inst),
+ .array_type_sentinel => try sema.zirArrayTypeSentinel(block, inst),
+ .as => try sema.zirAs(block, inst),
+ .as_node => try sema.zirAsNode(block, inst),
+ .@"asm" => try sema.zirAsm(block, inst, false),
+ .asm_volatile => try sema.zirAsm(block, inst, true),
+ .bit_and => try sema.zirBitwise(block, inst, .bit_and),
+ .bit_not => try sema.zirBitNot(block, inst),
+ .bit_or => try sema.zirBitwise(block, inst, .bit_or),
+ .bitcast => try sema.zirBitcast(block, inst),
+ .bitcast_result_ptr => try sema.zirBitcastResultPtr(block, inst),
+ .block => try sema.zirBlock(block, inst),
+ .bool_not => try sema.zirBoolNot(block, inst),
+ .bool_and => try sema.zirBoolOp(block, inst, false),
+ .bool_or => try sema.zirBoolOp(block, inst, true),
+ .bool_br_and => try sema.zirBoolBr(block, inst, false),
+ .bool_br_or => try sema.zirBoolBr(block, inst, true),
+ .call => try sema.zirCall(block, inst, .auto, false),
+ .call_chkused => try sema.zirCall(block, inst, .auto, true),
+ .call_compile_time => try sema.zirCall(block, inst, .compile_time, false),
+ .call_none => try sema.zirCallNone(block, inst, false),
+ .call_none_chkused => try sema.zirCallNone(block, inst, true),
+ .cmp_eq => try sema.zirCmp(block, inst, .eq),
+ .cmp_gt => try sema.zirCmp(block, inst, .gt),
+ .cmp_gte => try sema.zirCmp(block, inst, .gte),
+ .cmp_lt => try sema.zirCmp(block, inst, .lt),
+ .cmp_lte => try sema.zirCmp(block, inst, .lte),
+ .cmp_neq => try sema.zirCmp(block, inst, .neq),
+ .coerce_result_ptr => try sema.zirCoerceResultPtr(block, inst),
+ .@"const" => try sema.zirConst(block, inst),
+ .decl_ref => try sema.zirDeclRef(block, inst),
+ .decl_val => try sema.zirDeclVal(block, inst),
+ .load => try sema.zirLoad(block, inst),
+ .div => try sema.zirArithmetic(block, inst),
+ .elem_ptr => try sema.zirElemPtr(block, inst),
+ .elem_ptr_node => try sema.zirElemPtrNode(block, inst),
+ .elem_val => try sema.zirElemVal(block, inst),
+ .elem_val_node => try sema.zirElemValNode(block, inst),
+ .enum_literal => try sema.zirEnumLiteral(block, inst),
+ .enum_literal_small => try sema.zirEnumLiteralSmall(block, inst),
+ .err_union_code => try sema.zirErrUnionCode(block, inst),
+ .err_union_code_ptr => try sema.zirErrUnionCodePtr(block, inst),
+ .err_union_payload_safe => try sema.zirErrUnionPayload(block, inst, true),
+ .err_union_payload_safe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, true),
+ .err_union_payload_unsafe => try sema.zirErrUnionPayload(block, inst, false),
+ .err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, false),
+ .error_union_type => try sema.zirErrorUnionType(block, inst),
+ .error_value => try sema.zirErrorValue(block, inst),
+ .error_to_int => try sema.zirErrorToInt(block, inst),
+ .int_to_error => try sema.zirIntToError(block, inst),
+ .field_ptr => try sema.zirFieldPtr(block, inst),
+ .field_ptr_named => try sema.zirFieldPtrNamed(block, inst),
+ .field_val => try sema.zirFieldVal(block, inst),
+ .field_val_named => try sema.zirFieldValNamed(block, inst),
+ .floatcast => try sema.zirFloatcast(block, inst),
+ .fn_type => try sema.zirFnType(block, inst, false),
+ .fn_type_cc => try sema.zirFnTypeCc(block, inst, false),
+ .fn_type_cc_var_args => try sema.zirFnTypeCc(block, inst, true),
+ .fn_type_var_args => try sema.zirFnType(block, inst, true),
+ .import => try sema.zirImport(block, inst),
+ .indexable_ptr_len => try sema.zirIndexablePtrLen(block, inst),
+ .int => try sema.zirInt(block, inst),
+ .int_type => try sema.zirIntType(block, inst),
+ .intcast => try sema.zirIntcast(block, inst),
+ .is_err => try sema.zirIsErr(block, inst),
+ .is_err_ptr => try sema.zirIsErrPtr(block, inst),
+ .is_non_null => try sema.zirIsNull(block, inst, true),
+ .is_non_null_ptr => try sema.zirIsNullPtr(block, inst, true),
+ .is_null => try sema.zirIsNull(block, inst, false),
+ .is_null_ptr => try sema.zirIsNullPtr(block, inst, false),
+ .loop => try sema.zirLoop(block, inst),
+ .merge_error_sets => try sema.zirMergeErrorSets(block, inst),
+ .mod_rem => try sema.zirArithmetic(block, inst),
+ .mul => try sema.zirArithmetic(block, inst),
+ .mulwrap => try sema.zirArithmetic(block, inst),
+ .negate => try sema.zirNegate(block, inst, .sub),
+ .negate_wrap => try sema.zirNegate(block, inst, .subwrap),
+ .optional_payload_safe => try sema.zirOptionalPayload(block, inst, true),
+ .optional_payload_safe_ptr => try sema.zirOptionalPayloadPtr(block, inst, true),
+ .optional_payload_unsafe => try sema.zirOptionalPayload(block, inst, false),
+ .optional_payload_unsafe_ptr => try sema.zirOptionalPayloadPtr(block, inst, false),
+ .optional_type => try sema.zirOptionalType(block, inst),
+ .optional_type_from_ptr_elem => try sema.zirOptionalTypeFromPtrElem(block, inst),
+ .param_type => try sema.zirParamType(block, inst),
+ .ptr_type => try sema.zirPtrType(block, inst),
+ .ptr_type_simple => try sema.zirPtrTypeSimple(block, inst),
+ .ptrtoint => try sema.zirPtrtoint(block, inst),
+ .ref => try sema.zirRef(block, inst),
+ .ret_ptr => try sema.zirRetPtr(block, inst),
+ .ret_type => try sema.zirRetType(block, inst),
+ .shl => try sema.zirShl(block, inst),
+ .shr => try sema.zirShr(block, inst),
+ .slice_end => try sema.zirSliceEnd(block, inst),
+ .slice_sentinel => try sema.zirSliceSentinel(block, inst),
+ .slice_start => try sema.zirSliceStart(block, inst),
+ .str => try sema.zirStr(block, inst),
+ .sub => try sema.zirArithmetic(block, inst),
+ .subwrap => try sema.zirArithmetic(block, inst),
+ .switch_block => try sema.zirSwitchBlock(block, inst, false, .none),
+ .switch_block_multi => try sema.zirSwitchBlockMulti(block, inst, false, .none),
+ .switch_block_else => try sema.zirSwitchBlock(block, inst, false, .@"else"),
+ .switch_block_else_multi => try sema.zirSwitchBlockMulti(block, inst, false, .@"else"),
+ .switch_block_under => try sema.zirSwitchBlock(block, inst, false, .under),
+ .switch_block_under_multi => try sema.zirSwitchBlockMulti(block, inst, false, .under),
+ .switch_block_ref => try sema.zirSwitchBlock(block, inst, true, .none),
+ .switch_block_ref_multi => try sema.zirSwitchBlockMulti(block, inst, true, .none),
+ .switch_block_ref_else => try sema.zirSwitchBlock(block, inst, true, .@"else"),
+ .switch_block_ref_else_multi => try sema.zirSwitchBlockMulti(block, inst, true, .@"else"),
+ .switch_block_ref_under => try sema.zirSwitchBlock(block, inst, true, .under),
+ .switch_block_ref_under_multi => try sema.zirSwitchBlockMulti(block, inst, true, .under),
+ .switch_capture => try sema.zirSwitchCapture(block, inst, false, false),
+ .switch_capture_ref => try sema.zirSwitchCapture(block, inst, false, true),
+ .switch_capture_multi => try sema.zirSwitchCapture(block, inst, true, false),
+ .switch_capture_multi_ref => try sema.zirSwitchCapture(block, inst, true, true),
+ .switch_capture_else => try sema.zirSwitchCaptureElse(block, inst, false),
+ .switch_capture_else_ref => try sema.zirSwitchCaptureElse(block, inst, true),
+ .typeof => try sema.zirTypeof(block, inst),
+ .typeof_elem => try sema.zirTypeofElem(block, inst),
+ .typeof_peer => try sema.zirTypeofPeer(block, inst),
+ .xor => try sema.zirBitwise(block, inst, .xor),
+ .struct_init_empty => try sema.zirStructInitEmpty(block, inst),
+
+ .struct_decl => try sema.zirStructDecl(block, inst, .Auto),
+ .struct_decl_packed => try sema.zirStructDecl(block, inst, .Packed),
+ .struct_decl_extern => try sema.zirStructDecl(block, inst, .Extern),
+ .enum_decl => try sema.zirEnumDecl(block, inst),
+ .union_decl => try sema.zirUnionDecl(block, inst),
+ .opaque_decl => try sema.zirOpaqueDecl(block, inst),
+
+ // Instructions that we know to *always* be noreturn based solely on their tag.
+ // These functions match the return type of analyzeBody so that we can
+ // tail call them here.
+ .condbr => return sema.zirCondbr(block, inst),
+ .@"break" => return sema.zirBreak(block, inst),
+ .break_inline => return inst,
+ .compile_error => return sema.zirCompileError(block, inst),
+ .ret_coerce => return sema.zirRetTok(block, inst, true),
+ .ret_node => return sema.zirRetNode(block, inst),
+ .ret_tok => return sema.zirRetTok(block, inst, false),
+ .@"unreachable" => return sema.zirUnreachable(block, inst),
+ .repeat => return sema.zirRepeat(block, inst),
+
+ // Instructions that we know can *never* be noreturn based solely on
+ // their tag. We avoid needlessly checking if they are noreturn and
+ // continue the loop.
+ // We also know that they cannot be referenced later, so we avoid
+ // putting them into the map.
+ .breakpoint => {
+ try sema.zirBreakpoint(block, inst);
+ continue;
+ },
+ .dbg_stmt_node => {
+ try sema.zirDbgStmtNode(block, inst);
+ continue;
+ },
+ .ensure_err_payload_void => {
+ try sema.zirEnsureErrPayloadVoid(block, inst);
+ continue;
+ },
+ .ensure_result_non_error => {
+ try sema.zirEnsureResultNonError(block, inst);
+ continue;
+ },
+ .ensure_result_used => {
+ try sema.zirEnsureResultUsed(block, inst);
+ continue;
+ },
+ .compile_log => {
+ try sema.zirCompileLog(block, inst);
+ continue;
+ },
+ .set_eval_branch_quota => {
+ try sema.zirSetEvalBranchQuota(block, inst);
+ continue;
+ },
+ .store => {
+ try sema.zirStore(block, inst);
+ continue;
+ },
+ .store_node => {
+ try sema.zirStoreNode(block, inst);
+ continue;
+ },
+ .store_to_block_ptr => {
+ try sema.zirStoreToBlockPtr(block, inst);
+ continue;
+ },
+ .store_to_inferred_ptr => {
+ try sema.zirStoreToInferredPtr(block, inst);
+ continue;
+ },
+ .resolve_inferred_alloc => {
+ try sema.zirResolveInferredAlloc(block, inst);
+ continue;
+ },
+ .validate_struct_init_ptr => {
+ try sema.zirValidateStructInitPtr(block, inst);
+ continue;
+ },
+
+ // Special case instructions to handle comptime control flow.
+ .repeat_inline => {
+ // Send comptime control flow back to the beginning of this block.
+ const src: LazySrcLoc = .{ .node_offset = datas[inst].node };
+ try sema.emitBackwardBranch(block, src);
+ i = 0;
+ continue;
+ },
+ .block_inline => blk: {
+ // Directly analyze the block body without introducing a new block.
+ const inst_data = datas[inst].pl_node;
+ const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+ const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len];
+ const break_inst = try sema.analyzeBody(block, inline_body);
+ const break_data = datas[break_inst].@"break";
+ if (inst == break_data.block_inst) {
+ break :blk try sema.resolveInst(break_data.operand);
+ } else {
+ return break_inst;
+ }
+ },
+ .condbr_inline => blk: {
+ const inst_data = datas[inst].pl_node;
+ const cond_src: LazySrcLoc = .{ .node_offset_if_cond = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.CondBr, inst_data.payload_index);
+ const then_body = sema.code.extra[extra.end..][0..extra.data.then_body_len];
+ const else_body = sema.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
+ const cond = try sema.resolveInstConst(block, cond_src, extra.data.condition);
+ const inline_body = if (cond.val.toBool()) then_body else else_body;
+ const break_inst = try sema.analyzeBody(block, inline_body);
+ const break_data = datas[break_inst].@"break";
+ if (inst == break_data.block_inst) {
+ break :blk try sema.resolveInst(break_data.operand);
+ } else {
+ return break_inst;
+ }
+ },
+ };
+ if (map[inst].ty.isNoReturn())
+ return always_noreturn;
+ }
+}
+
+/// TODO when we rework TZIR memory layout, this function will no longer have a possible error.
+pub fn resolveInst(sema: *Sema, zir_ref: zir.Inst.Ref) error{OutOfMemory}!*ir.Inst {
+ var i: usize = @enumToInt(zir_ref);
+
+ // First section of indexes correspond to a set number of constant values.
+ if (i < zir.Inst.Ref.typed_value_map.len) {
+ // TODO when we rework TZIR memory layout, this function can be as simple as:
+ // if (zir_ref < zir.const_inst_list.len + sema.param_count)
+ // return zir_ref;
+ // Until then we allocate memory for a new, mutable `ir.Inst` to match what
+ // TZIR expects.
+ return sema.mod.constInst(sema.arena, .unneeded, zir.Inst.Ref.typed_value_map[i]);
+ }
+ i -= zir.Inst.Ref.typed_value_map.len;
+
+ // Next section of indexes correspond to function parameters, if any.
+ if (i < sema.param_inst_list.len) {
+ return sema.param_inst_list[i];
+ }
+ i -= sema.param_inst_list.len;
+
+ // Finally, the last section of indexes refers to the map of ZIR=>TZIR.
+ return sema.inst_map[i];
+}
+
+fn resolveConstString(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ zir_ref: zir.Inst.Ref,
+) ![]u8 {
+ const tzir_inst = try sema.resolveInst(zir_ref);
+ const wanted_type = Type.initTag(.const_slice_u8);
+ const coerced_inst = try sema.coerce(block, wanted_type, tzir_inst, src);
+ const val = try sema.resolveConstValue(block, src, coerced_inst);
+ return val.toAllocatedBytes(sema.arena);
+}
+
+fn resolveType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, zir_ref: zir.Inst.Ref) !Type {
+ const tzir_inst = try sema.resolveInst(zir_ref);
+ const wanted_type = Type.initTag(.@"type");
+ const coerced_inst = try sema.coerce(block, wanted_type, tzir_inst, src);
+ const val = try sema.resolveConstValue(block, src, coerced_inst);
+ return val.toType(sema.arena);
+}
+
+fn resolveConstValue(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, base: *ir.Inst) !Value {
+ return (try sema.resolveDefinedValue(block, src, base)) orelse
+ return sema.failWithNeededComptime(block, src);
+}
+
+fn resolveDefinedValue(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, base: *ir.Inst) !?Value {
+ if (base.value()) |val| {
+ if (val.isUndef()) {
+ return sema.failWithUseOfUndef(block, src);
+ }
+ return val;
+ }
+ return null;
+}
+
+fn failWithNeededComptime(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) InnerError {
+ return sema.mod.fail(&block.base, src, "unable to resolve comptime value", .{});
+}
+
+fn failWithUseOfUndef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) InnerError {
+ return sema.mod.fail(&block.base, src, "use of undefined value here causes undefined behavior", .{});
+}
+
+/// Appropriate to call when the coercion has already been done by result
+/// location semantics. Asserts the value fits in the provided `Int` type.
+/// Only supports `Int` types 64 bits or less.
+fn resolveAlreadyCoercedInt(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ zir_ref: zir.Inst.Ref,
+ comptime Int: type,
+) !Int {
+ comptime assert(@typeInfo(Int).Int.bits <= 64);
+ const tzir_inst = try sema.resolveInst(zir_ref);
+ const val = try sema.resolveConstValue(block, src, tzir_inst);
+ switch (@typeInfo(Int).Int.signedness) {
+ .signed => return @intCast(Int, val.toSignedInt()),
+ .unsigned => return @intCast(Int, val.toUnsignedInt()),
+ }
+}
+
+fn resolveInt(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ zir_ref: zir.Inst.Ref,
+ dest_type: Type,
+) !u64 {
+ const tzir_inst = try sema.resolveInst(zir_ref);
+ const coerced = try sema.coerce(block, dest_type, tzir_inst, src);
+ const val = try sema.resolveConstValue(block, src, coerced);
+
+ return val.toUnsignedInt();
+}
+
+fn resolveInstConst(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ zir_ref: zir.Inst.Ref,
+) InnerError!TypedValue {
+ const tzir_inst = try sema.resolveInst(zir_ref);
+ const val = try sema.resolveConstValue(block, src, tzir_inst);
+ return TypedValue{
+ .ty = tzir_inst.ty,
+ .val = val,
+ };
+}
+
+fn zirConst(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const tv_ptr = sema.code.instructions.items(.data)[inst].@"const";
+ // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions
+ // after analysis. This happens, for example, with variable declaration initialization
+ // expressions.
+ const typed_value_copy = try tv_ptr.copy(sema.arena);
+ return sema.mod.constInst(sema.arena, .unneeded, typed_value_copy);
+}
+
+fn zirBitcastResultPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+ return sema.mod.fail(&block.base, sema.src, "TODO implement zir_sema.zirBitcastResultPtr", .{});
+}
+
+fn zirCoerceResultPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+ return sema.mod.fail(&block.base, sema.src, "TODO implement zirCoerceResultPtr", .{});
+}
+
+fn zirStructDecl(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ layout: std.builtin.TypeInfo.ContainerLayout,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = sema.gpa;
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.StructDecl, inst_data.payload_index);
+ const fields_len = extra.data.fields_len;
+ const bit_bags_count = std.math.divCeil(usize, fields_len, 16) catch unreachable;
+
+ var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
+ errdefer new_decl_arena.deinit();
+
+ var fields_map: std.StringArrayHashMapUnmanaged(Module.Struct.Field) = .{};
+ try fields_map.ensureCapacity(&new_decl_arena.allocator, fields_len);
+
+ {
+ var field_index: usize = extra.end + bit_bags_count;
+ var bit_bag_index: usize = extra.end;
+ var cur_bit_bag: u32 = undefined;
+ var field_i: u32 = 0;
+ while (field_i < fields_len) : (field_i += 1) {
+ if (field_i % 16 == 0) {
+ cur_bit_bag = sema.code.extra[bit_bag_index];
+ bit_bag_index += 1;
+ }
+ const has_align = @truncate(u1, cur_bit_bag) != 0;
+ cur_bit_bag >>= 1;
+ const has_default = @truncate(u1, cur_bit_bag) != 0;
+ cur_bit_bag >>= 1;
+
+ const field_name_zir = sema.code.nullTerminatedString(sema.code.extra[field_index]);
+ field_index += 1;
+ const field_type_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[field_index]);
+ field_index += 1;
+
+ // This string needs to outlive the ZIR code.
+ const field_name = try new_decl_arena.allocator.dupe(u8, field_name_zir);
+ // TODO: if we need to report an error here, use a source location
+ // that points to this type expression rather than the struct.
+ // But only resolve the source location if we need to emit a compile error.
+ const field_ty = try sema.resolveType(block, src, field_type_ref);
+
+ const gop = fields_map.getOrPutAssumeCapacity(field_name);
+ assert(!gop.found_existing);
+ gop.entry.value = .{
+ .ty = field_ty,
+ .abi_align = Value.initTag(.abi_align_default),
+ .default_val = Value.initTag(.unreachable_value),
+ };
+
+ if (has_align) {
+ const align_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[field_index]);
+ field_index += 1;
+ // TODO: if we need to report an error here, use a source location
+ // that points to this alignment expression rather than the struct.
+ // But only resolve the source location if we need to emit a compile error.
+ gop.entry.value.abi_align = (try sema.resolveInstConst(block, src, align_ref)).val;
+ }
+ if (has_default) {
+ const default_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[field_index]);
+ field_index += 1;
+ // TODO: if we need to report an error here, use a source location
+ // that points to this default value expression rather than the struct.
+ // But only resolve the source location if we need to emit a compile error.
+ gop.entry.value.default_val = (try sema.resolveInstConst(block, src, default_ref)).val;
+ }
+ }
+ }
+
+ const struct_obj = try new_decl_arena.allocator.create(Module.Struct);
+ const struct_ty = try Type.Tag.@"struct".create(&new_decl_arena.allocator, struct_obj);
+ struct_obj.* = .{
+ .owner_decl = sema.owner_decl,
+ .fields = fields_map,
+ .node_offset = inst_data.src_node,
+ .container = .{
+ .ty = struct_ty,
+ .file_scope = block.getFileScope(),
+ },
+ };
+ const new_decl = try sema.mod.createAnonymousDecl(&block.base, &new_decl_arena, .{
+ .ty = Type.initTag(.type),
+ .val = try Value.Tag.ty.create(gpa, struct_ty),
+ });
+ return sema.analyzeDeclVal(block, src, new_decl);
+}
+
+fn zirEnumDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+
+ return sema.mod.fail(&block.base, sema.src, "TODO implement zirEnumDecl", .{});
+}
+
+fn zirUnionDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+
+ return sema.mod.fail(&block.base, sema.src, "TODO implement zirUnionDecl", .{});
+}
+
+fn zirOpaqueDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+
+ return sema.mod.fail(&block.base, sema.src, "TODO implement zirOpaqueDecl", .{});
+}
+
+fn zirRetPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const src: LazySrcLoc = .unneeded;
+ try sema.requireFunctionBlock(block, src);
+ const fn_ty = sema.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
+ const ret_type = fn_ty.fnReturnType();
+ const ptr_type = try sema.mod.simplePtrType(sema.arena, ret_type, true, .One);
+ return block.addNoOp(src, ptr_type, .alloc);
+}
+
+fn zirRef(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_tok;
+ const operand = try sema.resolveInst(inst_data.operand);
+ return sema.analyzeRef(block, inst_data.src(), operand);
+}
+
+fn zirRetType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const src: LazySrcLoc = .unneeded;
+ try sema.requireFunctionBlock(block, src);
+ const fn_ty = sema.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
+ const ret_type = fn_ty.fnReturnType();
+ return sema.mod.constType(sema.arena, src, ret_type);
+}
+
+fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const operand = try sema.resolveInst(inst_data.operand);
+ const src = inst_data.src();
+
+ return sema.ensureResultUsed(block, operand, src);
+}
+
+fn ensureResultUsed(
+ sema: *Sema,
+ block: *Scope.Block,
+ operand: *Inst,
+ src: LazySrcLoc,
+) InnerError!void {
+ switch (operand.ty.zigTypeTag()) {
+ .Void, .NoReturn => return,
+ else => return sema.mod.fail(&block.base, src, "expression value is ignored", .{}),
+ }
+}
+
+fn zirEnsureResultNonError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const operand = try sema.resolveInst(inst_data.operand);
+ const src = inst_data.src();
+ switch (operand.ty.zigTypeTag()) {
+ .ErrorSet, .ErrorUnion => return sema.mod.fail(&block.base, src, "error is discarded", .{}),
+ else => return,
+ }
+}
+
+fn zirIndexablePtrLen(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const array_ptr = try sema.resolveInst(inst_data.operand);
+
+ const elem_ty = array_ptr.ty.elemType();
+ if (!elem_ty.isIndexable()) {
+ const cond_src: LazySrcLoc = .{ .node_offset_for_cond = inst_data.src_node };
+ const msg = msg: {
+ const msg = try sema.mod.errMsg(
+ &block.base,
+ cond_src,
+ "type '{}' does not support indexing",
+ .{elem_ty},
+ );
+ errdefer msg.destroy(sema.gpa);
+ try sema.mod.errNote(
+ &block.base,
+ cond_src,
+ msg,
+ "for loop operand must be an array, slice, tuple, or vector",
+ .{},
+ );
+ break :msg msg;
+ };
+ return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
+ }
+ const result_ptr = try sema.namedFieldPtr(block, src, array_ptr, "len", src);
+ return sema.analyzeLoad(block, src, result_ptr, result_ptr.src);
+}
+
+fn zirAlloc(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
+ const var_decl_src = inst_data.src();
+ const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
+ const ptr_type = try sema.mod.simplePtrType(sema.arena, var_type, true, .One);
+ try sema.requireRuntimeBlock(block, var_decl_src);
+ return block.addNoOp(var_decl_src, ptr_type, .alloc);
+}
+
+fn zirAllocMut(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const var_decl_src = inst_data.src();
+ const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
+ const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
+ try sema.validateVarType(block, ty_src, var_type);
+ const ptr_type = try sema.mod.simplePtrType(sema.arena, var_type, true, .One);
+ try sema.requireRuntimeBlock(block, var_decl_src);
+ return block.addNoOp(var_decl_src, ptr_type, .alloc);
+}
+
+fn zirAllocInferred(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ inferred_alloc_ty: Type,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+
+ const val_payload = try sema.arena.create(Value.Payload.InferredAlloc);
+ val_payload.* = .{
+ .data = .{},
+ };
+ // `Module.constInst` does not add the instruction to the block because it is
+ // not needed in the case of constant values. However here, we plan to "downgrade"
+ // to a normal instruction when we hit `resolve_inferred_alloc`. So we append
+ // to the block even though it is currently a `.constant`.
+ const result = try sema.mod.constInst(sema.arena, src, .{
+ .ty = inferred_alloc_ty,
+ .val = Value.initPayload(&val_payload.base),
+ });
+ try sema.requireFunctionBlock(block, src);
+ try block.instructions.append(sema.gpa, result);
+ return result;
+}
+
+fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
+ const ptr = try sema.resolveInst(inst_data.operand);
+ const ptr_val = ptr.castTag(.constant).?.val;
+ const inferred_alloc = ptr_val.castTag(.inferred_alloc).?;
+ const peer_inst_list = inferred_alloc.data.stored_inst_list.items;
+ const final_elem_ty = try sema.resolvePeerTypes(block, ty_src, peer_inst_list);
+ const var_is_mut = switch (ptr.ty.tag()) {
+ .inferred_alloc_const => false,
+ .inferred_alloc_mut => true,
+ else => unreachable,
+ };
+ if (var_is_mut) {
+ try sema.validateVarType(block, ty_src, final_elem_ty);
+ }
+ const final_ptr_ty = try sema.mod.simplePtrType(sema.arena, final_elem_ty, true, .One);
+
+ // Change it to a normal alloc.
+ ptr.ty = final_ptr_ty;
+ ptr.tag = .alloc;
+}
+
+fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+ const instrs = sema.code.extra[extra.end..][0..extra.data.body_len];
+
+ log.warn("TODO implement zirValidateStructInitPtr (compile errors for missing/dupe fields)", .{});
+}
+
+fn zirStoreToBlockPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const bin_inst = sema.code.instructions.items(.data)[inst].bin;
+ const ptr = try sema.resolveInst(bin_inst.lhs);
+ const value = try sema.resolveInst(bin_inst.rhs);
+ const ptr_ty = try sema.mod.simplePtrType(sema.arena, value.ty, true, .One);
+ // TODO detect when this store should be done at compile-time. For example,
+ // if expressions should force it when the condition is compile-time known.
+ const src: LazySrcLoc = .unneeded;
+ try sema.requireRuntimeBlock(block, src);
+ const bitcasted_ptr = try block.addUnOp(src, ptr_ty, .bitcast, ptr);
+ return sema.storePtr(block, src, bitcasted_ptr, value);
+}
+
+fn zirStoreToInferredPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const src: LazySrcLoc = .unneeded;
+ const bin_inst = sema.code.instructions.items(.data)[inst].bin;
+ const ptr = try sema.resolveInst(bin_inst.lhs);
+ const value = try sema.resolveInst(bin_inst.rhs);
+ const inferred_alloc = ptr.castTag(.constant).?.val.castTag(.inferred_alloc).?;
+ // Add the stored instruction to the set we will use to resolve peer types
+ // for the inferred allocation.
+ try inferred_alloc.data.stored_inst_list.append(sema.arena, value);
+ // Create a runtime bitcast instruction with exactly the type the pointer wants.
+ const ptr_ty = try sema.mod.simplePtrType(sema.arena, value.ty, true, .One);
+ try sema.requireRuntimeBlock(block, src);
+ const bitcasted_ptr = try block.addUnOp(src, ptr_ty, .bitcast, ptr);
+ return sema.storePtr(block, src, bitcasted_ptr, value);
+}
+
+fn zirSetEvalBranchQuota(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ try sema.requireFunctionBlock(block, src);
+ const quota = try sema.resolveAlreadyCoercedInt(block, src, inst_data.operand, u32);
+ if (sema.branch_quota < quota)
+ sema.branch_quota = quota;
+}
+
+fn zirStore(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const bin_inst = sema.code.instructions.items(.data)[inst].bin;
+ const ptr = try sema.resolveInst(bin_inst.lhs);
+ const value = try sema.resolveInst(bin_inst.rhs);
+ return sema.storePtr(block, sema.src, ptr, value);
+}
+
+fn zirStoreNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const ptr = try sema.resolveInst(extra.lhs);
+ const value = try sema.resolveInst(extra.rhs);
+ return sema.storePtr(block, src, ptr, value);
+}
+
+fn zirParamType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const src: LazySrcLoc = .unneeded;
+ const inst_data = sema.code.instructions.items(.data)[inst].param_type;
+ const fn_inst = try sema.resolveInst(inst_data.callee);
+ const param_index = inst_data.param_index;
+
+ const fn_ty: Type = switch (fn_inst.ty.zigTypeTag()) {
+ .Fn => fn_inst.ty,
+ .BoundFn => {
+ return sema.mod.fail(&block.base, fn_inst.src, "TODO implement zirParamType for method call syntax", .{});
+ },
+ else => {
+ return sema.mod.fail(&block.base, fn_inst.src, "expected function, found '{}'", .{fn_inst.ty});
+ },
+ };
+
+ const param_count = fn_ty.fnParamLen();
+ if (param_index >= param_count) {
+ if (fn_ty.fnIsVarArgs()) {
+ return sema.mod.constType(sema.arena, src, Type.initTag(.var_args_param));
+ }
+ return sema.mod.fail(&block.base, src, "arg index {d} out of bounds; '{}' has {d} argument(s)", .{
+ param_index,
+ fn_ty,
+ param_count,
+ });
+ }
+
+ // TODO support generic functions
+ const param_type = fn_ty.fnParamType(param_index);
+ return sema.mod.constType(sema.arena, src, param_type);
+}
+
+fn zirStr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const zir_bytes = sema.code.instructions.items(.data)[inst].str.get(sema.code);
+
+ // `zir_bytes` references memory inside the ZIR module, which can get deallocated
+ // after semantic analysis is complete, for example in the case of the initialization
+ // expression of a variable declaration. We need the memory to be in the new
+ // anonymous Decl's arena.
+
+ var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
+ errdefer new_decl_arena.deinit();
+
+ const bytes = try new_decl_arena.allocator.dupe(u8, zir_bytes);
+
+ const decl_ty = try Type.Tag.array_u8_sentinel_0.create(&new_decl_arena.allocator, bytes.len);
+ const decl_val = try Value.Tag.bytes.create(&new_decl_arena.allocator, bytes);
+
+ const new_decl = try sema.mod.createAnonymousDecl(&block.base, &new_decl_arena, .{
+ .ty = decl_ty,
+ .val = decl_val,
+ });
+ return sema.analyzeDeclRef(block, .unneeded, new_decl);
+}
+
+fn zirInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const int = sema.code.instructions.items(.data)[inst].int;
+ return sema.mod.constIntUnsigned(sema.arena, .unneeded, Type.initTag(.comptime_int), int);
+}
+
+fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const msg = try sema.resolveConstString(block, operand_src, inst_data.operand);
+ return sema.mod.fail(&block.base, src, "{s}", .{msg});
+}
+
+fn zirCompileLog(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ var managed = sema.mod.compile_log_text.toManaged(sema.gpa);
+ defer sema.mod.compile_log_text = managed.moveToUnmanaged();
+ const writer = managed.writer();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(zir.Inst.MultiOp, inst_data.payload_index);
+ const args = sema.code.refSlice(extra.end, extra.data.operands_len);
+
+ for (args) |arg_ref, i| {
+ if (i != 0) try writer.print(", ", .{});
+
+ const arg = try sema.resolveInst(arg_ref);
+ if (arg.value()) |val| {
+ try writer.print("@as({}, {})", .{ arg.ty, val });
+ } else {
+ try writer.print("@as({}, [runtime value])", .{arg.ty});
+ }
+ }
+ try writer.print("\n", .{});
+
+ const gop = try sema.mod.compile_log_decls.getOrPut(sema.gpa, sema.owner_decl);
+ if (!gop.found_existing) {
+ gop.entry.value = inst_data.src().toSrcLoc(&block.base);
+ }
+}
+
+fn zirRepeat(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const src_node = sema.code.instructions.items(.data)[inst].node;
+ const src: LazySrcLoc = .{ .node_offset = src_node };
+ try sema.requireRuntimeBlock(block, src);
+ return always_noreturn;
+}
+
+fn zirLoop(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+ const body = sema.code.extra[extra.end..][0..extra.data.body_len];
+
+ // TZIR expects a block outside the loop block too.
+ const block_inst = try sema.arena.create(Inst.Block);
+ block_inst.* = .{
+ .base = .{
+ .tag = Inst.Block.base_tag,
+ .ty = undefined,
+ .src = src,
+ },
+ .body = undefined,
+ };
+
+ var child_block = parent_block.makeSubBlock();
+ child_block.label = Scope.Block.Label{
+ .zir_block = inst,
+ .merges = .{
+ .results = .{},
+ .br_list = .{},
+ .block_inst = block_inst,
+ },
+ };
+ const merges = &child_block.label.?.merges;
+
+ defer child_block.instructions.deinit(sema.gpa);
+ defer merges.results.deinit(sema.gpa);
+ defer merges.br_list.deinit(sema.gpa);
+
+ // Reserve space for a Loop instruction so that generated Break instructions can
+ // point to it, even if it doesn't end up getting used because the code ends up being
+ // comptime evaluated.
+ const loop_inst = try sema.arena.create(Inst.Loop);
+ loop_inst.* = .{
+ .base = .{
+ .tag = Inst.Loop.base_tag,
+ .ty = Type.initTag(.noreturn),
+ .src = src,
+ },
+ .body = undefined,
+ };
+
+ var loop_block = child_block.makeSubBlock();
+ defer loop_block.instructions.deinit(sema.gpa);
+
+ _ = try sema.analyzeBody(&loop_block, body);
+
+ // Loop repetition is implied so the last instruction may or may not be a noreturn instruction.
+
+ try child_block.instructions.append(sema.gpa, &loop_inst.base);
+ loop_inst.body = .{ .instructions = try sema.arena.dupe(*Inst, loop_block.instructions.items) };
+
+ return sema.analyzeBlockBody(parent_block, src, &child_block, merges);
+}
+
+fn zirBlock(sema: *Sema, parent_block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+ const body = sema.code.extra[extra.end..][0..extra.data.body_len];
+
+ // Reserve space for a Block instruction so that generated Break instructions can
+ // point to it, even if it doesn't end up getting used because the code ends up being
+ // comptime evaluated.
+ const block_inst = try sema.arena.create(Inst.Block);
+ block_inst.* = .{
+ .base = .{
+ .tag = Inst.Block.base_tag,
+ .ty = undefined, // Set after analysis.
+ .src = src,
+ },
+ .body = undefined,
+ };
+
+ var child_block: Scope.Block = .{
+ .parent = parent_block,
+ .sema = sema,
+ .src_decl = parent_block.src_decl,
+ .instructions = .{},
+ // TODO @as here is working around a stage1 miscompilation bug :(
+ .label = @as(?Scope.Block.Label, Scope.Block.Label{
+ .zir_block = inst,
+ .merges = .{
+ .results = .{},
+ .br_list = .{},
+ .block_inst = block_inst,
+ },
+ }),
+ .inlining = parent_block.inlining,
+ .is_comptime = parent_block.is_comptime,
+ };
+ const merges = &child_block.label.?.merges;
+
+ defer child_block.instructions.deinit(sema.gpa);
+ defer merges.results.deinit(sema.gpa);
+ defer merges.br_list.deinit(sema.gpa);
+
+ _ = try sema.analyzeBody(&child_block, body);
+
+ return sema.analyzeBlockBody(parent_block, src, &child_block, merges);
+}
+
+fn analyzeBlockBody(
+ sema: *Sema,
+ parent_block: *Scope.Block,
+ src: LazySrcLoc,
+ child_block: *Scope.Block,
+ merges: *Scope.Block.Merges,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ // Blocks must terminate with noreturn instruction.
+ assert(child_block.instructions.items.len != 0);
+ assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn());
+
+ if (merges.results.items.len == 0) {
+ // No need for a block instruction. We can put the new instructions
+ // directly into the parent block.
+ const copied_instructions = try sema.arena.dupe(*Inst, child_block.instructions.items);
+ try parent_block.instructions.appendSlice(sema.gpa, copied_instructions);
+ return copied_instructions[copied_instructions.len - 1];
+ }
+ if (merges.results.items.len == 1) {
+ const last_inst_index = child_block.instructions.items.len - 1;
+ const last_inst = child_block.instructions.items[last_inst_index];
+ if (last_inst.breakBlock()) |br_block| {
+ if (br_block == merges.block_inst) {
+ // No need for a block instruction. We can put the new instructions directly
+ // into the parent block. Here we omit the break instruction.
+ const copied_instructions = try sema.arena.dupe(*Inst, child_block.instructions.items[0..last_inst_index]);
+ try parent_block.instructions.appendSlice(sema.gpa, copied_instructions);
+ return merges.results.items[0];
+ }
+ }
+ }
+ // It is impossible to have the number of results be > 1 in a comptime scope.
+ assert(!child_block.is_comptime); // Should already got a compile error in the condbr condition.
+
+ // Need to set the type and emit the Block instruction. This allows machine code generation
+ // to emit a jump instruction to after the block when it encounters the break.
+ try parent_block.instructions.append(sema.gpa, &merges.block_inst.base);
+ const resolved_ty = try sema.resolvePeerTypes(parent_block, src, merges.results.items);
+ merges.block_inst.base.ty = resolved_ty;
+ merges.block_inst.body = .{
+ .instructions = try sema.arena.dupe(*Inst, child_block.instructions.items),
+ };
+ // Now that the block has its type resolved, we need to go back into all the break
+ // instructions, and insert type coercion on the operands.
+ for (merges.br_list.items) |br| {
+ if (br.operand.ty.eql(resolved_ty)) {
+ // No type coercion needed.
+ continue;
+ }
+ var coerce_block = parent_block.makeSubBlock();
+ defer coerce_block.instructions.deinit(sema.gpa);
+ const coerced_operand = try sema.coerce(&coerce_block, resolved_ty, br.operand, br.operand.src);
+ // If no instructions were produced, such as in the case of a coercion of a
+ // constant value to a new type, we can simply point the br operand to it.
+ if (coerce_block.instructions.items.len == 0) {
+ br.operand = coerced_operand;
+ continue;
+ }
+ assert(coerce_block.instructions.items[coerce_block.instructions.items.len - 1] == coerced_operand);
+ // Here we depend on the br instruction having been over-allocated (if necessary)
+ // inside zirBreak so that it can be converted into a br_block_flat instruction.
+ const br_src = br.base.src;
+ const br_ty = br.base.ty;
+ const br_block_flat = @ptrCast(*Inst.BrBlockFlat, br);
+ br_block_flat.* = .{
+ .base = .{
+ .src = br_src,
+ .ty = br_ty,
+ .tag = .br_block_flat,
+ },
+ .block = merges.block_inst,
+ .body = .{
+ .instructions = try sema.arena.dupe(*Inst, coerce_block.instructions.items),
+ },
+ };
+ }
+ return &merges.block_inst.base;
+}
+
+fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const src_node = sema.code.instructions.items(.data)[inst].node;
+ const src: LazySrcLoc = .{ .node_offset = src_node };
+ try sema.requireRuntimeBlock(block, src);
+ _ = try block.addNoOp(src, Type.initTag(.void), .breakpoint);
+}
+
+fn zirBreak(sema: *Sema, start_block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].@"break";
+ const src = sema.src;
+ const operand = try sema.resolveInst(inst_data.operand);
+ const zir_block = inst_data.block_inst;
+
+ var block = start_block;
+ while (true) {
+ if (block.label) |*label| {
+ if (label.zir_block == zir_block) {
+ // Here we add a br instruction, but we over-allocate a little bit
+ // (if necessary) to make it possible to convert the instruction into
+ // a br_block_flat instruction later.
+ const br = @ptrCast(*Inst.Br, try sema.arena.alignedAlloc(
+ u8,
+ Inst.convertable_br_align,
+ Inst.convertable_br_size,
+ ));
+ br.* = .{
+ .base = .{
+ .tag = .br,
+ .ty = Type.initTag(.noreturn),
+ .src = src,
+ },
+ .operand = operand,
+ .block = label.merges.block_inst,
+ };
+ try start_block.instructions.append(sema.gpa, &br.base);
+ try label.merges.results.append(sema.gpa, operand);
+ try label.merges.br_list.append(sema.gpa, br);
+ return inst;
+ }
+ }
+ block = block.parent.?;
+ }
+}
+
+fn zirDbgStmtNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ // We do not set sema.src here because dbg_stmt instructions are only emitted for
+ // ZIR code that possibly will need to generate runtime code. So error messages
+ // and other source locations must not rely on sema.src being set from dbg_stmt
+ // instructions.
+ if (block.is_comptime) return;
+
+ const src_node = sema.code.instructions.items(.data)[inst].node;
+ const src: LazySrcLoc = .{ .node_offset = src_node };
+
+ const src_loc = src.toSrcLoc(&block.base);
+ const abs_byte_off = try src_loc.byteOffset();
+ _ = try block.addDbgStmt(src, abs_byte_off);
+}
+
+fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const decl = sema.code.decls[inst_data.payload_index];
+ return sema.analyzeDeclRef(block, src, decl);
+}
+
+fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const decl = sema.code.decls[inst_data.payload_index];
+ return sema.analyzeDeclVal(block, src, decl);
+}
+
+fn zirCallNone(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ ensure_result_used: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const func_src: LazySrcLoc = .{ .node_offset_call_func = inst_data.src_node };
+
+ return sema.analyzeCall(block, inst_data.operand, func_src, inst_data.src(), .auto, ensure_result_used, &.{});
+}
+
+fn zirCall(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ modifier: std.builtin.CallOptions.Modifier,
+ ensure_result_used: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const func_src: LazySrcLoc = .{ .node_offset_call_func = inst_data.src_node };
+ const call_src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.Call, inst_data.payload_index);
+ const args = sema.code.refSlice(extra.end, extra.data.args_len);
+
+ return sema.analyzeCall(block, extra.data.callee, func_src, call_src, modifier, ensure_result_used, args);
+}
+
+fn analyzeCall(
+ sema: *Sema,
+ block: *Scope.Block,
+ zir_func: zir.Inst.Ref,
+ func_src: LazySrcLoc,
+ call_src: LazySrcLoc,
+ modifier: std.builtin.CallOptions.Modifier,
+ ensure_result_used: bool,
+ zir_args: []const zir.Inst.Ref,
+) InnerError!*ir.Inst {
+ const func = try sema.resolveInst(zir_func);
+
+ if (func.ty.zigTypeTag() != .Fn)
+ return sema.mod.fail(&block.base, func_src, "type '{}' not a function", .{func.ty});
+
+ const cc = func.ty.fnCallingConvention();
+ if (cc == .Naked) {
+ // TODO add error note: declared here
+ return sema.mod.fail(
+ &block.base,
+ func_src,
+ "unable to call function with naked calling convention",
+ .{},
+ );
+ }
+ const fn_params_len = func.ty.fnParamLen();
+ if (func.ty.fnIsVarArgs()) {
+ assert(cc == .C);
+ if (zir_args.len < fn_params_len) {
+ // TODO add error note: declared here
+ return sema.mod.fail(
+ &block.base,
+ func_src,
+ "expected at least {d} argument(s), found {d}",
+ .{ fn_params_len, zir_args.len },
+ );
+ }
+ } else if (fn_params_len != zir_args.len) {
+ // TODO add error note: declared here
+ return sema.mod.fail(
+ &block.base,
+ func_src,
+ "expected {d} argument(s), found {d}",
+ .{ fn_params_len, zir_args.len },
+ );
+ }
+
+ if (modifier == .compile_time) {
+ return sema.mod.fail(&block.base, call_src, "TODO implement comptime function calls", .{});
+ }
+ if (modifier != .auto) {
+ return sema.mod.fail(&block.base, call_src, "TODO implement call with modifier {}", .{modifier});
+ }
+
+ // TODO handle function calls of generic functions
+ const casted_args = try sema.arena.alloc(*Inst, zir_args.len);
+ for (zir_args) |zir_arg, i| {
+ // the args are already casted to the result of a param type instruction.
+ casted_args[i] = try sema.resolveInst(zir_arg);
+ }
+
+ const ret_type = func.ty.fnReturnType();
+
+ const is_comptime_call = block.is_comptime or modifier == .compile_time;
+ const is_inline_call = is_comptime_call or modifier == .always_inline or
+ func.ty.fnCallingConvention() == .Inline;
+ const result: *Inst = if (is_inline_call) res: {
+ const func_val = try sema.resolveConstValue(block, func_src, func);
+ const module_fn = switch (func_val.tag()) {
+ .function => func_val.castTag(.function).?.data,
+ .extern_fn => return sema.mod.fail(&block.base, call_src, "{s} call of extern function", .{
+ @as([]const u8, if (is_comptime_call) "comptime" else "inline"),
+ }),
+ else => unreachable,
+ };
+
+ // Analyze the ZIR. The same ZIR gets analyzed into a runtime function
+ // or an inlined call depending on what union tag the `label` field is
+ // set to in the `Scope.Block`.
+ // This block instruction will be used to capture the return value from the
+ // inlined function.
+ const block_inst = try sema.arena.create(Inst.Block);
+ block_inst.* = .{
+ .base = .{
+ .tag = Inst.Block.base_tag,
+ .ty = ret_type,
+ .src = call_src,
+ },
+ .body = undefined,
+ };
+ // This one is shared among sub-blocks within the same callee, but not
+ // shared among the entire inline/comptime call stack.
+ var inlining: Scope.Block.Inlining = .{
+ .merges = .{
+ .results = .{},
+ .br_list = .{},
+ .block_inst = block_inst,
+ },
+ };
+ var inline_sema: Sema = .{
+ .mod = sema.mod,
+ .gpa = sema.mod.gpa,
+ .arena = sema.arena,
+ .code = module_fn.zir,
+ .inst_map = try sema.gpa.alloc(*ir.Inst, module_fn.zir.instructions.len),
+ .owner_decl = sema.owner_decl,
+ .owner_func = sema.owner_func,
+ .func = module_fn,
+ .param_inst_list = casted_args,
+ .branch_quota = sema.branch_quota,
+ .branch_count = sema.branch_count,
+ };
+ defer sema.gpa.free(inline_sema.inst_map);
+
+ var child_block: Scope.Block = .{
+ .parent = null,
+ .sema = &inline_sema,
+ .src_decl = module_fn.owner_decl,
+ .instructions = .{},
+ .label = null,
+ .inlining = &inlining,
+ .is_comptime = is_comptime_call,
+ };
+
+ const merges = &child_block.inlining.?.merges;
+
+ defer child_block.instructions.deinit(sema.gpa);
+ defer merges.results.deinit(sema.gpa);
+ defer merges.br_list.deinit(sema.gpa);
+
+ try inline_sema.emitBackwardBranch(&child_block, call_src);
+
+ // This will have return instructions analyzed as break instructions to
+ // the block_inst above.
+ _ = try inline_sema.root(&child_block);
+
+ const result = try inline_sema.analyzeBlockBody(block, call_src, &child_block, merges);
+
+ sema.branch_quota = inline_sema.branch_quota;
+ sema.branch_count = inline_sema.branch_count;
+
+ break :res result;
+ } else res: {
+ try sema.requireRuntimeBlock(block, call_src);
+ break :res try block.addCall(call_src, ret_type, func, casted_args);
+ };
+
+ if (ensure_result_used) {
+ try sema.ensureResultUsed(block, result, call_src);
+ }
+ return result;
+}
+
+fn zirIntType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const int_type = sema.code.instructions.items(.data)[inst].int_type;
+ const src = int_type.src();
+ const ty = try Module.makeIntType(sema.arena, int_type.signedness, int_type.bit_count);
+
+ return sema.mod.constType(sema.arena, src, ty);
+}
+
+fn zirOptionalType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const child_type = try sema.resolveType(block, src, inst_data.operand);
+ const opt_type = try sema.mod.optionalType(sema.arena, child_type);
+
+ return sema.mod.constType(sema.arena, src, opt_type);
+}
+
+fn zirOptionalTypeFromPtrElem(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const ptr = try sema.resolveInst(inst_data.operand);
+ const elem_ty = ptr.ty.elemType();
+ const opt_ty = try sema.mod.optionalType(sema.arena, elem_ty);
+
+ return sema.mod.constType(sema.arena, inst_data.src(), opt_ty);
+}
+
+fn zirArrayType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ // TODO these should be lazily evaluated
+ const bin_inst = sema.code.instructions.items(.data)[inst].bin;
+ const len = try sema.resolveInstConst(block, .unneeded, bin_inst.lhs);
+ const elem_type = try sema.resolveType(block, .unneeded, bin_inst.rhs);
+ const array_ty = try sema.mod.arrayType(sema.arena, len.val.toUnsignedInt(), null, elem_type);
+
+ return sema.mod.constType(sema.arena, .unneeded, array_ty);
+}
+
+fn zirArrayTypeSentinel(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ // TODO these should be lazily evaluated
+ const inst_data = sema.code.instructions.items(.data)[inst].array_type_sentinel;
+ const len = try sema.resolveInstConst(block, .unneeded, inst_data.len);
+ const extra = sema.code.extraData(zir.Inst.ArrayTypeSentinel, inst_data.payload_index).data;
+ const sentinel = try sema.resolveInstConst(block, .unneeded, extra.sentinel);
+ const elem_type = try sema.resolveType(block, .unneeded, extra.elem_type);
+ const array_ty = try sema.mod.arrayType(sema.arena, len.val.toUnsignedInt(), sentinel.val, elem_type);
+
+ return sema.mod.constType(sema.arena, .unneeded, array_ty);
+}
+
+fn zirErrorUnionType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const error_union = try sema.resolveType(block, lhs_src, extra.lhs);
+ const payload = try sema.resolveType(block, rhs_src, extra.rhs);
+
+ if (error_union.zigTypeTag() != .ErrorSet) {
+ return sema.mod.fail(&block.base, lhs_src, "expected error set type, found {}", .{
+ error_union.elemType(),
+ });
+ }
+ const err_union_ty = try sema.mod.errorUnionType(sema.arena, error_union, payload);
+ return sema.mod.constType(sema.arena, src, err_union_ty);
+}
+
+fn zirErrorValue(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
+ const src = inst_data.src();
+
+ // Create an anonymous error set type with only this error value, and return the value.
+ const entry = try sema.mod.getErrorValue(inst_data.get(sema.code));
+ const result_type = try Type.Tag.error_set_single.create(sema.arena, entry.key);
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = result_type,
+ .val = try Value.Tag.@"error".create(sema.arena, .{
+ .name = entry.key,
+ }),
+ });
+}
+
+fn zirErrorToInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const op = try sema.resolveInst(inst_data.operand);
+ const op_coerced = try sema.coerce(block, Type.initTag(.anyerror), op, operand_src);
+
+ if (op_coerced.value()) |val| {
+ const payload = try sema.arena.create(Value.Payload.U64);
+ payload.* = .{
+ .base = .{ .tag = .int_u64 },
+ .data = (try sema.mod.getErrorValue(val.castTag(.@"error").?.data.name)).value,
+ };
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = Type.initTag(.u16),
+ .val = Value.initPayload(&payload.base),
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ return block.addUnOp(src, Type.initTag(.u16), .error_to_int, op_coerced);
+}
+
+fn zirIntToError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+
+ const op = try sema.resolveInst(inst_data.operand);
+
+ if (try sema.resolveDefinedValue(block, operand_src, op)) |value| {
+ const int = value.toUnsignedInt();
+ if (int > sema.mod.global_error_set.count() or int == 0)
+ return sema.mod.fail(&block.base, operand_src, "integer value {d} represents no error", .{int});
+ const payload = try sema.arena.create(Value.Payload.Error);
+ payload.* = .{
+ .base = .{ .tag = .@"error" },
+ .data = .{ .name = sema.mod.error_name_list.items[int] },
+ };
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = Type.initTag(.anyerror),
+ .val = Value.initPayload(&payload.base),
+ });
+ }
+ try sema.requireRuntimeBlock(block, src);
+ if (block.wantSafety()) {
+ return sema.mod.fail(&block.base, src, "TODO: get max errors in compilation", .{});
+ // const is_gt_max = @panic("TODO get max errors in compilation");
+ // try sema.addSafetyCheck(block, is_gt_max, .invalid_error_code);
+ }
+ return block.addUnOp(src, Type.initTag(.anyerror), .int_to_error, op);
+}
+
+fn zirMergeErrorSets(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const lhs_ty = try sema.resolveType(block, lhs_src, extra.lhs);
+ const rhs_ty = try sema.resolveType(block, rhs_src, extra.rhs);
+ if (rhs_ty.zigTypeTag() != .ErrorSet)
+ return sema.mod.fail(&block.base, rhs_src, "expected error set type, found {}", .{rhs_ty});
+ if (lhs_ty.zigTypeTag() != .ErrorSet)
+ return sema.mod.fail(&block.base, lhs_src, "expected error set type, found {}", .{lhs_ty});
+
+ // Anything merged with anyerror is anyerror.
+ if (lhs_ty.tag() == .anyerror or rhs_ty.tag() == .anyerror) {
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.anyerror_type),
+ });
+ }
+ // When we support inferred error sets, we'll want to use a data structure that can
+ // represent a merged set of errors without forcing them to be resolved here. Until then
+ // we re-use the same data structure that is used for explicit error set declarations.
+ var set: std.StringHashMapUnmanaged(void) = .{};
+ defer set.deinit(sema.gpa);
+
+ switch (lhs_ty.tag()) {
+ .error_set_single => {
+ const name = lhs_ty.castTag(.error_set_single).?.data;
+ try set.put(sema.gpa, name, {});
+ },
+ .error_set => {
+ const lhs_set = lhs_ty.castTag(.error_set).?.data;
+ try set.ensureCapacity(sema.gpa, set.count() + lhs_set.names_len);
+ for (lhs_set.names_ptr[0..lhs_set.names_len]) |name| {
+ set.putAssumeCapacityNoClobber(name, {});
+ }
+ },
+ else => unreachable,
+ }
+ switch (rhs_ty.tag()) {
+ .error_set_single => {
+ const name = rhs_ty.castTag(.error_set_single).?.data;
+ try set.put(sema.gpa, name, {});
+ },
+ .error_set => {
+ const rhs_set = rhs_ty.castTag(.error_set).?.data;
+ try set.ensureCapacity(sema.gpa, set.count() + rhs_set.names_len);
+ for (rhs_set.names_ptr[0..rhs_set.names_len]) |name| {
+ set.putAssumeCapacity(name, {});
+ }
+ },
+ else => unreachable,
+ }
+
+ const new_names = try sema.arena.alloc([]const u8, set.count());
+ var it = set.iterator();
+ var i: usize = 0;
+ while (it.next()) |entry| : (i += 1) {
+ new_names[i] = entry.key;
+ }
+
+ const new_error_set = try sema.arena.create(Module.ErrorSet);
+ new_error_set.* = .{
+ .owner_decl = sema.owner_decl,
+ .node_offset = inst_data.src_node,
+ .names_ptr = new_names.ptr,
+ .names_len = @intCast(u32, new_names.len),
+ };
+ const error_set_ty = try Type.Tag.error_set.create(sema.arena, new_error_set);
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = Type.initTag(.type),
+ .val = try Value.Tag.ty.create(sema.arena, error_set_ty),
+ });
+}
+
+fn zirEnumLiteral(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
+ const src = inst_data.src();
+ const duped_name = try sema.arena.dupe(u8, inst_data.get(sema.code));
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = Type.initTag(.enum_literal),
+ .val = try Value.Tag.enum_literal.create(sema.arena, duped_name),
+ });
+}
+
+fn zirEnumLiteralSmall(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const name = sema.code.instructions.items(.data)[inst].small_str.get();
+ const src: LazySrcLoc = .unneeded;
+ const duped_name = try sema.arena.dupe(u8, name);
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = Type.initTag(.enum_literal),
+ .val = try Value.Tag.enum_literal.create(sema.arena, duped_name),
+ });
+}
+
+/// Pointer in, pointer out.
+fn zirOptionalPayloadPtr(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ safety_check: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const optional_ptr = try sema.resolveInst(inst_data.operand);
+ assert(optional_ptr.ty.zigTypeTag() == .Pointer);
+ const src = inst_data.src();
+
+ const opt_type = optional_ptr.ty.elemType();
+ if (opt_type.zigTypeTag() != .Optional) {
+ return sema.mod.fail(&block.base, src, "expected optional type, found {}", .{opt_type});
+ }
+
+ const child_type = try opt_type.optionalChildAlloc(sema.arena);
+ const child_pointer = try sema.mod.simplePtrType(sema.arena, child_type, !optional_ptr.ty.isConstPtr(), .One);
+
+ if (optional_ptr.value()) |pointer_val| {
+ const val = try pointer_val.pointerDeref(sema.arena);
+ if (val.isNull()) {
+ return sema.mod.fail(&block.base, src, "unable to unwrap null", .{});
+ }
+ // The same Value represents the pointer to the optional and the payload.
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = child_pointer,
+ .val = pointer_val,
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ if (safety_check and block.wantSafety()) {
+ const is_non_null = try block.addUnOp(src, Type.initTag(.bool), .is_non_null_ptr, optional_ptr);
+ try sema.addSafetyCheck(block, is_non_null, .unwrap_null);
+ }
+ return block.addUnOp(src, child_pointer, .optional_payload_ptr, optional_ptr);
+}
+
+/// Value in, value out.
+fn zirOptionalPayload(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ safety_check: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand = try sema.resolveInst(inst_data.operand);
+ const opt_type = operand.ty;
+ if (opt_type.zigTypeTag() != .Optional) {
+ return sema.mod.fail(&block.base, src, "expected optional type, found {}", .{opt_type});
+ }
+
+ const child_type = try opt_type.optionalChildAlloc(sema.arena);
+
+ if (operand.value()) |val| {
+ if (val.isNull()) {
+ return sema.mod.fail(&block.base, src, "unable to unwrap null", .{});
+ }
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = child_type,
+ .val = val,
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ if (safety_check and block.wantSafety()) {
+ const is_non_null = try block.addUnOp(src, Type.initTag(.bool), .is_non_null, operand);
+ try sema.addSafetyCheck(block, is_non_null, .unwrap_null);
+ }
+ return block.addUnOp(src, child_type, .optional_payload, operand);
+}
+
+/// Value in, value out
+fn zirErrUnionPayload(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ safety_check: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand = try sema.resolveInst(inst_data.operand);
+ if (operand.ty.zigTypeTag() != .ErrorUnion)
+ return sema.mod.fail(&block.base, operand.src, "expected error union type, found '{}'", .{operand.ty});
+
+ if (operand.value()) |val| {
+ if (val.getError()) |name| {
+ return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name});
+ }
+ const data = val.castTag(.error_union).?.data;
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = operand.ty.castTag(.error_union).?.data.payload,
+ .val = data,
+ });
+ }
+ try sema.requireRuntimeBlock(block, src);
+ if (safety_check and block.wantSafety()) {
+ const is_non_err = try block.addUnOp(src, Type.initTag(.bool), .is_err, operand);
+ try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion);
+ }
+ return block.addUnOp(src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_payload, operand);
+}
+
+/// Pointer in, pointer out.
+fn zirErrUnionPayloadPtr(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ safety_check: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand = try sema.resolveInst(inst_data.operand);
+ assert(operand.ty.zigTypeTag() == .Pointer);
+
+ if (operand.ty.elemType().zigTypeTag() != .ErrorUnion)
+ return sema.mod.fail(&block.base, src, "expected error union type, found {}", .{operand.ty.elemType()});
+
+ const operand_pointer_ty = try sema.mod.simplePtrType(sema.arena, operand.ty.elemType().castTag(.error_union).?.data.payload, !operand.ty.isConstPtr(), .One);
+
+ if (operand.value()) |pointer_val| {
+ const val = try pointer_val.pointerDeref(sema.arena);
+ if (val.getError()) |name| {
+ return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name});
+ }
+ const data = val.castTag(.error_union).?.data;
+ // The same Value represents the pointer to the error union and the payload.
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = operand_pointer_ty,
+ .val = try Value.Tag.ref_val.create(
+ sema.arena,
+ data,
+ ),
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ if (safety_check and block.wantSafety()) {
+ const is_non_err = try block.addUnOp(src, Type.initTag(.bool), .is_err, operand);
+ try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion);
+ }
+ return block.addUnOp(src, operand_pointer_ty, .unwrap_errunion_payload_ptr, operand);
+}
+
+/// Value in, value out
+fn zirErrUnionCode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand = try sema.resolveInst(inst_data.operand);
+ if (operand.ty.zigTypeTag() != .ErrorUnion)
+ return sema.mod.fail(&block.base, src, "expected error union type, found '{}'", .{operand.ty});
+
+ if (operand.value()) |val| {
+ assert(val.getError() != null);
+ const data = val.castTag(.error_union).?.data;
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = operand.ty.castTag(.error_union).?.data.error_set,
+ .val = data,
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ return block.addUnOp(src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_err, operand);
+}
+
+/// Pointer in, value out
+fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand = try sema.resolveInst(inst_data.operand);
+ assert(operand.ty.zigTypeTag() == .Pointer);
+
+ if (operand.ty.elemType().zigTypeTag() != .ErrorUnion)
+ return sema.mod.fail(&block.base, src, "expected error union type, found {}", .{operand.ty.elemType()});
+
+ if (operand.value()) |pointer_val| {
+ const val = try pointer_val.pointerDeref(sema.arena);
+ assert(val.getError() != null);
+ const data = val.castTag(.error_union).?.data;
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = operand.ty.elemType().castTag(.error_union).?.data.error_set,
+ .val = data,
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ return block.addUnOp(src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_err_ptr, operand);
+}
+
+fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_tok;
+ const src = inst_data.src();
+ const operand = try sema.resolveInst(inst_data.operand);
+ if (operand.ty.zigTypeTag() != .ErrorUnion)
+ return sema.mod.fail(&block.base, src, "expected error union type, found '{}'", .{operand.ty});
+ if (operand.ty.castTag(.error_union).?.data.payload.zigTypeTag() != .Void) {
+ return sema.mod.fail(&block.base, src, "expression value is ignored", .{});
+ }
+}
+
+fn zirFnType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index, var_args: bool) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.FnType, inst_data.payload_index);
+ const param_types = sema.code.refSlice(extra.end, extra.data.param_types_len);
+
+ return sema.fnTypeCommon(
+ block,
+ inst_data.src_node,
+ param_types,
+ extra.data.return_type,
+ .Unspecified,
+ var_args,
+ );
+}
+
+fn zirFnTypeCc(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index, var_args: bool) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.FnTypeCc, inst_data.payload_index);
+ const param_types = sema.code.refSlice(extra.end, extra.data.param_types_len);
+
+ const cc_tv = try sema.resolveInstConst(block, cc_src, extra.data.cc);
+ // TODO once we're capable of importing and analyzing decls from
+ // std.builtin, this needs to change
+ const cc_str = cc_tv.val.castTag(.enum_literal).?.data;
+ const cc = std.meta.stringToEnum(std.builtin.CallingConvention, cc_str) orelse
+ return sema.mod.fail(&block.base, cc_src, "Unknown calling convention {s}", .{cc_str});
+ return sema.fnTypeCommon(
+ block,
+ inst_data.src_node,
+ param_types,
+ extra.data.return_type,
+ cc,
+ var_args,
+ );
+}
+
+fn fnTypeCommon(
+ sema: *Sema,
+ block: *Scope.Block,
+ src_node_offset: i32,
+ zir_param_types: []const zir.Inst.Ref,
+ zir_return_type: zir.Inst.Ref,
+ cc: std.builtin.CallingConvention,
+ var_args: bool,
+) InnerError!*Inst {
+ const src: LazySrcLoc = .{ .node_offset = src_node_offset };
+ const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset };
+ const return_type = try sema.resolveType(block, ret_ty_src, zir_return_type);
+
+ // Hot path for some common function types.
+ if (zir_param_types.len == 0 and !var_args) {
+ if (return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) {
+ return sema.mod.constType(sema.arena, src, Type.initTag(.fn_noreturn_no_args));
+ }
+
+ if (return_type.zigTypeTag() == .Void and cc == .Unspecified) {
+ return sema.mod.constType(sema.arena, src, Type.initTag(.fn_void_no_args));
+ }
+
+ if (return_type.zigTypeTag() == .NoReturn and cc == .Naked) {
+ return sema.mod.constType(sema.arena, src, Type.initTag(.fn_naked_noreturn_no_args));
+ }
+
+ if (return_type.zigTypeTag() == .Void and cc == .C) {
+ return sema.mod.constType(sema.arena, src, Type.initTag(.fn_ccc_void_no_args));
+ }
+ }
+
+ const param_types = try sema.arena.alloc(Type, zir_param_types.len);
+ for (zir_param_types) |param_type, i| {
+ // TODO make a compile error from `resolveType` report the source location
+ // of the specific parameter. Will need to take a similar strategy as
+ // `resolveSwitchItemVal` to avoid resolving the source location unless
+ // we actually need to report an error.
+ param_types[i] = try sema.resolveType(block, src, param_type);
+ }
+
+ const fn_ty = try Type.Tag.function.create(sema.arena, .{
+ .param_types = param_types,
+ .return_type = return_type,
+ .cc = cc,
+ .is_var_args = var_args,
+ });
+ return sema.mod.constType(sema.arena, src, fn_ty);
+}
+
+fn zirAs(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const bin_inst = sema.code.instructions.items(.data)[inst].bin;
+ return sema.analyzeAs(block, .unneeded, bin_inst.lhs, bin_inst.rhs);
+}
+
+fn zirAsNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.As, inst_data.payload_index).data;
+ return sema.analyzeAs(block, src, extra.dest_type, extra.operand);
+}
+
+fn analyzeAs(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ zir_dest_type: zir.Inst.Ref,
+ zir_operand: zir.Inst.Ref,
+) InnerError!*Inst {
+ const dest_type = try sema.resolveType(block, src, zir_dest_type);
+ const operand = try sema.resolveInst(zir_operand);
+ return sema.coerce(block, dest_type, operand, src);
+}
+
+fn zirPtrtoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const ptr = try sema.resolveInst(inst_data.operand);
+ if (ptr.ty.zigTypeTag() != .Pointer) {
+ const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ return sema.mod.fail(&block.base, ptr_src, "expected pointer, found '{}'", .{ptr.ty});
+ }
+ // TODO handle known-pointer-address
+ const src = inst_data.src();
+ try sema.requireRuntimeBlock(block, src);
+ const ty = Type.initTag(.usize);
+ return block.addUnOp(src, ty, .ptrtoint, ptr);
+}
+
+fn zirFieldVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.Field, inst_data.payload_index).data;
+ const field_name = sema.code.nullTerminatedString(extra.field_name_start);
+ const object = try sema.resolveInst(extra.lhs);
+ const object_ptr = try sema.analyzeRef(block, src, object);
+ const result_ptr = try sema.namedFieldPtr(block, src, object_ptr, field_name, field_name_src);
+ return sema.analyzeLoad(block, src, result_ptr, result_ptr.src);
+}
+
+fn zirFieldPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.Field, inst_data.payload_index).data;
+ const field_name = sema.code.nullTerminatedString(extra.field_name_start);
+ const object_ptr = try sema.resolveInst(extra.lhs);
+ return sema.namedFieldPtr(block, src, object_ptr, field_name, field_name_src);
+}
+
+fn zirFieldValNamed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.FieldNamed, inst_data.payload_index).data;
+ const object = try sema.resolveInst(extra.lhs);
+ const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name);
+ const object_ptr = try sema.analyzeRef(block, src, object);
+ const result_ptr = try sema.namedFieldPtr(block, src, object_ptr, field_name, field_name_src);
+ return sema.analyzeLoad(block, src, result_ptr, src);
+}
+
+fn zirFieldPtrNamed(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.FieldNamed, inst_data.payload_index).data;
+ const object_ptr = try sema.resolveInst(extra.lhs);
+ const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name);
+ return sema.namedFieldPtr(block, src, object_ptr, field_name, field_name_src);
+}
+
+fn zirIntcast(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+
+ const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs);
+ const operand = try sema.resolveInst(extra.rhs);
+
+ const dest_is_comptime_int = switch (dest_type.zigTypeTag()) {
+ .ComptimeInt => true,
+ .Int => false,
+ else => return sema.mod.fail(
+ &block.base,
+ dest_ty_src,
+ "expected integer type, found '{}'",
+ .{dest_type},
+ ),
+ };
+
+ switch (operand.ty.zigTypeTag()) {
+ .ComptimeInt, .Int => {},
+ else => return sema.mod.fail(
+ &block.base,
+ operand_src,
+ "expected integer type, found '{}'",
+ .{operand.ty},
+ ),
+ }
+
+ if (operand.value() != null) {
+ return sema.coerce(block, dest_type, operand, operand_src);
+ } else if (dest_is_comptime_int) {
+ return sema.mod.fail(&block.base, src, "unable to cast runtime value to 'comptime_int'", .{});
+ }
+
+ return sema.mod.fail(&block.base, src, "TODO implement analyze widen or shorten int", .{});
+}
+
+fn zirBitcast(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+
+ const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs);
+ const operand = try sema.resolveInst(extra.rhs);
+ return sema.bitcast(block, dest_type, operand);
+}
+
+fn zirFloatcast(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+
+ const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs);
+ const operand = try sema.resolveInst(extra.rhs);
+
+ const dest_is_comptime_float = switch (dest_type.zigTypeTag()) {
+ .ComptimeFloat => true,
+ .Float => false,
+ else => return sema.mod.fail(
+ &block.base,
+ dest_ty_src,
+ "expected float type, found '{}'",
+ .{dest_type},
+ ),
+ };
+
+ switch (operand.ty.zigTypeTag()) {
+ .ComptimeFloat, .Float, .ComptimeInt => {},
+ else => return sema.mod.fail(
+ &block.base,
+ operand_src,
+ "expected float type, found '{}'",
+ .{operand.ty},
+ ),
+ }
+
+ if (operand.value() != null) {
+ return sema.coerce(block, dest_type, operand, operand_src);
+ } else if (dest_is_comptime_float) {
+ return sema.mod.fail(&block.base, src, "unable to cast runtime value to 'comptime_float'", .{});
+ }
+
+ return sema.mod.fail(&block.base, src, "TODO implement analyze widen or shorten float", .{});
+}
+
+fn zirElemVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const bin_inst = sema.code.instructions.items(.data)[inst].bin;
+ const array = try sema.resolveInst(bin_inst.lhs);
+ const array_ptr = try sema.analyzeRef(block, sema.src, array);
+ const elem_index = try sema.resolveInst(bin_inst.rhs);
+ const result_ptr = try sema.elemPtr(block, sema.src, array_ptr, elem_index, sema.src);
+ return sema.analyzeLoad(block, sema.src, result_ptr, sema.src);
+}
+
+fn zirElemValNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const elem_index_src: LazySrcLoc = .{ .node_offset_array_access_index = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const array = try sema.resolveInst(extra.lhs);
+ const array_ptr = try sema.analyzeRef(block, src, array);
+ const elem_index = try sema.resolveInst(extra.rhs);
+ const result_ptr = try sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src);
+ return sema.analyzeLoad(block, src, result_ptr, src);
+}
+
+fn zirElemPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const bin_inst = sema.code.instructions.items(.data)[inst].bin;
+ const array_ptr = try sema.resolveInst(bin_inst.lhs);
+ const elem_index = try sema.resolveInst(bin_inst.rhs);
+ return sema.elemPtr(block, sema.src, array_ptr, elem_index, sema.src);
+}
+
+fn zirElemPtrNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const elem_index_src: LazySrcLoc = .{ .node_offset_array_access_index = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const array_ptr = try sema.resolveInst(extra.lhs);
+ const elem_index = try sema.resolveInst(extra.rhs);
+ return sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src);
+}
+
+fn zirSliceStart(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.SliceStart, inst_data.payload_index).data;
+ const array_ptr = try sema.resolveInst(extra.lhs);
+ const start = try sema.resolveInst(extra.start);
+
+ return sema.analyzeSlice(block, src, array_ptr, start, null, null, .unneeded);
+}
+
+fn zirSliceEnd(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.SliceEnd, inst_data.payload_index).data;
+ const array_ptr = try sema.resolveInst(extra.lhs);
+ const start = try sema.resolveInst(extra.start);
+ const end = try sema.resolveInst(extra.end);
+
+ return sema.analyzeSlice(block, src, array_ptr, start, end, null, .unneeded);
+}
+
+fn zirSliceSentinel(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const sentinel_src: LazySrcLoc = .{ .node_offset_slice_sentinel = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.SliceSentinel, inst_data.payload_index).data;
+ const array_ptr = try sema.resolveInst(extra.lhs);
+ const start = try sema.resolveInst(extra.start);
+ const end = try sema.resolveInst(extra.end);
+ const sentinel = try sema.resolveInst(extra.sentinel);
+
+ return sema.analyzeSlice(block, src, array_ptr, start, end, sentinel, sentinel_src);
+}
+
+fn zirSwitchCapture(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ is_multi: bool,
+ is_ref: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const zir_datas = sema.code.instructions.items(.data);
+ const capture_info = zir_datas[inst].switch_capture;
+ const switch_info = zir_datas[capture_info.switch_inst].pl_node;
+ const src = switch_info.src();
+
+ return sema.mod.fail(&block.base, src, "TODO implement Sema for zirSwitchCapture", .{});
+}
+
+fn zirSwitchCaptureElse(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ is_ref: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const zir_datas = sema.code.instructions.items(.data);
+ const capture_info = zir_datas[inst].switch_capture;
+ const switch_info = zir_datas[capture_info.switch_inst].pl_node;
+ const src = switch_info.src();
+
+ return sema.mod.fail(&block.base, src, "TODO implement Sema for zirSwitchCaptureElse", .{});
+}
+
+fn zirSwitchBlock(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ is_ref: bool,
+ special_prong: zir.SpecialProng,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.SwitchBlock, inst_data.payload_index);
+
+ const operand_ptr = try sema.resolveInst(extra.data.operand);
+ const operand = if (is_ref)
+ try sema.analyzeLoad(block, src, operand_ptr, operand_src)
+ else
+ operand_ptr;
+
+ return sema.analyzeSwitch(
+ block,
+ operand,
+ extra.end,
+ special_prong,
+ extra.data.cases_len,
+ 0,
+ inst,
+ inst_data.src_node,
+ );
+}
+
+fn zirSwitchBlockMulti(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ is_ref: bool,
+ special_prong: zir.SpecialProng,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.SwitchBlockMulti, inst_data.payload_index);
+
+ const operand_ptr = try sema.resolveInst(extra.data.operand);
+ const operand = if (is_ref)
+ try sema.analyzeLoad(block, src, operand_ptr, operand_src)
+ else
+ operand_ptr;
+
+ return sema.analyzeSwitch(
+ block,
+ operand,
+ extra.end,
+ special_prong,
+ extra.data.scalar_cases_len,
+ extra.data.multi_cases_len,
+ inst,
+ inst_data.src_node,
+ );
+}
+
+fn analyzeSwitch(
+ sema: *Sema,
+ block: *Scope.Block,
+ operand: *Inst,
+ extra_end: usize,
+ special_prong: zir.SpecialProng,
+ scalar_cases_len: usize,
+ multi_cases_len: usize,
+ switch_inst: zir.Inst.Index,
+ src_node_offset: i32,
+) InnerError!*Inst {
+ const gpa = sema.gpa;
+ const special: struct { body: []const zir.Inst.Index, end: usize } = switch (special_prong) {
+ .none => .{ .body = &.{}, .end = extra_end },
+ .under, .@"else" => blk: {
+ const body_len = sema.code.extra[extra_end];
+ const extra_body_start = extra_end + 1;
+ break :blk .{
+ .body = sema.code.extra[extra_body_start..][0..body_len],
+ .end = extra_body_start + body_len,
+ };
+ },
+ };
+
+ const src: LazySrcLoc = .{ .node_offset = src_node_offset };
+ const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset };
+ const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset };
+
+ // Validate usage of '_' prongs.
+ if (special_prong == .under and !operand.ty.isExhaustiveEnum()) {
+ const msg = msg: {
+ const msg = try sema.mod.errMsg(
+ &block.base,
+ src,
+ "'_' prong only allowed when switching on non-exhaustive enums",
+ .{},
+ );
+ errdefer msg.destroy(gpa);
+ try sema.mod.errNote(
+ &block.base,
+ special_prong_src,
+ msg,
+ "'_' prong here",
+ .{},
+ );
+ break :msg msg;
+ };
+ return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
+ }
+
+ // Validate for duplicate items, missing else prong, and invalid range.
+ switch (operand.ty.zigTypeTag()) {
+ .Enum => return sema.mod.fail(&block.base, src, "TODO validate switch .Enum", .{}),
+ .ErrorSet => return sema.mod.fail(&block.base, src, "TODO validate switch .ErrorSet", .{}),
+ .Union => return sema.mod.fail(&block.base, src, "TODO validate switch .Union", .{}),
+ .Int, .ComptimeInt => {
+ var range_set = RangeSet.init(gpa);
+ defer range_set.deinit();
+
+ var extra_index: usize = special.end;
+ {
+ var scalar_i: u32 = 0;
+ while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
+ const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body = sema.code.extra[extra_index..][0..body_len];
+ extra_index += body_len;
+
+ try sema.validateSwitchItem(
+ block,
+ &range_set,
+ item_ref,
+ src_node_offset,
+ .{ .scalar = scalar_i },
+ );
+ }
+ }
+ {
+ var multi_i: u32 = 0;
+ while (multi_i < multi_cases_len) : (multi_i += 1) {
+ const items_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const ranges_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const items = sema.code.refSlice(extra_index, items_len);
+ extra_index += items_len;
+
+ for (items) |item_ref, item_i| {
+ try sema.validateSwitchItem(
+ block,
+ &range_set,
+ item_ref,
+ src_node_offset,
+ .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
+ );
+ }
+
+ var range_i: u32 = 0;
+ while (range_i < ranges_len) : (range_i += 1) {
+ const item_first = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ const item_last = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+
+ try sema.validateSwitchRange(
+ block,
+ &range_set,
+ item_first,
+ item_last,
+ src_node_offset,
+ .{ .range = .{ .prong = multi_i, .item = range_i } },
+ );
+ }
+
+ extra_index += body_len;
+ }
+ }
+
+ check_range: {
+ if (operand.ty.zigTypeTag() == .Int) {
+ var arena = std.heap.ArenaAllocator.init(gpa);
+ defer arena.deinit();
+
+ const min_int = try operand.ty.minInt(&arena, sema.mod.getTarget());
+ const max_int = try operand.ty.maxInt(&arena, sema.mod.getTarget());
+ if (try range_set.spans(min_int, max_int)) {
+ if (special_prong == .@"else") {
+ return sema.mod.fail(
+ &block.base,
+ special_prong_src,
+ "unreachable else prong; all cases already handled",
+ .{},
+ );
+ }
+ break :check_range;
+ }
+ }
+ if (special_prong != .@"else") {
+ return sema.mod.fail(
+ &block.base,
+ src,
+ "switch must handle all possibilities",
+ .{},
+ );
+ }
+ }
+ },
+ .Bool => {
+ var true_count: u8 = 0;
+ var false_count: u8 = 0;
+
+ var extra_index: usize = special.end;
+ {
+ var scalar_i: u32 = 0;
+ while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
+ const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body = sema.code.extra[extra_index..][0..body_len];
+ extra_index += body_len;
+
+ try sema.validateSwitchItemBool(
+ block,
+ &true_count,
+ &false_count,
+ item_ref,
+ src_node_offset,
+ .{ .scalar = scalar_i },
+ );
+ }
+ }
+ {
+ var multi_i: u32 = 0;
+ while (multi_i < multi_cases_len) : (multi_i += 1) {
+ const items_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const ranges_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const items = sema.code.refSlice(extra_index, items_len);
+ extra_index += items_len + body_len;
+
+ for (items) |item_ref, item_i| {
+ try sema.validateSwitchItemBool(
+ block,
+ &true_count,
+ &false_count,
+ item_ref,
+ src_node_offset,
+ .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
+ );
+ }
+
+ try sema.validateSwitchNoRange(block, ranges_len, operand.ty, src_node_offset);
+ }
+ }
+ switch (special_prong) {
+ .@"else" => {
+ if (true_count + false_count == 2) {
+ return sema.mod.fail(
+ &block.base,
+ src,
+ "unreachable else prong; all cases already handled",
+ .{},
+ );
+ }
+ },
+ .under, .none => {
+ if (true_count + false_count < 2) {
+ return sema.mod.fail(
+ &block.base,
+ src,
+ "switch must handle all possibilities",
+ .{},
+ );
+ }
+ },
+ }
+ },
+ .EnumLiteral, .Void, .Fn, .Pointer, .Type => {
+ if (special_prong != .@"else") {
+ return sema.mod.fail(
+ &block.base,
+ src,
+ "else prong required when switching on type '{}'",
+ .{operand.ty},
+ );
+ }
+
+ var seen_values = ValueSrcMap.init(gpa);
+ defer seen_values.deinit();
+
+ var extra_index: usize = special.end;
+ {
+ var scalar_i: u32 = 0;
+ while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
+ const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body = sema.code.extra[extra_index..][0..body_len];
+ extra_index += body_len;
+
+ try sema.validateSwitchItemSparse(
+ block,
+ &seen_values,
+ item_ref,
+ src_node_offset,
+ .{ .scalar = scalar_i },
+ );
+ }
+ }
+ {
+ var multi_i: u32 = 0;
+ while (multi_i < multi_cases_len) : (multi_i += 1) {
+ const items_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const ranges_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const items = sema.code.refSlice(extra_index, items_len);
+ extra_index += items_len + body_len;
+
+ for (items) |item_ref, item_i| {
+ try sema.validateSwitchItemSparse(
+ block,
+ &seen_values,
+ item_ref,
+ src_node_offset,
+ .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
+ );
+ }
+
+ try sema.validateSwitchNoRange(block, ranges_len, operand.ty, src_node_offset);
+ }
+ }
+ },
+
+ .ErrorUnion,
+ .NoReturn,
+ .Array,
+ .Struct,
+ .Undefined,
+ .Null,
+ .Optional,
+ .BoundFn,
+ .Opaque,
+ .Vector,
+ .Frame,
+ .AnyFrame,
+ .ComptimeFloat,
+ .Float,
+ => return sema.mod.fail(&block.base, operand_src, "invalid switch operand type '{}'", .{
+ operand.ty,
+ }),
+ }
+
+ if (try sema.resolveDefinedValue(block, src, operand)) |operand_val| {
+ var extra_index: usize = special.end;
+ {
+ var scalar_i: usize = 0;
+ while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
+ const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body = sema.code.extra[extra_index..][0..body_len];
+ extra_index += body_len;
+
+ // Validation above ensured these will succeed.
+ const item = sema.resolveInst(item_ref) catch unreachable;
+ const item_val = sema.resolveConstValue(block, .unneeded, item) catch unreachable;
+ if (operand_val.eql(item_val)) {
+ return sema.resolveBody(block, body);
+ }
+ }
+ }
+ {
+ var multi_i: usize = 0;
+ while (multi_i < multi_cases_len) : (multi_i += 1) {
+ const items_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const ranges_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const items = sema.code.refSlice(extra_index, items_len);
+ extra_index += items_len;
+ const body = sema.code.extra[extra_index + 2 * ranges_len ..][0..body_len];
+
+ for (items) |item_ref| {
+ // Validation above ensured these will succeed.
+ const item = sema.resolveInst(item_ref) catch unreachable;
+ const item_val = sema.resolveConstValue(block, item.src, item) catch unreachable;
+ if (operand_val.eql(item_val)) {
+ return sema.resolveBody(block, body);
+ }
+ }
+
+ var range_i: usize = 0;
+ while (range_i < ranges_len) : (range_i += 1) {
+ const item_first = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ const item_last = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+
+ // Validation above ensured these will succeed.
+ const first_tv = sema.resolveInstConst(block, .unneeded, item_first) catch unreachable;
+ const last_tv = sema.resolveInstConst(block, .unneeded, item_last) catch unreachable;
+ if (Value.compare(operand_val, .gte, first_tv.val) and
+ Value.compare(operand_val, .lte, last_tv.val))
+ {
+ return sema.resolveBody(block, body);
+ }
+ }
+
+ extra_index += body_len;
+ }
+ }
+ return sema.resolveBody(block, special.body);
+ }
+
+ if (scalar_cases_len + multi_cases_len == 0) {
+ return sema.resolveBody(block, special.body);
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+
+ const block_inst = try sema.arena.create(Inst.Block);
+ block_inst.* = .{
+ .base = .{
+ .tag = Inst.Block.base_tag,
+ .ty = undefined, // Set after analysis.
+ .src = src,
+ },
+ .body = undefined,
+ };
+
+ var child_block: Scope.Block = .{
+ .parent = block,
+ .sema = sema,
+ .src_decl = block.src_decl,
+ .instructions = .{},
+ // TODO @as here is working around a stage1 miscompilation bug :(
+ .label = @as(?Scope.Block.Label, Scope.Block.Label{
+ .zir_block = switch_inst,
+ .merges = .{
+ .results = .{},
+ .br_list = .{},
+ .block_inst = block_inst,
+ },
+ }),
+ .inlining = block.inlining,
+ .is_comptime = block.is_comptime,
+ };
+ const merges = &child_block.label.?.merges;
+ defer child_block.instructions.deinit(gpa);
+ defer merges.results.deinit(gpa);
+ defer merges.br_list.deinit(gpa);
+
+ // TODO when reworking TZIR memory layout make multi cases get generated as cases,
+ // not as part of the "else" block.
+ const cases = try sema.arena.alloc(Inst.SwitchBr.Case, scalar_cases_len);
+
+ var case_block = child_block.makeSubBlock();
+ defer case_block.instructions.deinit(gpa);
+
+ var extra_index: usize = special.end;
+
+ var scalar_i: usize = 0;
+ while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
+ const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body = sema.code.extra[extra_index..][0..body_len];
+ extra_index += body_len;
+
+ case_block.instructions.shrinkRetainingCapacity(0);
+ // We validate these above; these two calls are guaranteed to succeed.
+ const item = sema.resolveInst(item_ref) catch unreachable;
+ const item_val = sema.resolveConstValue(&case_block, .unneeded, item) catch unreachable;
+
+ _ = try sema.analyzeBody(&case_block, body);
+
+ cases[scalar_i] = .{
+ .item = item_val,
+ .body = .{ .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items) },
+ };
+ }
+
+ var first_else_body: Body = undefined;
+ var prev_condbr: ?*Inst.CondBr = null;
+
+ var multi_i: usize = 0;
+ while (multi_i < multi_cases_len) : (multi_i += 1) {
+ const items_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const ranges_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const items = sema.code.refSlice(extra_index, items_len);
+ extra_index += items_len;
+
+ case_block.instructions.shrinkRetainingCapacity(0);
+
+ var any_ok: ?*Inst = null;
+ const bool_ty = comptime Type.initTag(.bool);
+
+ for (items) |item_ref| {
+ const item = try sema.resolveInst(item_ref);
+ _ = try sema.resolveConstValue(&child_block, item.src, item);
+
+ const cmp_ok = try case_block.addBinOp(item.src, bool_ty, .cmp_eq, operand, item);
+ if (any_ok) |some| {
+ any_ok = try case_block.addBinOp(item.src, bool_ty, .bool_or, some, cmp_ok);
+ } else {
+ any_ok = cmp_ok;
+ }
+ }
+
+ var range_i: usize = 0;
+ while (range_i < ranges_len) : (range_i += 1) {
+ const first_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ const last_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+
+ const item_first = try sema.resolveInst(first_ref);
+ const item_last = try sema.resolveInst(last_ref);
+
+ _ = try sema.resolveConstValue(&child_block, item_first.src, item_first);
+ _ = try sema.resolveConstValue(&child_block, item_last.src, item_last);
+
+ const range_src = item_first.src;
+
+ // operand >= first and operand <= last
+ const range_first_ok = try case_block.addBinOp(
+ item_first.src,
+ bool_ty,
+ .cmp_gte,
+ operand,
+ item_first,
+ );
+ const range_last_ok = try case_block.addBinOp(
+ item_last.src,
+ bool_ty,
+ .cmp_lte,
+ operand,
+ item_last,
+ );
+ const range_ok = try case_block.addBinOp(
+ range_src,
+ bool_ty,
+ .bool_and,
+ range_first_ok,
+ range_last_ok,
+ );
+ if (any_ok) |some| {
+ any_ok = try case_block.addBinOp(range_src, bool_ty, .bool_or, some, range_ok);
+ } else {
+ any_ok = range_ok;
+ }
+ }
+
+ const new_condbr = try sema.arena.create(Inst.CondBr);
+ new_condbr.* = .{
+ .base = .{
+ .tag = .condbr,
+ .ty = Type.initTag(.noreturn),
+ .src = src,
+ },
+ .condition = any_ok.?,
+ .then_body = undefined,
+ .else_body = undefined,
+ };
+ try case_block.instructions.append(gpa, &new_condbr.base);
+
+ const cond_body: Body = .{
+ .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),
+ };
+
+ case_block.instructions.shrinkRetainingCapacity(0);
+ const body = sema.code.extra[extra_index..][0..body_len];
+ extra_index += body_len;
+ _ = try sema.analyzeBody(&case_block, body);
+ new_condbr.then_body = .{
+ .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),
+ };
+ if (prev_condbr) |condbr| {
+ condbr.else_body = cond_body;
+ } else {
+ first_else_body = cond_body;
+ }
+ prev_condbr = new_condbr;
+ }
+
+ const final_else_body: Body = blk: {
+ if (special.body.len != 0) {
+ case_block.instructions.shrinkRetainingCapacity(0);
+ _ = try sema.analyzeBody(&case_block, special.body);
+ const else_body: Body = .{
+ .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),
+ };
+ if (prev_condbr) |condbr| {
+ condbr.else_body = else_body;
+ break :blk first_else_body;
+ } else {
+ break :blk else_body;
+ }
+ } else {
+ break :blk .{ .instructions = &.{} };
+ }
+ };
+
+ _ = try child_block.addSwitchBr(src, operand, cases, final_else_body);
+ return sema.analyzeBlockBody(block, src, &child_block, merges);
+}
+
+fn resolveSwitchItemVal(
+ sema: *Sema,
+ block: *Scope.Block,
+ item_ref: zir.Inst.Ref,
+ switch_node_offset: i32,
+ switch_prong_src: AstGen.SwitchProngSrc,
+ range_expand: AstGen.SwitchProngSrc.RangeExpand,
+) InnerError!Value {
+ const item = try sema.resolveInst(item_ref);
+ // We have to avoid the other helper functions here because we cannot construct a LazySrcLoc
+ // because we only have the switch AST node. Only if we know for sure we need to report
+ // a compile error do we resolve the full source locations.
+ if (item.value()) |val| {
+ if (val.isUndef()) {
+ const src = switch_prong_src.resolve(block.src_decl, switch_node_offset, range_expand);
+ return sema.failWithUseOfUndef(block, src);
+ }
+ return val;
+ }
+ const src = switch_prong_src.resolve(block.src_decl, switch_node_offset, range_expand);
+ return sema.failWithNeededComptime(block, src);
+}
+
+fn validateSwitchRange(
+ sema: *Sema,
+ block: *Scope.Block,
+ range_set: *RangeSet,
+ first_ref: zir.Inst.Ref,
+ last_ref: zir.Inst.Ref,
+ src_node_offset: i32,
+ switch_prong_src: AstGen.SwitchProngSrc,
+) InnerError!void {
+ const first_val = try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first);
+ const last_val = try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last);
+ const maybe_prev_src = try range_set.add(first_val, last_val, switch_prong_src);
+ return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+}
+
+fn validateSwitchItem(
+ sema: *Sema,
+ block: *Scope.Block,
+ range_set: *RangeSet,
+ item_ref: zir.Inst.Ref,
+ src_node_offset: i32,
+ switch_prong_src: AstGen.SwitchProngSrc,
+) InnerError!void {
+ const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
+ const maybe_prev_src = try range_set.add(item_val, item_val, switch_prong_src);
+ return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+}
+
+fn validateSwitchDupe(
+ sema: *Sema,
+ block: *Scope.Block,
+ maybe_prev_src: ?AstGen.SwitchProngSrc,
+ switch_prong_src: AstGen.SwitchProngSrc,
+ src_node_offset: i32,
+) InnerError!void {
+ const prev_prong_src = maybe_prev_src orelse return;
+ const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none);
+ const prev_src = prev_prong_src.resolve(block.src_decl, src_node_offset, .none);
+ const msg = msg: {
+ const msg = try sema.mod.errMsg(
+ &block.base,
+ src,
+ "duplicate switch value",
+ .{},
+ );
+ errdefer msg.destroy(sema.gpa);
+ try sema.mod.errNote(
+ &block.base,
+ prev_src,
+ msg,
+ "previous value here",
+ .{},
+ );
+ break :msg msg;
+ };
+ return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
+}
+
+fn validateSwitchItemBool(
+ sema: *Sema,
+ block: *Scope.Block,
+ true_count: *u8,
+ false_count: *u8,
+ item_ref: zir.Inst.Ref,
+ src_node_offset: i32,
+ switch_prong_src: AstGen.SwitchProngSrc,
+) InnerError!void {
+ const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
+ if (item_val.toBool()) {
+ true_count.* += 1;
+ } else {
+ false_count.* += 1;
+ }
+ if (true_count.* + false_count.* > 2) {
+ const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none);
+ return sema.mod.fail(&block.base, src, "duplicate switch value", .{});
+ }
+}
+
+const ValueSrcMap = std.HashMap(Value, AstGen.SwitchProngSrc, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage);
+
+fn validateSwitchItemSparse(
+ sema: *Sema,
+ block: *Scope.Block,
+ seen_values: *ValueSrcMap,
+ item_ref: zir.Inst.Ref,
+ src_node_offset: i32,
+ switch_prong_src: AstGen.SwitchProngSrc,
+) InnerError!void {
+ const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
+ const entry = (try seen_values.fetchPut(item_val, switch_prong_src)) orelse return;
+ return sema.validateSwitchDupe(block, entry.value, switch_prong_src, src_node_offset);
+}
+
+fn validateSwitchNoRange(
+ sema: *Sema,
+ block: *Scope.Block,
+ ranges_len: u32,
+ operand_ty: Type,
+ src_node_offset: i32,
+) InnerError!void {
+ if (ranges_len == 0)
+ return;
+
+ const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset };
+ const range_src: LazySrcLoc = .{ .node_offset_switch_range = src_node_offset };
+
+ const msg = msg: {
+ const msg = try sema.mod.errMsg(
+ &block.base,
+ operand_src,
+ "ranges not allowed when switching on type '{}'",
+ .{operand_ty},
+ );
+ errdefer msg.destroy(sema.gpa);
+ try sema.mod.errNote(
+ &block.base,
+ range_src,
+ msg,
+ "range here",
+ .{},
+ );
+ break :msg msg;
+ };
+ return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
+}
+
+fn zirImport(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const operand = try sema.resolveConstString(block, operand_src, inst_data.operand);
+
+ const file_scope = sema.analyzeImport(block, src, operand) catch |err| switch (err) {
+ error.ImportOutsidePkgPath => {
+ return sema.mod.fail(&block.base, src, "import of file outside package path: '{s}'", .{operand});
+ },
+ error.FileNotFound => {
+ return sema.mod.fail(&block.base, src, "unable to find '{s}'", .{operand});
+ },
+ else => {
+ // TODO: make sure this gets retried and not cached
+ return sema.mod.fail(&block.base, src, "unable to open '{s}': {s}", .{ operand, @errorName(err) });
+ },
+ };
+ return sema.mod.constType(sema.arena, src, file_scope.root_container.ty);
+}
+
+fn zirShl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+ return sema.mod.fail(&block.base, sema.src, "TODO implement zirShl", .{});
+}
+
+fn zirShr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+ return sema.mod.fail(&block.base, sema.src, "TODO implement zirShr", .{});
+}
+
+fn zirBitwise(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ ir_tag: ir.Inst.Tag,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const lhs = try sema.resolveInst(extra.lhs);
+ const rhs = try sema.resolveInst(extra.rhs);
+
+ const instructions = &[_]*Inst{ lhs, rhs };
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions);
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+
+ const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
+ resolved_type.elemType()
+ else
+ resolved_type;
+
+ const scalar_tag = scalar_type.zigTypeTag();
+
+ if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) {
+ if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
+ return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{
+ lhs.ty.arrayLen(),
+ rhs.ty.arrayLen(),
+ });
+ }
+ return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBitwise", .{});
+ } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) {
+ return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
+ lhs.ty,
+ rhs.ty,
+ });
+ }
+
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+
+ if (!is_int) {
+ return sema.mod.fail(&block.base, src, "invalid operands to binary bitwise expression: '{s}' and '{s}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) });
+ }
+
+ if (casted_lhs.value()) |lhs_val| {
+ if (casted_rhs.value()) |rhs_val| {
+ if (lhs_val.isUndef() or rhs_val.isUndef()) {
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = resolved_type,
+ .val = Value.initTag(.undef),
+ });
+ }
+ return sema.mod.fail(&block.base, src, "TODO implement comptime bitwise operations", .{});
+ }
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ return block.addBinOp(src, scalar_type, ir_tag, casted_lhs, casted_rhs);
+}
+
+fn zirBitNot(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+ return sema.mod.fail(&block.base, sema.src, "TODO implement zirBitNot", .{});
+}
+
+fn zirArrayCat(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+ return sema.mod.fail(&block.base, sema.src, "TODO implement zirArrayCat", .{});
+}
+
+fn zirArrayMul(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+ return sema.mod.fail(&block.base, sema.src, "TODO implement zirArrayMul", .{});
+}
+
+fn zirNegate(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ tag_override: zir.Inst.Tag,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const lhs = try sema.resolveInst(.zero);
+ const rhs = try sema.resolveInst(inst_data.operand);
+
+ return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src);
+}
+
+fn zirArithmetic(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const tag_override = block.sema.code.instructions.items(.tag)[inst];
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const lhs = try sema.resolveInst(extra.lhs);
+ const rhs = try sema.resolveInst(extra.rhs);
+
+ return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src);
+}
+
+fn analyzeArithmetic(
+ sema: *Sema,
+ block: *Scope.Block,
+ zir_tag: zir.Inst.Tag,
+ lhs: *Inst,
+ rhs: *Inst,
+ src: LazySrcLoc,
+ lhs_src: LazySrcLoc,
+ rhs_src: LazySrcLoc,
+) InnerError!*Inst {
+ const instructions = &[_]*Inst{ lhs, rhs };
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions);
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+
+ const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
+ resolved_type.elemType()
+ else
+ resolved_type;
+
+ const scalar_tag = scalar_type.zigTypeTag();
+
+ if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) {
+ if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
+ return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{
+ lhs.ty.arrayLen(),
+ rhs.ty.arrayLen(),
+ });
+ }
+ return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{});
+ } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) {
+ return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
+ lhs.ty,
+ rhs.ty,
+ });
+ }
+
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+ const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat;
+
+ if (!is_int and !(is_float and floatOpAllowed(zir_tag))) {
+ return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) });
+ }
+
+ if (casted_lhs.value()) |lhs_val| {
+ if (casted_rhs.value()) |rhs_val| {
+ if (lhs_val.isUndef() or rhs_val.isUndef()) {
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = resolved_type,
+ .val = Value.initTag(.undef),
+ });
+ }
+ // incase rhs is 0, simply return lhs without doing any calculations
+ // TODO Once division is implemented we should throw an error when dividing by 0.
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = scalar_type,
+ .val = lhs_val,
+ });
+ }
+
+ const value = switch (zir_tag) {
+ .add => blk: {
+ const val = if (is_int)
+ try Module.intAdd(sema.arena, lhs_val, rhs_val)
+ else
+ try Module.floatAdd(sema.arena, scalar_type, src, lhs_val, rhs_val);
+ break :blk val;
+ },
+ .sub => blk: {
+ const val = if (is_int)
+ try Module.intSub(sema.arena, lhs_val, rhs_val)
+ else
+ try Module.floatSub(sema.arena, scalar_type, src, lhs_val, rhs_val);
+ break :blk val;
+ },
+ else => return sema.mod.fail(&block.base, src, "TODO Implement arithmetic operand '{s}'", .{@tagName(zir_tag)}),
+ };
+
+ log.debug("{s}({}, {}) result: {}", .{ @tagName(zir_tag), lhs_val, rhs_val, value });
+
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = scalar_type,
+ .val = value,
+ });
+ }
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ const ir_tag: Inst.Tag = switch (zir_tag) {
+ .add => .add,
+ .addwrap => .addwrap,
+ .sub => .sub,
+ .subwrap => .subwrap,
+ .mul => .mul,
+ .mulwrap => .mulwrap,
+ else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for operand '{s}''", .{@tagName(zir_tag)}),
+ };
+
+ return block.addBinOp(src, scalar_type, ir_tag, casted_lhs, casted_rhs);
+}
+
+fn zirLoad(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const ptr_src: LazySrcLoc = .{ .node_offset_deref_ptr = inst_data.src_node };
+ const ptr = try sema.resolveInst(inst_data.operand);
+ return sema.analyzeLoad(block, src, ptr, ptr_src);
+}
+
+fn zirAsm(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ is_volatile: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const asm_source_src: LazySrcLoc = .{ .node_offset_asm_source = inst_data.src_node };
+ const ret_ty_src: LazySrcLoc = .{ .node_offset_asm_ret_ty = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.Asm, inst_data.payload_index);
+ const return_type = try sema.resolveType(block, ret_ty_src, extra.data.return_type);
+ const asm_source = try sema.resolveConstString(block, asm_source_src, extra.data.asm_source);
+
+ var extra_i = extra.end;
+ const Output = struct { name: []const u8, inst: *Inst };
+ const output: ?Output = if (extra.data.output != .none) blk: {
+ const name = sema.code.nullTerminatedString(sema.code.extra[extra_i]);
+ extra_i += 1;
+ break :blk Output{
+ .name = name,
+ .inst = try sema.resolveInst(extra.data.output),
+ };
+ } else null;
+
+ const args = try sema.arena.alloc(*Inst, extra.data.args_len);
+ const inputs = try sema.arena.alloc([]const u8, extra.data.args_len);
+ const clobbers = try sema.arena.alloc([]const u8, extra.data.clobbers_len);
+
+ for (args) |*arg| {
+ arg.* = try sema.resolveInst(@intToEnum(zir.Inst.Ref, sema.code.extra[extra_i]));
+ extra_i += 1;
+ }
+ for (inputs) |*name| {
+ name.* = sema.code.nullTerminatedString(sema.code.extra[extra_i]);
+ extra_i += 1;
+ }
+ for (clobbers) |*name| {
+ name.* = sema.code.nullTerminatedString(sema.code.extra[extra_i]);
+ extra_i += 1;
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ const asm_tzir = try sema.arena.create(Inst.Assembly);
+ asm_tzir.* = .{
+ .base = .{
+ .tag = .assembly,
+ .ty = return_type,
+ .src = src,
+ },
+ .asm_source = asm_source,
+ .is_volatile = is_volatile,
+ .output = if (output) |o| o.inst else null,
+ .output_name = if (output) |o| o.name else null,
+ .inputs = inputs,
+ .clobbers = clobbers,
+ .args = args,
+ };
+ try block.instructions.append(sema.gpa, &asm_tzir.base);
+ return &asm_tzir.base;
+}
+
+fn zirCmp(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ op: std.math.CompareOperator,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const src: LazySrcLoc = inst_data.src();
+ const lhs = try sema.resolveInst(extra.lhs);
+ const rhs = try sema.resolveInst(extra.rhs);
+
+ const is_equality_cmp = switch (op) {
+ .eq, .neq => true,
+ else => false,
+ };
+ const lhs_ty_tag = lhs.ty.zigTypeTag();
+ const rhs_ty_tag = rhs.ty.zigTypeTag();
+ if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {
+ // null == null, null != null
+ return sema.mod.constBool(sema.arena, src, op == .eq);
+ } else if (is_equality_cmp and
+ ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
+ rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
+ {
+ // comparing null with optionals
+ const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs;
+ return sema.analyzeIsNull(block, src, opt_operand, op == .neq);
+ } else if (is_equality_cmp and
+ ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))
+ {
+ return sema.mod.fail(&block.base, src, "TODO implement C pointer cmp", .{});
+ } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
+ const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty;
+ return sema.mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type});
+ } else if (is_equality_cmp and
+ ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or
+ (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))
+ {
+ return sema.mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
+ } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
+ if (!is_equality_cmp) {
+ return sema.mod.fail(&block.base, src, "{s} operator not allowed for errors", .{@tagName(op)});
+ }
+ if (rhs.value()) |rval| {
+ if (lhs.value()) |lval| {
+ // TODO optimisation oppurtunity: evaluate if std.mem.eql is faster with the names, or calling to Module.getErrorValue to get the values and then compare them is faster
+ return sema.mod.constBool(sema.arena, src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq));
+ }
+ }
+ try sema.requireRuntimeBlock(block, src);
+ return block.addBinOp(src, Type.initTag(.bool), if (op == .eq) .cmp_eq else .cmp_neq, lhs, rhs);
+ } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) {
+ // This operation allows any combination of integer and float types, regardless of the
+ // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
+ // numeric types.
+ return sema.cmpNumeric(block, src, lhs, rhs, op);
+ } else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) {
+ if (!is_equality_cmp) {
+ return sema.mod.fail(&block.base, src, "{s} operator not allowed for types", .{@tagName(op)});
+ }
+ return sema.mod.constBool(sema.arena, src, lhs.value().?.eql(rhs.value().?) == (op == .eq));
+ }
+ return sema.mod.fail(&block.base, src, "TODO implement more cmp analysis", .{});
+}
+
+fn zirTypeof(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand = try sema.resolveInst(inst_data.operand);
+ return sema.mod.constType(sema.arena, src, operand.ty);
+}
+
+fn zirTypeofElem(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand_ptr = try sema.resolveInst(inst_data.operand);
+ const elem_ty = operand_ptr.ty.elemType();
+ return sema.mod.constType(sema.arena, src, elem_ty);
+}
+
+fn zirTypeofPeer(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = sema.code.extraData(zir.Inst.MultiOp, inst_data.payload_index);
+ const args = sema.code.refSlice(extra.end, extra.data.operands_len);
+
+ const inst_list = try sema.gpa.alloc(*ir.Inst, extra.data.operands_len);
+ defer sema.gpa.free(inst_list);
+
+ for (args) |arg_ref, i| {
+ inst_list[i] = try sema.resolveInst(arg_ref);
+ }
+
+ const result_type = try sema.resolvePeerTypes(block, src, inst_list);
+ return sema.mod.constType(sema.arena, src, result_type);
+}
+
+fn zirBoolNot(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const uncasted_operand = try sema.resolveInst(inst_data.operand);
+
+ const bool_type = Type.initTag(.bool);
+ const operand = try sema.coerce(block, bool_type, uncasted_operand, uncasted_operand.src);
+ if (try sema.resolveDefinedValue(block, src, operand)) |val| {
+ return sema.mod.constBool(sema.arena, src, !val.toBool());
+ }
+ try sema.requireRuntimeBlock(block, src);
+ return block.addUnOp(src, bool_type, .not, operand);
+}
+
+fn zirBoolOp(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ comptime is_bool_or: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const src: LazySrcLoc = .unneeded;
+ const bool_type = Type.initTag(.bool);
+ const bin_inst = sema.code.instructions.items(.data)[inst].bin;
+ const uncasted_lhs = try sema.resolveInst(bin_inst.lhs);
+ const lhs = try sema.coerce(block, bool_type, uncasted_lhs, uncasted_lhs.src);
+ const uncasted_rhs = try sema.resolveInst(bin_inst.rhs);
+ const rhs = try sema.coerce(block, bool_type, uncasted_rhs, uncasted_rhs.src);
+
+ if (lhs.value()) |lhs_val| {
+ if (rhs.value()) |rhs_val| {
+ if (is_bool_or) {
+ return sema.mod.constBool(sema.arena, src, lhs_val.toBool() or rhs_val.toBool());
+ } else {
+ return sema.mod.constBool(sema.arena, src, lhs_val.toBool() and rhs_val.toBool());
+ }
+ }
+ }
+ try sema.requireRuntimeBlock(block, src);
+ const tag: ir.Inst.Tag = if (is_bool_or) .bool_or else .bool_and;
+ return block.addBinOp(src, bool_type, tag, lhs, rhs);
+}
+
+fn zirBoolBr(
+ sema: *Sema,
+ parent_block: *Scope.Block,
+ inst: zir.Inst.Index,
+ is_bool_or: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const datas = sema.code.instructions.items(.data);
+ const inst_data = datas[inst].bool_br;
+ const src: LazySrcLoc = .unneeded;
+ const lhs = try sema.resolveInst(inst_data.lhs);
+ const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
+ const body = sema.code.extra[extra.end..][0..extra.data.body_len];
+
+ if (try sema.resolveDefinedValue(parent_block, src, lhs)) |lhs_val| {
+ if (lhs_val.toBool() == is_bool_or) {
+ return sema.mod.constBool(sema.arena, src, is_bool_or);
+ }
+ // comptime-known left-hand side. No need for a block here; the result
+ // is simply the rhs expression. Here we rely on there only being 1
+ // break instruction (`break_inline`).
+ return sema.resolveBody(parent_block, body);
+ }
+
+ const block_inst = try sema.arena.create(Inst.Block);
+ block_inst.* = .{
+ .base = .{
+ .tag = Inst.Block.base_tag,
+ .ty = Type.initTag(.bool),
+ .src = src,
+ },
+ .body = undefined,
+ };
+
+ var child_block = parent_block.makeSubBlock();
+ defer child_block.instructions.deinit(sema.gpa);
+
+ var then_block = child_block.makeSubBlock();
+ defer then_block.instructions.deinit(sema.gpa);
+
+ var else_block = child_block.makeSubBlock();
+ defer else_block.instructions.deinit(sema.gpa);
+
+ const lhs_block = if (is_bool_or) &then_block else &else_block;
+ const rhs_block = if (is_bool_or) &else_block else &then_block;
+
+ const lhs_result = try sema.mod.constInst(sema.arena, src, .{
+ .ty = Type.initTag(.bool),
+ .val = if (is_bool_or) Value.initTag(.bool_true) else Value.initTag(.bool_false),
+ });
+ _ = try lhs_block.addBr(src, block_inst, lhs_result);
+
+ const rhs_result = try sema.resolveBody(rhs_block, body);
+ _ = try rhs_block.addBr(src, block_inst, rhs_result);
+
+ const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, then_block.instructions.items) };
+ const tzir_else_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, rhs_block.instructions.items) };
+ _ = try child_block.addCondBr(src, lhs, tzir_then_body, tzir_else_body);
+
+ block_inst.body = .{
+ .instructions = try sema.arena.dupe(*Inst, child_block.instructions.items),
+ };
+ try parent_block.instructions.append(sema.gpa, &block_inst.base);
+ return &block_inst.base;
+}
+
+fn zirIsNull(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ invert_logic: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand = try sema.resolveInst(inst_data.operand);
+ return sema.analyzeIsNull(block, src, operand, invert_logic);
+}
+
+fn zirIsNullPtr(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ invert_logic: bool,
+) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const ptr = try sema.resolveInst(inst_data.operand);
+ const loaded = try sema.analyzeLoad(block, src, ptr, src);
+ return sema.analyzeIsNull(block, src, loaded, invert_logic);
+}
+
+fn zirIsErr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const operand = try sema.resolveInst(inst_data.operand);
+ return sema.analyzeIsErr(block, inst_data.src(), operand);
+}
+
+fn zirIsErrPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const ptr = try sema.resolveInst(inst_data.operand);
+ const loaded = try sema.analyzeLoad(block, src, ptr, src);
+ return sema.analyzeIsErr(block, src, loaded);
+}
+
+fn zirCondbr(
+ sema: *Sema,
+ parent_block: *Scope.Block,
+ inst: zir.Inst.Index,
+) InnerError!zir.Inst.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const cond_src: LazySrcLoc = .{ .node_offset_if_cond = inst_data.src_node };
+ const extra = sema.code.extraData(zir.Inst.CondBr, inst_data.payload_index);
+
+ const then_body = sema.code.extra[extra.end..][0..extra.data.then_body_len];
+ const else_body = sema.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
+
+ const uncasted_cond = try sema.resolveInst(extra.data.condition);
+ const cond = try sema.coerce(parent_block, Type.initTag(.bool), uncasted_cond, cond_src);
+
+ if (try sema.resolveDefinedValue(parent_block, src, cond)) |cond_val| {
+ const body = if (cond_val.toBool()) then_body else else_body;
+ _ = try sema.analyzeBody(parent_block, body);
+ return always_noreturn;
+ }
+
+ var sub_block = parent_block.makeSubBlock();
+ defer sub_block.instructions.deinit(sema.gpa);
+
+ _ = try sema.analyzeBody(&sub_block, then_body);
+ const tzir_then_body: ir.Body = .{
+ .instructions = try sema.arena.dupe(*Inst, sub_block.instructions.items),
+ };
+
+ sub_block.instructions.shrinkRetainingCapacity(0);
+
+ _ = try sema.analyzeBody(&sub_block, else_body);
+ const tzir_else_body: ir.Body = .{
+ .instructions = try sema.arena.dupe(*Inst, sub_block.instructions.items),
+ };
+
+ _ = try parent_block.addCondBr(src, cond, tzir_then_body, tzir_else_body);
+ return always_noreturn;
+}
+
+fn zirUnreachable(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].@"unreachable";
+ const src = inst_data.src();
+ const safety_check = inst_data.safety;
+ try sema.requireRuntimeBlock(block, src);
+ // TODO Add compile error for @optimizeFor occurring too late in a scope.
+ if (safety_check and block.wantSafety()) {
+ return sema.safetyPanic(block, src, .unreach);
+ } else {
+ _ = try block.addNoOp(src, Type.initTag(.noreturn), .unreach);
+ return always_noreturn;
+ }
+}
+
+fn zirRetTok(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ need_coercion: bool,
+) InnerError!zir.Inst.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_tok;
+ const operand = try sema.resolveInst(inst_data.operand);
+ const src = inst_data.src();
+
+ return sema.analyzeRet(block, operand, src, need_coercion);
+}
+
+fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const operand = try sema.resolveInst(inst_data.operand);
+ const src = inst_data.src();
+
+ return sema.analyzeRet(block, operand, src, false);
+}
+
+fn analyzeRet(
+ sema: *Sema,
+ block: *Scope.Block,
+ operand: *Inst,
+ src: LazySrcLoc,
+ need_coercion: bool,
+) InnerError!zir.Inst.Index {
+ if (block.inlining) |inlining| {
+ // We are inlining a function call; rewrite the `ret` as a `break`.
+ try inlining.merges.results.append(sema.gpa, operand);
+ _ = try block.addBr(src, inlining.merges.block_inst, operand);
+ return always_noreturn;
+ }
+
+ if (need_coercion) {
+ if (sema.func) |func| {
+ const fn_ty = func.owner_decl.typed_value.most_recent.typed_value.ty;
+ const fn_ret_ty = fn_ty.fnReturnType();
+ const casted_operand = try sema.coerce(block, fn_ret_ty, operand, src);
+ if (fn_ret_ty.zigTypeTag() == .Void)
+ _ = try block.addNoOp(src, Type.initTag(.noreturn), .retvoid)
+ else
+ _ = try block.addUnOp(src, Type.initTag(.noreturn), .ret, casted_operand);
+ return always_noreturn;
+ }
+ }
+ _ = try block.addUnOp(src, Type.initTag(.noreturn), .ret, operand);
+ return always_noreturn;
+}
+
+fn floatOpAllowed(tag: zir.Inst.Tag) bool {
+ // extend this swich as additional operators are implemented
+ return switch (tag) {
+ .add, .sub => true,
+ else => false,
+ };
+}
+
+fn zirPtrTypeSimple(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].ptr_type_simple;
+ const elem_type = try sema.resolveType(block, .unneeded, inst_data.elem_type);
+ const ty = try sema.mod.ptrType(
+ sema.arena,
+ elem_type,
+ null,
+ 0,
+ 0,
+ 0,
+ inst_data.is_mutable,
+ inst_data.is_allowzero,
+ inst_data.is_volatile,
+ inst_data.size,
+ );
+ return sema.mod.constType(sema.arena, .unneeded, ty);
+}
+
+fn zirPtrType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const src: LazySrcLoc = .unneeded;
+ const inst_data = sema.code.instructions.items(.data)[inst].ptr_type;
+ const extra = sema.code.extraData(zir.Inst.PtrType, inst_data.payload_index);
+
+ var extra_i = extra.end;
+
+ const sentinel = if (inst_data.flags.has_sentinel) blk: {
+ const ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_i]);
+ extra_i += 1;
+ break :blk (try sema.resolveInstConst(block, .unneeded, ref)).val;
+ } else null;
+
+ const abi_align = if (inst_data.flags.has_align) blk: {
+ const ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_i]);
+ extra_i += 1;
+ break :blk try sema.resolveAlreadyCoercedInt(block, .unneeded, ref, u32);
+ } else 0;
+
+ const bit_start = if (inst_data.flags.has_bit_range) blk: {
+ const ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_i]);
+ extra_i += 1;
+ break :blk try sema.resolveAlreadyCoercedInt(block, .unneeded, ref, u16);
+ } else 0;
+
+ const bit_end = if (inst_data.flags.has_bit_range) blk: {
+ const ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_i]);
+ extra_i += 1;
+ break :blk try sema.resolveAlreadyCoercedInt(block, .unneeded, ref, u16);
+ } else 0;
+
+ if (bit_end != 0 and bit_start >= bit_end * 8)
+ return sema.mod.fail(&block.base, src, "bit offset starts after end of host integer", .{});
+
+ const elem_type = try sema.resolveType(block, .unneeded, extra.data.elem_type);
+
+ const ty = try sema.mod.ptrType(
+ sema.arena,
+ elem_type,
+ sentinel,
+ abi_align,
+ bit_start,
+ bit_end,
+ inst_data.flags.is_mutable,
+ inst_data.flags.is_allowzero,
+ inst_data.flags.is_volatile,
+ inst_data.size,
+ );
+ return sema.mod.constType(sema.arena, src, ty);
+}
+
+fn zirStructInitEmpty(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const struct_type = try sema.resolveType(block, src, inst_data.operand);
+
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = struct_type,
+ .val = Value.initTag(.empty_struct_value),
+ });
+}
+
+fn requireFunctionBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void {
+ if (sema.func == null) {
+ return sema.mod.fail(&block.base, src, "instruction illegal outside function body", .{});
+ }
+}
+
+fn requireRuntimeBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void {
+ if (block.is_comptime) {
+ return sema.mod.fail(&block.base, src, "unable to resolve comptime value", .{});
+ }
+ try sema.requireFunctionBlock(block, src);
+}
+
+fn validateVarType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void {
+ if (!ty.isValidVarType(false)) {
+ return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{ty});
+ }
+}
+
+pub const PanicId = enum {
+ unreach,
+ unwrap_null,
+ unwrap_errunion,
+ invalid_error_code,
+};
+
+fn addSafetyCheck(sema: *Sema, parent_block: *Scope.Block, ok: *Inst, panic_id: PanicId) !void {
+ const block_inst = try sema.arena.create(Inst.Block);
+ block_inst.* = .{
+ .base = .{
+ .tag = Inst.Block.base_tag,
+ .ty = Type.initTag(.void),
+ .src = ok.src,
+ },
+ .body = .{
+ .instructions = try sema.arena.alloc(*Inst, 1), // Only need space for the condbr.
+ },
+ };
+
+ const ok_body: ir.Body = .{
+ .instructions = try sema.arena.alloc(*Inst, 1), // Only need space for the br_void.
+ };
+ const br_void = try sema.arena.create(Inst.BrVoid);
+ br_void.* = .{
+ .base = .{
+ .tag = .br_void,
+ .ty = Type.initTag(.noreturn),
+ .src = ok.src,
+ },
+ .block = block_inst,
+ };
+ ok_body.instructions[0] = &br_void.base;
+
+ var fail_block: Scope.Block = .{
+ .parent = parent_block,
+ .sema = sema,
+ .src_decl = parent_block.src_decl,
+ .instructions = .{},
+ .inlining = parent_block.inlining,
+ .is_comptime = parent_block.is_comptime,
+ };
+
+ defer fail_block.instructions.deinit(sema.gpa);
+
+ _ = try sema.safetyPanic(&fail_block, ok.src, panic_id);
+
+ const fail_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, fail_block.instructions.items) };
+
+ const condbr = try sema.arena.create(Inst.CondBr);
+ condbr.* = .{
+ .base = .{
+ .tag = .condbr,
+ .ty = Type.initTag(.noreturn),
+ .src = ok.src,
+ },
+ .condition = ok,
+ .then_body = ok_body,
+ .else_body = fail_body,
+ };
+ block_inst.body.instructions[0] = &condbr.base;
+
+ try parent_block.instructions.append(sema.gpa, &block_inst.base);
+}
+
+fn safetyPanic(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, panic_id: PanicId) !zir.Inst.Index {
+ // TODO Once we have a panic function to call, call it here instead of breakpoint.
+ _ = try block.addNoOp(src, Type.initTag(.void), .breakpoint);
+ _ = try block.addNoOp(src, Type.initTag(.noreturn), .unreach);
+ return always_noreturn;
+}
+
+fn emitBackwardBranch(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void {
+ sema.branch_count += 1;
+ if (sema.branch_count > sema.branch_quota) {
+ // TODO show the "called from here" stack
+ return sema.mod.fail(&block.base, src, "evaluation exceeded {d} backwards branches", .{sema.branch_quota});
+ }
+}
+
+fn namedFieldPtr(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ object_ptr: *Inst,
+ field_name: []const u8,
+ field_name_src: LazySrcLoc,
+) InnerError!*Inst {
+ const elem_ty = switch (object_ptr.ty.zigTypeTag()) {
+ .Pointer => object_ptr.ty.elemType(),
+ else => return sema.mod.fail(&block.base, object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
+ };
+ switch (elem_ty.zigTypeTag()) {
+ .Array => {
+ if (mem.eql(u8, field_name, "len")) {
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = Type.initTag(.single_const_pointer_to_comptime_int),
+ .val = try Value.Tag.ref_val.create(
+ sema.arena,
+ try Value.Tag.int_u64.create(sema.arena, elem_ty.arrayLen()),
+ ),
+ });
+ } else {
+ return sema.mod.fail(
+ &block.base,
+ field_name_src,
+ "no member named '{s}' in '{}'",
+ .{ field_name, elem_ty },
+ );
+ }
+ },
+ .Pointer => {
+ const ptr_child = elem_ty.elemType();
+ switch (ptr_child.zigTypeTag()) {
+ .Array => {
+ if (mem.eql(u8, field_name, "len")) {
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = Type.initTag(.single_const_pointer_to_comptime_int),
+ .val = try Value.Tag.ref_val.create(
+ sema.arena,
+ try Value.Tag.int_u64.create(sema.arena, ptr_child.arrayLen()),
+ ),
+ });
+ } else {
+ return sema.mod.fail(
+ &block.base,
+ field_name_src,
+ "no member named '{s}' in '{}'",
+ .{ field_name, elem_ty },
+ );
+ }
+ },
+ else => {},
+ }
+ },
+ .Type => {
+ _ = try sema.resolveConstValue(block, object_ptr.src, object_ptr);
+ const result = try sema.analyzeLoad(block, src, object_ptr, object_ptr.src);
+ const val = result.value().?;
+ const child_type = try val.toType(sema.arena);
+ switch (child_type.zigTypeTag()) {
+ .ErrorSet => {
+ // TODO resolve inferred error sets
+ const name: []const u8 = if (child_type.castTag(.error_set)) |payload| blk: {
+ const error_set = payload.data;
+ // TODO this is O(N). I'm putting off solving this until we solve inferred
+ // error sets at the same time.
+ const names = error_set.names_ptr[0..error_set.names_len];
+ for (names) |name| {
+ if (mem.eql(u8, field_name, name)) {
+ break :blk name;
+ }
+ }
+ return sema.mod.fail(&block.base, src, "no error named '{s}' in '{}'", .{
+ field_name,
+ child_type,
+ });
+ } else (try sema.mod.getErrorValue(field_name)).key;
+
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = try sema.mod.simplePtrType(sema.arena, child_type, false, .One),
+ .val = try Value.Tag.ref_val.create(
+ sema.arena,
+ try Value.Tag.@"error".create(sema.arena, .{
+ .name = name,
+ }),
+ ),
+ });
+ },
+ .Struct => {
+ const container_scope = child_type.getContainerScope();
+ if (sema.mod.lookupDeclName(&container_scope.base, field_name)) |decl| {
+ // TODO if !decl.is_pub and inDifferentFiles() "{} is private"
+ return sema.analyzeDeclRef(block, src, decl);
+ }
+
+ if (container_scope.file_scope == sema.mod.root_scope) {
+ return sema.mod.fail(&block.base, src, "root source file has no member called '{s}'", .{field_name});
+ } else {
+ return sema.mod.fail(&block.base, src, "container '{}' has no member called '{s}'", .{ child_type, field_name });
+ }
+ },
+ else => return sema.mod.fail(&block.base, src, "type '{}' does not support field access", .{child_type}),
+ }
+ },
+ .Struct => return sema.analyzeStructFieldPtr(block, src, object_ptr, field_name, field_name_src, elem_ty),
+ else => {},
+ }
+ return sema.mod.fail(&block.base, src, "type '{}' does not support field access", .{elem_ty});
+}
+
+fn analyzeStructFieldPtr(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ struct_ptr: *Inst,
+ field_name: []const u8,
+ field_name_src: LazySrcLoc,
+ elem_ty: Type,
+) InnerError!*Inst {
+ const mod = sema.mod;
+ const arena = sema.arena;
+ assert(elem_ty.zigTypeTag() == .Struct);
+
+ const struct_obj = elem_ty.castTag(.@"struct").?.data;
+
+ const field_index = struct_obj.fields.getIndex(field_name) orelse {
+ // TODO note: struct S declared here
+ return mod.fail(&block.base, field_name_src, "no field named '{s}' in struct '{}'", .{
+ field_name, elem_ty,
+ });
+ };
+ const field = struct_obj.fields.entries.items[field_index].value;
+ const ptr_field_ty = try mod.simplePtrType(arena, field.ty, true, .One);
+ // TODO comptime field access
+ try sema.requireRuntimeBlock(block, src);
+ return block.addStructFieldPtr(src, ptr_field_ty, struct_ptr, @intCast(u32, field_index));
+}
+
+fn elemPtr(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ array_ptr: *Inst,
+ elem_index: *Inst,
+ elem_index_src: LazySrcLoc,
+) InnerError!*Inst {
+ const elem_ty = switch (array_ptr.ty.zigTypeTag()) {
+ .Pointer => array_ptr.ty.elemType(),
+ else => return sema.mod.fail(&block.base, array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}),
+ };
+ if (!elem_ty.isIndexable()) {
+ return sema.mod.fail(&block.base, src, "array access of non-array type '{}'", .{elem_ty});
+ }
+
+ if (elem_ty.isSinglePointer() and elem_ty.elemType().zigTypeTag() == .Array) {
+ // we have to deref the ptr operand to get the actual array pointer
+ const array_ptr_deref = try sema.analyzeLoad(block, src, array_ptr, array_ptr.src);
+ if (array_ptr_deref.value()) |array_ptr_val| {
+ if (elem_index.value()) |index_val| {
+ // Both array pointer and index are compile-time known.
+ const index_u64 = index_val.toUnsignedInt();
+ // @intCast here because it would have been impossible to construct a value that
+ // required a larger index.
+ const elem_ptr = try array_ptr_val.elemPtr(sema.arena, @intCast(usize, index_u64));
+ const pointee_type = elem_ty.elemType().elemType();
+
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = try Type.Tag.single_const_pointer.create(sema.arena, pointee_type),
+ .val = elem_ptr,
+ });
+ }
+ }
+ }
+
+ return sema.mod.fail(&block.base, src, "TODO implement more analyze elemptr", .{});
+}
+
+fn coerce(
+ sema: *Sema,
+ block: *Scope.Block,
+ dest_type: Type,
+ inst: *Inst,
+ inst_src: LazySrcLoc,
+) InnerError!*Inst {
+ if (dest_type.tag() == .var_args_param) {
+ return sema.coerceVarArgParam(block, inst);
+ }
+ // If the types are the same, we can return the operand.
+ if (dest_type.eql(inst.ty))
+ return inst;
+
+ const in_memory_result = coerceInMemoryAllowed(dest_type, inst.ty);
+ if (in_memory_result == .ok) {
+ return sema.bitcast(block, dest_type, inst);
+ }
+
+ // undefined to anything
+ if (inst.value()) |val| {
+ if (val.isUndef() or inst.ty.zigTypeTag() == .Undefined) {
+ return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = val });
+ }
+ }
+ assert(inst.ty.zigTypeTag() != .Undefined);
+
+ // T to E!T or E to E!T
+ if (dest_type.tag() == .error_union) {
+ return try sema.wrapErrorUnion(block, dest_type, inst);
+ }
+
+ // comptime known number to other number
+ if (try sema.coerceNum(block, dest_type, inst)) |some|
+ return some;
+
+ const target = sema.mod.getTarget();
+
+ switch (dest_type.zigTypeTag()) {
+ .Optional => {
+ // null to ?T
+ if (inst.ty.zigTypeTag() == .Null) {
+ return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) });
+ }
+
+ // T to ?T
+ var buf: Type.Payload.ElemType = undefined;
+ const child_type = dest_type.optionalChild(&buf);
+ if (child_type.eql(inst.ty)) {
+ return sema.wrapOptional(block, dest_type, inst);
+ } else if (try sema.coerceNum(block, child_type, inst)) |some| {
+ return sema.wrapOptional(block, dest_type, some);
+ }
+ },
+ .Pointer => {
+ // Coercions where the source is a single pointer to an array.
+ src_array_ptr: {
+ if (!inst.ty.isSinglePointer()) break :src_array_ptr;
+ const array_type = inst.ty.elemType();
+ if (array_type.zigTypeTag() != .Array) break :src_array_ptr;
+ const array_elem_type = array_type.elemType();
+ if (inst.ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr;
+ if (inst.ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr;
+
+ const dst_elem_type = dest_type.elemType();
+ switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) {
+ .ok => {},
+ .no_match => break :src_array_ptr,
+ }
+
+ switch (dest_type.ptrSize()) {
+ .Slice => {
+ // *[N]T to []T
+ return sema.coerceArrayPtrToSlice(block, dest_type, inst);
+ },
+ .C => {
+ // *[N]T to [*c]T
+ return sema.coerceArrayPtrToMany(block, dest_type, inst);
+ },
+ .Many => {
+ // *[N]T to [*]T
+ // *[N:s]T to [*:s]T
+ const src_sentinel = array_type.sentinel();
+ const dst_sentinel = dest_type.sentinel();
+ if (src_sentinel == null and dst_sentinel == null)
+ return sema.coerceArrayPtrToMany(block, dest_type, inst);
+
+ if (src_sentinel) |src_s| {
+ if (dst_sentinel) |dst_s| {
+ if (src_s.eql(dst_s)) {
+ return sema.coerceArrayPtrToMany(block, dest_type, inst);
+ }
+ }
+ }
+ },
+ .One => {},
+ }
+ }
+ },
+ .Int => {
+ // integer widening
+ if (inst.ty.zigTypeTag() == .Int) {
+ assert(inst.value() == null); // handled above
+
+ const dst_info = dest_type.intInfo(target);
+ const src_info = inst.ty.intInfo(target);
+ if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or
+ // small enough unsigned ints can get casted to large enough signed ints
+ (src_info.signedness == .signed and dst_info.signedness == .unsigned and dst_info.bits > src_info.bits))
+ {
+ try sema.requireRuntimeBlock(block, inst_src);
+ return block.addUnOp(inst_src, dest_type, .intcast, inst);
+ }
+ }
+ },
+ .Float => {
+ // float widening
+ if (inst.ty.zigTypeTag() == .Float) {
+ assert(inst.value() == null); // handled above
+
+ const src_bits = inst.ty.floatBits(target);
+ const dst_bits = dest_type.floatBits(target);
+ if (dst_bits >= src_bits) {
+ try sema.requireRuntimeBlock(block, inst_src);
+ return block.addUnOp(inst_src, dest_type, .floatcast, inst);
+ }
+ }
+ },
+ else => {},
+ }
+
+ return sema.mod.fail(&block.base, inst_src, "expected {}, found {}", .{ dest_type, inst.ty });
+}
+
+const InMemoryCoercionResult = enum {
+ ok,
+ no_match,
+};
+
+fn coerceInMemoryAllowed(dest_type: Type, src_type: Type) InMemoryCoercionResult {
+ if (dest_type.eql(src_type))
+ return .ok;
+
+ // TODO: implement more of this function
+
+ return .no_match;
+}
+
+fn coerceNum(sema: *Sema, block: *Scope.Block, dest_type: Type, inst: *Inst) InnerError!?*Inst {
+ const val = inst.value() orelse return null;
+ const src_zig_tag = inst.ty.zigTypeTag();
+ const dst_zig_tag = dest_type.zigTypeTag();
+
+ const target = sema.mod.getTarget();
+
+ if (dst_zig_tag == .ComptimeInt or dst_zig_tag == .Int) {
+ if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
+ if (val.floatHasFraction()) {
+ return sema.mod.fail(&block.base, inst.src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst.ty });
+ }
+ return sema.mod.fail(&block.base, inst.src, "TODO float to int", .{});
+ } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
+ if (!val.intFitsInType(dest_type, target)) {
+ return sema.mod.fail(&block.base, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
+ }
+ return sema.mod.constInst(sema.arena, inst.src, .{ .ty = dest_type, .val = val });
+ }
+ } else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) {
+ if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) {
+ const res = val.floatCast(sema.arena, dest_type, target) catch |err| switch (err) {
+ error.Overflow => return sema.mod.fail(
+ &block.base,
+ inst.src,
+ "cast of value {} to type '{}' loses information",
+ .{ val, dest_type },
+ ),
+ error.OutOfMemory => return error.OutOfMemory,
+ };
+ return sema.mod.constInst(sema.arena, inst.src, .{ .ty = dest_type, .val = res });
+ } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) {
+ return sema.mod.fail(&block.base, inst.src, "TODO int to float", .{});
+ }
+ }
+ return null;
+}
+
+fn coerceVarArgParam(sema: *Sema, block: *Scope.Block, inst: *Inst) !*Inst {
+ switch (inst.ty.zigTypeTag()) {
+ .ComptimeInt, .ComptimeFloat => return sema.mod.fail(&block.base, inst.src, "integer and float literals in var args function must be casted", .{}),
+ else => {},
+ }
+ // TODO implement more of this function.
+ return inst;
+}
+
+fn storePtr(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ ptr: *Inst,
+ uncasted_value: *Inst,
+) !void {
+ if (ptr.ty.isConstPtr())
+ return sema.mod.fail(&block.base, src, "cannot assign to constant", .{});
+
+ const elem_ty = ptr.ty.elemType();
+ const value = try sema.coerce(block, elem_ty, uncasted_value, src);
+ if (elem_ty.onePossibleValue() != null)
+ return;
+
+ // TODO handle comptime pointer writes
+ // TODO handle if the element type requires comptime
+
+ try sema.requireRuntimeBlock(block, src);
+ _ = try block.addBinOp(src, Type.initTag(.void), .store, ptr, value);
+}
+
+fn bitcast(sema: *Sema, block: *Scope.Block, dest_type: Type, inst: *Inst) !*Inst {
+ if (inst.value()) |val| {
+ // Keep the comptime Value representation; take the new type.
+ return sema.mod.constInst(sema.arena, inst.src, .{ .ty = dest_type, .val = val });
+ }
+ // TODO validate the type size and other compile errors
+ try sema.requireRuntimeBlock(block, inst.src);
+ return block.addUnOp(inst.src, dest_type, .bitcast, inst);
+}
+
+fn coerceArrayPtrToSlice(sema: *Sema, block: *Scope.Block, dest_type: Type, inst: *Inst) !*Inst {
+ if (inst.value()) |val| {
+ // The comptime Value representation is compatible with both types.
+ return sema.mod.constInst(sema.arena, inst.src, .{ .ty = dest_type, .val = val });
+ }
+ return sema.mod.fail(&block.base, inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{});
+}
+
+fn coerceArrayPtrToMany(sema: *Sema, block: *Scope.Block, dest_type: Type, inst: *Inst) !*Inst {
+ if (inst.value()) |val| {
+ // The comptime Value representation is compatible with both types.
+ return sema.mod.constInst(sema.arena, inst.src, .{ .ty = dest_type, .val = val });
+ }
+ return sema.mod.fail(&block.base, inst.src, "TODO implement coerceArrayPtrToMany runtime instruction", .{});
+}
+
+fn analyzeDeclVal(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl) InnerError!*Inst {
+ const decl_ref = try sema.analyzeDeclRef(block, src, decl);
+ return sema.analyzeLoad(block, src, decl_ref, src);
+}
+
+fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl) InnerError!*Inst {
+ try sema.mod.declareDeclDependency(sema.owner_decl, decl);
+ sema.mod.ensureDeclAnalyzed(decl) catch |err| {
+ if (sema.func) |func| {
+ func.state = .dependency_failure;
+ } else {
+ sema.owner_decl.analysis = .dependency_failure;
+ }
+ return err;
+ };
+
+ const decl_tv = try decl.typedValue();
+ if (decl_tv.val.tag() == .variable) {
+ return sema.analyzeVarRef(block, src, decl_tv);
+ }
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = try sema.mod.simplePtrType(sema.arena, decl_tv.ty, false, .One),
+ .val = try Value.Tag.decl_ref.create(sema.arena, decl),
+ });
+}
+
+fn analyzeVarRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, tv: TypedValue) InnerError!*Inst {
+ const variable = tv.val.castTag(.variable).?.data;
+
+ const ty = try sema.mod.simplePtrType(sema.arena, tv.ty, variable.is_mutable, .One);
+ if (!variable.is_mutable and !variable.is_extern) {
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = ty,
+ .val = try Value.Tag.ref_val.create(sema.arena, variable.init),
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ const inst = try sema.arena.create(Inst.VarPtr);
+ inst.* = .{
+ .base = .{
+ .tag = .varptr,
+ .ty = ty,
+ .src = src,
+ },
+ .variable = variable,
+ };
+ try block.instructions.append(sema.gpa, &inst.base);
+ return &inst.base;
+}
+
+fn analyzeRef(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ operand: *Inst,
+) InnerError!*Inst {
+ const ptr_type = try sema.mod.simplePtrType(sema.arena, operand.ty, false, .One);
+
+ if (operand.value()) |val| {
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = ptr_type,
+ .val = try Value.Tag.ref_val.create(sema.arena, val),
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ return block.addUnOp(src, ptr_type, .ref, operand);
+}
+
+fn analyzeLoad(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ ptr: *Inst,
+ ptr_src: LazySrcLoc,
+) InnerError!*Inst {
+ const elem_ty = switch (ptr.ty.zigTypeTag()) {
+ .Pointer => ptr.ty.elemType(),
+ else => return sema.mod.fail(&block.base, ptr_src, "expected pointer, found '{}'", .{ptr.ty}),
+ };
+ if (ptr.value()) |val| {
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = elem_ty,
+ .val = try val.pointerDeref(sema.arena),
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ return block.addUnOp(src, elem_ty, .load, ptr);
+}
+
+fn analyzeIsNull(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ operand: *Inst,
+ invert_logic: bool,
+) InnerError!*Inst {
+ if (operand.value()) |opt_val| {
+ const is_null = opt_val.isNull();
+ const bool_value = if (invert_logic) !is_null else is_null;
+ return sema.mod.constBool(sema.arena, src, bool_value);
+ }
+ try sema.requireRuntimeBlock(block, src);
+ const inst_tag: Inst.Tag = if (invert_logic) .is_non_null else .is_null;
+ return block.addUnOp(src, Type.initTag(.bool), inst_tag, operand);
+}
+
+fn analyzeIsErr(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, operand: *Inst) InnerError!*Inst {
+ const ot = operand.ty.zigTypeTag();
+ if (ot != .ErrorSet and ot != .ErrorUnion) return sema.mod.constBool(sema.arena, src, false);
+ if (ot == .ErrorSet) return sema.mod.constBool(sema.arena, src, true);
+ assert(ot == .ErrorUnion);
+ if (operand.value()) |err_union| {
+ return sema.mod.constBool(sema.arena, src, err_union.getError() != null);
+ }
+ try sema.requireRuntimeBlock(block, src);
+ return block.addUnOp(src, Type.initTag(.bool), .is_err, operand);
+}
+
+fn analyzeSlice(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ array_ptr: *Inst,
+ start: *Inst,
+ end_opt: ?*Inst,
+ sentinel_opt: ?*Inst,
+ sentinel_src: LazySrcLoc,
+) InnerError!*Inst {
+ const ptr_child = switch (array_ptr.ty.zigTypeTag()) {
+ .Pointer => array_ptr.ty.elemType(),
+ else => return sema.mod.fail(&block.base, src, "expected pointer, found '{}'", .{array_ptr.ty}),
+ };
+
+ var array_type = ptr_child;
+ const elem_type = switch (ptr_child.zigTypeTag()) {
+ .Array => ptr_child.elemType(),
+ .Pointer => blk: {
+ if (ptr_child.isSinglePointer()) {
+ if (ptr_child.elemType().zigTypeTag() == .Array) {
+ array_type = ptr_child.elemType();
+ break :blk ptr_child.elemType().elemType();
+ }
+
+ return sema.mod.fail(&block.base, src, "slice of single-item pointer", .{});
+ }
+ break :blk ptr_child.elemType();
+ },
+ else => return sema.mod.fail(&block.base, src, "slice of non-array type '{}'", .{ptr_child}),
+ };
+
+ const slice_sentinel = if (sentinel_opt) |sentinel| blk: {
+ const casted = try sema.coerce(block, elem_type, sentinel, sentinel.src);
+ break :blk try sema.resolveConstValue(block, sentinel_src, casted);
+ } else null;
+
+ var return_ptr_size: std.builtin.TypeInfo.Pointer.Size = .Slice;
+ var return_elem_type = elem_type;
+ if (end_opt) |end| {
+ if (end.value()) |end_val| {
+ if (start.value()) |start_val| {
+ const start_u64 = start_val.toUnsignedInt();
+ const end_u64 = end_val.toUnsignedInt();
+ if (start_u64 > end_u64) {
+ return sema.mod.fail(&block.base, src, "out of bounds slice", .{});
+ }
+
+ const len = end_u64 - start_u64;
+ const array_sentinel = if (array_type.zigTypeTag() == .Array and end_u64 == array_type.arrayLen())
+ array_type.sentinel()
+ else
+ slice_sentinel;
+ return_elem_type = try sema.mod.arrayType(sema.arena, len, array_sentinel, elem_type);
+ return_ptr_size = .One;
+ }
+ }
+ }
+ const return_type = try sema.mod.ptrType(
+ sema.arena,
+ return_elem_type,
+ if (end_opt == null) slice_sentinel else null,
+ 0, // TODO alignment
+ 0,
+ 0,
+ !ptr_child.isConstPtr(),
+ ptr_child.isAllowzeroPtr(),
+ ptr_child.isVolatilePtr(),
+ return_ptr_size,
+ );
+
+ return sema.mod.fail(&block.base, src, "TODO implement analysis of slice", .{});
+}
+
+fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_string: []const u8) !*Scope.File {
+ const cur_pkg = block.getFileScope().pkg;
+ const cur_pkg_dir_path = cur_pkg.root_src_directory.path orelse ".";
+ const found_pkg = cur_pkg.table.get(target_string);
+
+ const resolved_path = if (found_pkg) |pkg|
+ try std.fs.path.resolve(sema.gpa, &[_][]const u8{ pkg.root_src_directory.path orelse ".", pkg.root_src_path })
+ else
+ try std.fs.path.resolve(sema.gpa, &[_][]const u8{ cur_pkg_dir_path, target_string });
+ errdefer sema.gpa.free(resolved_path);
+
+ if (sema.mod.import_table.get(resolved_path)) |some| {
+ sema.gpa.free(resolved_path);
+ return some;
+ }
+
+ if (found_pkg == null) {
+ const resolved_root_path = try std.fs.path.resolve(sema.gpa, &[_][]const u8{cur_pkg_dir_path});
+ defer sema.gpa.free(resolved_root_path);
+
+ if (!mem.startsWith(u8, resolved_path, resolved_root_path)) {
+ return error.ImportOutsidePkgPath;
+ }
+ }
+
+ // TODO Scope.Container arena for ty and sub_file_path
+ const file_scope = try sema.gpa.create(Scope.File);
+ errdefer sema.gpa.destroy(file_scope);
+ const struct_ty = try Type.Tag.empty_struct.create(sema.gpa, &file_scope.root_container);
+ errdefer sema.gpa.destroy(struct_ty.castTag(.empty_struct).?);
+
+ file_scope.* = .{
+ .sub_file_path = resolved_path,
+ .source = .{ .unloaded = {} },
+ .tree = undefined,
+ .status = .never_loaded,
+ .pkg = found_pkg orelse cur_pkg,
+ .root_container = .{
+ .file_scope = file_scope,
+ .decls = .{},
+ .ty = struct_ty,
+ },
+ };
+ sema.mod.analyzeContainer(&file_scope.root_container) catch |err| switch (err) {
+ error.AnalysisFail => {
+ assert(sema.mod.comp.totalErrorCount() != 0);
+ },
+ else => |e| return e,
+ };
+ try sema.mod.import_table.put(sema.gpa, file_scope.sub_file_path, file_scope);
+ return file_scope;
+}
+
+/// Asserts that lhs and rhs types are both numeric.
+fn cmpNumeric(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ lhs: *Inst,
+ rhs: *Inst,
+ op: std.math.CompareOperator,
+) InnerError!*Inst {
+ assert(lhs.ty.isNumeric());
+ assert(rhs.ty.isNumeric());
+
+ const lhs_ty_tag = lhs.ty.zigTypeTag();
+ const rhs_ty_tag = rhs.ty.zigTypeTag();
+
+ if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) {
+ if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
+ return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{
+ lhs.ty.arrayLen(),
+ rhs.ty.arrayLen(),
+ });
+ }
+ return sema.mod.fail(&block.base, src, "TODO implement support for vectors in cmpNumeric", .{});
+ } else if (lhs_ty_tag == .Vector or rhs_ty_tag == .Vector) {
+ return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{
+ lhs.ty,
+ rhs.ty,
+ });
+ }
+
+ if (lhs.value()) |lhs_val| {
+ if (rhs.value()) |rhs_val| {
+ return sema.mod.constBool(sema.arena, src, Value.compare(lhs_val, op, rhs_val));
+ }
+ }
+
+ // TODO handle comparisons against lazy zero values
+ // Some values can be compared against zero without being runtime known or without forcing
+ // a full resolution of their value, for example `@sizeOf(@Frame(function))` is known to
+ // always be nonzero, and we benefit from not forcing the full evaluation and stack frame layout
+ // of this function if we don't need to.
+
+ // It must be a runtime comparison.
+ try sema.requireRuntimeBlock(block, src);
+ // For floats, emit a float comparison instruction.
+ const lhs_is_float = switch (lhs_ty_tag) {
+ .Float, .ComptimeFloat => true,
+ else => false,
+ };
+ const rhs_is_float = switch (rhs_ty_tag) {
+ .Float, .ComptimeFloat => true,
+ else => false,
+ };
+ const target = sema.mod.getTarget();
+ if (lhs_is_float and rhs_is_float) {
+ // Implicit cast the smaller one to the larger one.
+ const dest_type = x: {
+ if (lhs_ty_tag == .ComptimeFloat) {
+ break :x rhs.ty;
+ } else if (rhs_ty_tag == .ComptimeFloat) {
+ break :x lhs.ty;
+ }
+ if (lhs.ty.floatBits(target) >= rhs.ty.floatBits(target)) {
+ break :x lhs.ty;
+ } else {
+ break :x rhs.ty;
+ }
+ };
+ const casted_lhs = try sema.coerce(block, dest_type, lhs, lhs.src);
+ const casted_rhs = try sema.coerce(block, dest_type, rhs, rhs.src);
+ return block.addBinOp(src, dest_type, Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs);
+ }
+ // For mixed unsigned integer sizes, implicit cast both operands to the larger integer.
+ // For mixed signed and unsigned integers, implicit cast both operands to a signed
+ // integer with + 1 bit.
+ // For mixed floats and integers, extract the integer part from the float, cast that to
+ // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float,
+ // add/subtract 1.
+ const lhs_is_signed = if (lhs.value()) |lhs_val|
+ lhs_val.compareWithZero(.lt)
+ else
+ (lhs.ty.isFloat() or lhs.ty.isSignedInt());
+ const rhs_is_signed = if (rhs.value()) |rhs_val|
+ rhs_val.compareWithZero(.lt)
+ else
+ (rhs.ty.isFloat() or rhs.ty.isSignedInt());
+ const dest_int_is_signed = lhs_is_signed or rhs_is_signed;
+
+ var dest_float_type: ?Type = null;
+
+ var lhs_bits: usize = undefined;
+ if (lhs.value()) |lhs_val| {
+ if (lhs_val.isUndef())
+ return sema.mod.constUndef(sema.arena, src, Type.initTag(.bool));
+ const is_unsigned = if (lhs_is_float) x: {
+ var bigint_space: Value.BigIntSpace = undefined;
+ var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(sema.gpa);
+ defer bigint.deinit();
+ const zcmp = lhs_val.orderAgainstZero();
+ if (lhs_val.floatHasFraction()) {
+ switch (op) {
+ .eq => return sema.mod.constBool(sema.arena, src, false),
+ .neq => return sema.mod.constBool(sema.arena, src, true),
+ else => {},
+ }
+ if (zcmp == .lt) {
+ try bigint.addScalar(bigint.toConst(), -1);
+ } else {
+ try bigint.addScalar(bigint.toConst(), 1);
+ }
+ }
+ lhs_bits = bigint.toConst().bitCountTwosComp();
+ break :x (zcmp != .lt);
+ } else x: {
+ lhs_bits = lhs_val.intBitCountTwosComp();
+ break :x (lhs_val.orderAgainstZero() != .lt);
+ };
+ lhs_bits += @boolToInt(is_unsigned and dest_int_is_signed);
+ } else if (lhs_is_float) {
+ dest_float_type = lhs.ty;
+ } else {
+ const int_info = lhs.ty.intInfo(target);
+ lhs_bits = int_info.bits + @boolToInt(int_info.signedness == .unsigned and dest_int_is_signed);
+ }
+
+ var rhs_bits: usize = undefined;
+ if (rhs.value()) |rhs_val| {
+ if (rhs_val.isUndef())
+ return sema.mod.constUndef(sema.arena, src, Type.initTag(.bool));
+ const is_unsigned = if (rhs_is_float) x: {
+ var bigint_space: Value.BigIntSpace = undefined;
+ var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(sema.gpa);
+ defer bigint.deinit();
+ const zcmp = rhs_val.orderAgainstZero();
+ if (rhs_val.floatHasFraction()) {
+ switch (op) {
+ .eq => return sema.mod.constBool(sema.arena, src, false),
+ .neq => return sema.mod.constBool(sema.arena, src, true),
+ else => {},
+ }
+ if (zcmp == .lt) {
+ try bigint.addScalar(bigint.toConst(), -1);
+ } else {
+ try bigint.addScalar(bigint.toConst(), 1);
+ }
+ }
+ rhs_bits = bigint.toConst().bitCountTwosComp();
+ break :x (zcmp != .lt);
+ } else x: {
+ rhs_bits = rhs_val.intBitCountTwosComp();
+ break :x (rhs_val.orderAgainstZero() != .lt);
+ };
+ rhs_bits += @boolToInt(is_unsigned and dest_int_is_signed);
+ } else if (rhs_is_float) {
+ dest_float_type = rhs.ty;
+ } else {
+ const int_info = rhs.ty.intInfo(target);
+ rhs_bits = int_info.bits + @boolToInt(int_info.signedness == .unsigned and dest_int_is_signed);
+ }
+
+ const dest_type = if (dest_float_type) |ft| ft else blk: {
+ const max_bits = std.math.max(lhs_bits, rhs_bits);
+ const casted_bits = std.math.cast(u16, max_bits) catch |err| switch (err) {
+ error.Overflow => return sema.mod.fail(&block.base, src, "{d} exceeds maximum integer bit count", .{max_bits}),
+ };
+ const signedness: std.builtin.Signedness = if (dest_int_is_signed) .signed else .unsigned;
+ break :blk try Module.makeIntType(sema.arena, signedness, casted_bits);
+ };
+ const casted_lhs = try sema.coerce(block, dest_type, lhs, lhs.src);
+ const casted_rhs = try sema.coerce(block, dest_type, rhs, rhs.src);
+
+ return block.addBinOp(src, Type.initTag(.bool), Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs);
+}
+
+fn wrapOptional(sema: *Sema, block: *Scope.Block, dest_type: Type, inst: *Inst) !*Inst {
+ if (inst.value()) |val| {
+ return sema.mod.constInst(sema.arena, inst.src, .{ .ty = dest_type, .val = val });
+ }
+
+ try sema.requireRuntimeBlock(block, inst.src);
+ return block.addUnOp(inst.src, dest_type, .wrap_optional, inst);
+}
+
+fn wrapErrorUnion(sema: *Sema, block: *Scope.Block, dest_type: Type, inst: *Inst) !*Inst {
+ // TODO deal with inferred error sets
+ const err_union = dest_type.castTag(.error_union).?;
+ if (inst.value()) |val| {
+ const to_wrap = if (inst.ty.zigTypeTag() != .ErrorSet) blk: {
+ _ = try sema.coerce(block, err_union.data.payload, inst, inst.src);
+ break :blk val;
+ } else switch (err_union.data.error_set.tag()) {
+ .anyerror => val,
+ .error_set_single => blk: {
+ const expected_name = val.castTag(.@"error").?.data.name;
+ const n = err_union.data.error_set.castTag(.error_set_single).?.data;
+ if (!mem.eql(u8, expected_name, n)) {
+ return sema.mod.fail(
+ &block.base,
+ inst.src,
+ "expected type '{}', found type '{}'",
+ .{ err_union.data.error_set, inst.ty },
+ );
+ }
+ break :blk val;
+ },
+ .error_set => blk: {
+ const expected_name = val.castTag(.@"error").?.data.name;
+ const error_set = err_union.data.error_set.castTag(.error_set).?.data;
+ const names = error_set.names_ptr[0..error_set.names_len];
+ // TODO this is O(N). I'm putting off solving this until we solve inferred
+ // error sets at the same time.
+ const found = for (names) |name| {
+ if (mem.eql(u8, expected_name, name)) break true;
+ } else false;
+ if (!found) {
+ return sema.mod.fail(
+ &block.base,
+ inst.src,
+ "expected type '{}', found type '{}'",
+ .{ err_union.data.error_set, inst.ty },
+ );
+ }
+ break :blk val;
+ },
+ else => unreachable,
+ };
+
+ return sema.mod.constInst(sema.arena, inst.src, .{
+ .ty = dest_type,
+ // creating a SubValue for the error_union payload
+ .val = try Value.Tag.error_union.create(
+ sema.arena,
+ to_wrap,
+ ),
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, inst.src);
+
+ // we are coercing from E to E!T
+ if (inst.ty.zigTypeTag() == .ErrorSet) {
+ var coerced = try sema.coerce(block, err_union.data.error_set, inst, inst.src);
+ return block.addUnOp(inst.src, dest_type, .wrap_errunion_err, coerced);
+ } else {
+ var coerced = try sema.coerce(block, err_union.data.payload, inst, inst.src);
+ return block.addUnOp(inst.src, dest_type, .wrap_errunion_payload, coerced);
+ }
+}
+
+fn resolvePeerTypes(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, instructions: []*Inst) !Type {
+ if (instructions.len == 0)
+ return Type.initTag(.noreturn);
+
+ if (instructions.len == 1)
+ return instructions[0].ty;
+
+ const target = sema.mod.getTarget();
+
+ var chosen = instructions[0];
+ for (instructions[1..]) |candidate| {
+ if (candidate.ty.eql(chosen.ty))
+ continue;
+ if (candidate.ty.zigTypeTag() == .NoReturn)
+ continue;
+ if (chosen.ty.zigTypeTag() == .NoReturn) {
+ chosen = candidate;
+ continue;
+ }
+ if (candidate.ty.zigTypeTag() == .Undefined)
+ continue;
+ if (chosen.ty.zigTypeTag() == .Undefined) {
+ chosen = candidate;
+ continue;
+ }
+ if (chosen.ty.isInt() and
+ candidate.ty.isInt() and
+ chosen.ty.isSignedInt() == candidate.ty.isSignedInt())
+ {
+ if (chosen.ty.intInfo(target).bits < candidate.ty.intInfo(target).bits) {
+ chosen = candidate;
+ }
+ continue;
+ }
+ if (chosen.ty.isFloat() and candidate.ty.isFloat()) {
+ if (chosen.ty.floatBits(target) < candidate.ty.floatBits(target)) {
+ chosen = candidate;
+ }
+ continue;
+ }
+
+ if (chosen.ty.zigTypeTag() == .ComptimeInt and candidate.ty.isInt()) {
+ chosen = candidate;
+ continue;
+ }
+
+ if (chosen.ty.isInt() and candidate.ty.zigTypeTag() == .ComptimeInt) {
+ continue;
+ }
+
+ // TODO error notes pointing out each type
+ return sema.mod.fail(&block.base, src, "incompatible types: '{}' and '{}'", .{ chosen.ty, candidate.ty });
+ }
+
+ return chosen.ty;
+}
diff --git a/src/astgen.zig b/src/astgen.zig
@@ -1,4318 +0,0 @@
-const std = @import("std");
-const mem = std.mem;
-const Allocator = std.mem.Allocator;
-const assert = std.debug.assert;
-
-const Value = @import("value.zig").Value;
-const Type = @import("type.zig").Type;
-const TypedValue = @import("TypedValue.zig");
-const zir = @import("zir.zig");
-const Module = @import("Module.zig");
-const ast = std.zig.ast;
-const trace = @import("tracy.zig").trace;
-const Scope = Module.Scope;
-const InnerError = Module.InnerError;
-const BuiltinFn = @import("BuiltinFn.zig");
-
-pub const ResultLoc = union(enum) {
- /// The expression is the right-hand side of assignment to `_`. Only the side-effects of the
- /// expression should be generated. The result instruction from the expression must
- /// be ignored.
- discard,
- /// The expression has an inferred type, and it will be evaluated as an rvalue.
- none,
- /// The expression must generate a pointer rather than a value. For example, the left hand side
- /// of an assignment uses this kind of result location.
- ref,
- /// The expression will be coerced into this type, but it will be evaluated as an rvalue.
- ty: *zir.Inst,
- /// The expression must store its result into this typed pointer. The result instruction
- /// from the expression must be ignored.
- ptr: *zir.Inst,
- /// The expression must store its result into this allocation, which has an inferred type.
- /// The result instruction from the expression must be ignored.
- inferred_ptr: *zir.Inst.Tag.alloc_inferred.Type(),
- /// The expression must store its result into this pointer, which is a typed pointer that
- /// has been bitcasted to whatever the expression's type is.
- /// The result instruction from the expression must be ignored.
- bitcasted_ptr: *zir.Inst.UnOp,
- /// There is a pointer for the expression to store its result into, however, its type
- /// is inferred based on peer type resolution for a `zir.Inst.Block`.
- /// The result instruction from the expression must be ignored.
- block_ptr: *Module.Scope.GenZIR,
-
- pub const Strategy = struct {
- elide_store_to_block_ptr_instructions: bool,
- tag: Tag,
-
- pub const Tag = enum {
- /// Both branches will use break_void; result location is used to communicate the
- /// result instruction.
- break_void,
- /// Use break statements to pass the block result value, and call rvalue() at
- /// the end depending on rl. Also elide the store_to_block_ptr instructions
- /// depending on rl.
- break_operand,
- };
- };
-};
-
-pub fn typeExpr(mod: *Module, scope: *Scope, type_node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- const token_starts = tree.tokens.items(.start);
-
- const type_src = token_starts[tree.firstToken(type_node)];
- const type_type = try addZIRInstConst(mod, scope, type_src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.type_type),
- });
- const type_rl: ResultLoc = .{ .ty = type_type };
- return expr(mod, scope, type_rl, type_node);
-}
-
-fn lvalExpr(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- const node_tags = tree.nodes.items(.tag);
- const main_tokens = tree.nodes.items(.main_token);
- switch (node_tags[node]) {
- .root => unreachable,
- .@"usingnamespace" => unreachable,
- .test_decl => unreachable,
- .global_var_decl => unreachable,
- .local_var_decl => unreachable,
- .simple_var_decl => unreachable,
- .aligned_var_decl => unreachable,
- .switch_case => unreachable,
- .switch_case_one => unreachable,
- .container_field_init => unreachable,
- .container_field_align => unreachable,
- .container_field => unreachable,
- .asm_output => unreachable,
- .asm_input => unreachable,
-
- .assign,
- .assign_bit_and,
- .assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_right,
- .assign_bit_xor,
- .assign_div,
- .assign_sub,
- .assign_sub_wrap,
- .assign_mod,
- .assign_add,
- .assign_add_wrap,
- .assign_mul,
- .assign_mul_wrap,
- .add,
- .add_wrap,
- .sub,
- .sub_wrap,
- .mul,
- .mul_wrap,
- .div,
- .mod,
- .bit_and,
- .bit_or,
- .bit_shift_left,
- .bit_shift_right,
- .bit_xor,
- .bang_equal,
- .equal_equal,
- .greater_than,
- .greater_or_equal,
- .less_than,
- .less_or_equal,
- .array_cat,
- .array_mult,
- .bool_and,
- .bool_or,
- .@"asm",
- .asm_simple,
- .string_literal,
- .integer_literal,
- .call,
- .call_comma,
- .async_call,
- .async_call_comma,
- .call_one,
- .call_one_comma,
- .async_call_one,
- .async_call_one_comma,
- .unreachable_literal,
- .@"return",
- .@"if",
- .if_simple,
- .@"while",
- .while_simple,
- .while_cont,
- .bool_not,
- .address_of,
- .float_literal,
- .undefined_literal,
- .true_literal,
- .false_literal,
- .null_literal,
- .optional_type,
- .block,
- .block_semicolon,
- .block_two,
- .block_two_semicolon,
- .@"break",
- .ptr_type_aligned,
- .ptr_type_sentinel,
- .ptr_type,
- .ptr_type_bit_range,
- .array_type,
- .array_type_sentinel,
- .enum_literal,
- .multiline_string_literal,
- .char_literal,
- .@"defer",
- .@"errdefer",
- .@"catch",
- .error_union,
- .merge_error_sets,
- .switch_range,
- .@"await",
- .bit_not,
- .negation,
- .negation_wrap,
- .@"resume",
- .@"try",
- .slice,
- .slice_open,
- .slice_sentinel,
- .array_init_one,
- .array_init_one_comma,
- .array_init_dot_two,
- .array_init_dot_two_comma,
- .array_init_dot,
- .array_init_dot_comma,
- .array_init,
- .array_init_comma,
- .struct_init_one,
- .struct_init_one_comma,
- .struct_init_dot_two,
- .struct_init_dot_two_comma,
- .struct_init_dot,
- .struct_init_dot_comma,
- .struct_init,
- .struct_init_comma,
- .@"switch",
- .switch_comma,
- .@"for",
- .for_simple,
- .@"suspend",
- .@"continue",
- .@"anytype",
- .fn_proto_simple,
- .fn_proto_multi,
- .fn_proto_one,
- .fn_proto,
- .fn_decl,
- .anyframe_type,
- .anyframe_literal,
- .error_set_decl,
- .container_decl,
- .container_decl_trailing,
- .container_decl_two,
- .container_decl_two_trailing,
- .container_decl_arg,
- .container_decl_arg_trailing,
- .tagged_union,
- .tagged_union_trailing,
- .tagged_union_two,
- .tagged_union_two_trailing,
- .tagged_union_enum_tag,
- .tagged_union_enum_tag_trailing,
- .@"comptime",
- .@"nosuspend",
- .error_value,
- => return mod.failNode(scope, node, "invalid left-hand side to assignment", .{}),
-
- .builtin_call,
- .builtin_call_comma,
- .builtin_call_two,
- .builtin_call_two_comma,
- => {
- const builtin_token = main_tokens[node];
- const builtin_name = tree.tokenSlice(builtin_token);
- // If the builtin is an invalid name, we don't cause an error here; instead
- // let it pass, and the error will be "invalid builtin function" later.
- if (BuiltinFn.list.get(builtin_name)) |info| {
- if (!info.allows_lvalue) {
- return mod.failNode(scope, node, "invalid left-hand side to assignment", .{});
- }
- }
- },
-
- // These can be assigned to.
- .unwrap_optional,
- .deref,
- .field_access,
- .array_access,
- .identifier,
- .grouped_expression,
- .@"orelse",
- => {},
- }
- return expr(mod, scope, .ref, node);
-}
-
-/// Turn Zig AST into untyped ZIR istructions.
-/// When `rl` is discard, ptr, inferred_ptr, bitcasted_ptr, or inferred_ptr, the
-/// result instruction can be used to inspect whether it is isNoReturn() but that is it,
-/// it must otherwise not be used.
-pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_tags = tree.tokens.items(.tag);
- const node_datas = tree.nodes.items(.data);
- const node_tags = tree.nodes.items(.tag);
- const token_starts = tree.tokens.items(.start);
-
- switch (node_tags[node]) {
- .root => unreachable, // Top-level declaration.
- .@"usingnamespace" => unreachable, // Top-level declaration.
- .test_decl => unreachable, // Top-level declaration.
- .container_field_init => unreachable, // Top-level declaration.
- .container_field_align => unreachable, // Top-level declaration.
- .container_field => unreachable, // Top-level declaration.
- .fn_decl => unreachable, // Top-level declaration.
-
- .global_var_decl => unreachable, // Handled in `blockExpr`.
- .local_var_decl => unreachable, // Handled in `blockExpr`.
- .simple_var_decl => unreachable, // Handled in `blockExpr`.
- .aligned_var_decl => unreachable, // Handled in `blockExpr`.
-
- .switch_case => unreachable, // Handled in `switchExpr`.
- .switch_case_one => unreachable, // Handled in `switchExpr`.
- .switch_range => unreachable, // Handled in `switchExpr`.
-
- .asm_output => unreachable, // Handled in `asmExpr`.
- .asm_input => unreachable, // Handled in `asmExpr`.
-
- .assign => return rvalueVoid(mod, scope, rl, node, try assign(mod, scope, node)),
- .assign_bit_and => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .bit_and)),
- .assign_bit_or => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .bit_or)),
- .assign_bit_shift_left => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .shl)),
- .assign_bit_shift_right => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .shr)),
- .assign_bit_xor => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .xor)),
- .assign_div => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .div)),
- .assign_sub => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .sub)),
- .assign_sub_wrap => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .subwrap)),
- .assign_mod => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .mod_rem)),
- .assign_add => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .add)),
- .assign_add_wrap => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .addwrap)),
- .assign_mul => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .mul)),
- .assign_mul_wrap => return rvalueVoid(mod, scope, rl, node, try assignOp(mod, scope, node, .mulwrap)),
-
- .add => return simpleBinOp(mod, scope, rl, node, .add),
- .add_wrap => return simpleBinOp(mod, scope, rl, node, .addwrap),
- .sub => return simpleBinOp(mod, scope, rl, node, .sub),
- .sub_wrap => return simpleBinOp(mod, scope, rl, node, .subwrap),
- .mul => return simpleBinOp(mod, scope, rl, node, .mul),
- .mul_wrap => return simpleBinOp(mod, scope, rl, node, .mulwrap),
- .div => return simpleBinOp(mod, scope, rl, node, .div),
- .mod => return simpleBinOp(mod, scope, rl, node, .mod_rem),
- .bit_and => return simpleBinOp(mod, scope, rl, node, .bit_and),
- .bit_or => return simpleBinOp(mod, scope, rl, node, .bit_or),
- .bit_shift_left => return simpleBinOp(mod, scope, rl, node, .shl),
- .bit_shift_right => return simpleBinOp(mod, scope, rl, node, .shr),
- .bit_xor => return simpleBinOp(mod, scope, rl, node, .xor),
-
- .bang_equal => return simpleBinOp(mod, scope, rl, node, .cmp_neq),
- .equal_equal => return simpleBinOp(mod, scope, rl, node, .cmp_eq),
- .greater_than => return simpleBinOp(mod, scope, rl, node, .cmp_gt),
- .greater_or_equal => return simpleBinOp(mod, scope, rl, node, .cmp_gte),
- .less_than => return simpleBinOp(mod, scope, rl, node, .cmp_lt),
- .less_or_equal => return simpleBinOp(mod, scope, rl, node, .cmp_lte),
-
- .array_cat => return simpleBinOp(mod, scope, rl, node, .array_cat),
- .array_mult => return simpleBinOp(mod, scope, rl, node, .array_mul),
-
- .bool_and => return boolBinOp(mod, scope, rl, node, true),
- .bool_or => return boolBinOp(mod, scope, rl, node, false),
-
- .bool_not => return rvalue(mod, scope, rl, try boolNot(mod, scope, node)),
- .bit_not => return rvalue(mod, scope, rl, try bitNot(mod, scope, node)),
- .negation => return rvalue(mod, scope, rl, try negation(mod, scope, node, .sub)),
- .negation_wrap => return rvalue(mod, scope, rl, try negation(mod, scope, node, .subwrap)),
-
- .identifier => return identifier(mod, scope, rl, node),
-
- .asm_simple => return asmExpr(mod, scope, rl, tree.asmSimple(node)),
- .@"asm" => return asmExpr(mod, scope, rl, tree.asmFull(node)),
-
- .string_literal => return stringLiteral(mod, scope, rl, node),
- .multiline_string_literal => return multilineStringLiteral(mod, scope, rl, node),
-
- .integer_literal => return integerLiteral(mod, scope, rl, node),
-
- .builtin_call_two, .builtin_call_two_comma => {
- if (node_datas[node].lhs == 0) {
- const params = [_]ast.Node.Index{};
- return builtinCall(mod, scope, rl, node, ¶ms);
- } else if (node_datas[node].rhs == 0) {
- const params = [_]ast.Node.Index{node_datas[node].lhs};
- return builtinCall(mod, scope, rl, node, ¶ms);
- } else {
- const params = [_]ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs };
- return builtinCall(mod, scope, rl, node, ¶ms);
- }
- },
- .builtin_call, .builtin_call_comma => {
- const params = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs];
- return builtinCall(mod, scope, rl, node, params);
- },
-
- .call_one, .call_one_comma, .async_call_one, .async_call_one_comma => {
- var params: [1]ast.Node.Index = undefined;
- return callExpr(mod, scope, rl, tree.callOne(¶ms, node));
- },
- .call, .call_comma, .async_call, .async_call_comma => {
- return callExpr(mod, scope, rl, tree.callFull(node));
- },
-
- .unreachable_literal => {
- const main_token = main_tokens[node];
- const src = token_starts[main_token];
- return addZIRNoOp(mod, scope, src, .unreachable_safe);
- },
- .@"return" => return ret(mod, scope, node),
- .field_access => return fieldAccess(mod, scope, rl, node),
- .float_literal => return floatLiteral(mod, scope, rl, node),
-
- .if_simple => return ifExpr(mod, scope, rl, tree.ifSimple(node)),
- .@"if" => return ifExpr(mod, scope, rl, tree.ifFull(node)),
-
- .while_simple => return whileExpr(mod, scope, rl, tree.whileSimple(node)),
- .while_cont => return whileExpr(mod, scope, rl, tree.whileCont(node)),
- .@"while" => return whileExpr(mod, scope, rl, tree.whileFull(node)),
-
- .for_simple => return forExpr(mod, scope, rl, tree.forSimple(node)),
- .@"for" => return forExpr(mod, scope, rl, tree.forFull(node)),
-
- // TODO handling these separately would actually be simpler & have fewer branches
- // once we have a ZIR instruction for each of these 3 cases.
- .slice_open => return sliceExpr(mod, scope, rl, tree.sliceOpen(node)),
- .slice => return sliceExpr(mod, scope, rl, tree.slice(node)),
- .slice_sentinel => return sliceExpr(mod, scope, rl, tree.sliceSentinel(node)),
-
- .deref => {
- const lhs = try expr(mod, scope, .none, node_datas[node].lhs);
- const src = token_starts[main_tokens[node]];
- const result = try addZIRUnOp(mod, scope, src, .deref, lhs);
- return rvalue(mod, scope, rl, result);
- },
- .address_of => {
- const result = try expr(mod, scope, .ref, node_datas[node].lhs);
- return rvalue(mod, scope, rl, result);
- },
- .undefined_literal => {
- const main_token = main_tokens[node];
- const src = token_starts[main_token];
- const result = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.@"undefined"),
- .val = Value.initTag(.undef),
- });
- return rvalue(mod, scope, rl, result);
- },
- .true_literal => {
- const main_token = main_tokens[node];
- const src = token_starts[main_token];
- const result = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.bool),
- .val = Value.initTag(.bool_true),
- });
- return rvalue(mod, scope, rl, result);
- },
- .false_literal => {
- const main_token = main_tokens[node];
- const src = token_starts[main_token];
- const result = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.bool),
- .val = Value.initTag(.bool_false),
- });
- return rvalue(mod, scope, rl, result);
- },
- .null_literal => {
- const main_token = main_tokens[node];
- const src = token_starts[main_token];
- const result = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.@"null"),
- .val = Value.initTag(.null_value),
- });
- return rvalue(mod, scope, rl, result);
- },
- .optional_type => {
- const src = token_starts[main_tokens[node]];
- const operand = try typeExpr(mod, scope, node_datas[node].lhs);
- const result = try addZIRUnOp(mod, scope, src, .optional_type, operand);
- return rvalue(mod, scope, rl, result);
- },
- .unwrap_optional => {
- const src = token_starts[main_tokens[node]];
- switch (rl) {
- .ref => return addZIRUnOp(
- mod,
- scope,
- src,
- .optional_payload_safe_ptr,
- try expr(mod, scope, .ref, node_datas[node].lhs),
- ),
- else => return rvalue(mod, scope, rl, try addZIRUnOp(
- mod,
- scope,
- src,
- .optional_payload_safe,
- try expr(mod, scope, .none, node_datas[node].lhs),
- )),
- }
- },
- .block_two, .block_two_semicolon => {
- const statements = [2]ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs };
- if (node_datas[node].lhs == 0) {
- return blockExpr(mod, scope, rl, node, statements[0..0]);
- } else if (node_datas[node].rhs == 0) {
- return blockExpr(mod, scope, rl, node, statements[0..1]);
- } else {
- return blockExpr(mod, scope, rl, node, statements[0..2]);
- }
- },
- .block, .block_semicolon => {
- const statements = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs];
- return blockExpr(mod, scope, rl, node, statements);
- },
- .enum_literal => {
- const ident_token = main_tokens[node];
- const name = try mod.identifierTokenString(scope, ident_token);
- const src = token_starts[ident_token];
- const result = try addZIRInst(mod, scope, src, zir.Inst.EnumLiteral, .{ .name = name }, .{});
- return rvalue(mod, scope, rl, result);
- },
- .error_value => {
- const ident_token = node_datas[node].rhs;
- const name = try mod.identifierTokenString(scope, ident_token);
- const src = token_starts[ident_token];
- const result = try addZirInstTag(mod, scope, src, .error_value, .{ .name = name });
- return rvalue(mod, scope, rl, result);
- },
- .error_union => {
- const error_set = try typeExpr(mod, scope, node_datas[node].lhs);
- const payload = try typeExpr(mod, scope, node_datas[node].rhs);
- const src = token_starts[main_tokens[node]];
- const result = try addZIRBinOp(mod, scope, src, .error_union_type, error_set, payload);
- return rvalue(mod, scope, rl, result);
- },
- .merge_error_sets => {
- const lhs = try typeExpr(mod, scope, node_datas[node].lhs);
- const rhs = try typeExpr(mod, scope, node_datas[node].rhs);
- const src = token_starts[main_tokens[node]];
- const result = try addZIRBinOp(mod, scope, src, .merge_error_sets, lhs, rhs);
- return rvalue(mod, scope, rl, result);
- },
- .anyframe_literal => {
- const main_token = main_tokens[node];
- const src = token_starts[main_token];
- const result = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.anyframe_type),
- });
- return rvalue(mod, scope, rl, result);
- },
- .anyframe_type => {
- const src = token_starts[node_datas[node].lhs];
- const return_type = try typeExpr(mod, scope, node_datas[node].rhs);
- const result = try addZIRUnOp(mod, scope, src, .anyframe_type, return_type);
- return rvalue(mod, scope, rl, result);
- },
- .@"catch" => {
- const catch_token = main_tokens[node];
- const payload_token: ?ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe)
- catch_token + 2
- else
- null;
- switch (rl) {
- .ref => return orelseCatchExpr(
- mod,
- scope,
- rl,
- node_datas[node].lhs,
- main_tokens[node],
- .is_err_ptr,
- .err_union_payload_unsafe_ptr,
- .err_union_code_ptr,
- node_datas[node].rhs,
- payload_token,
- ),
- else => return orelseCatchExpr(
- mod,
- scope,
- rl,
- node_datas[node].lhs,
- main_tokens[node],
- .is_err,
- .err_union_payload_unsafe,
- .err_union_code,
- node_datas[node].rhs,
- payload_token,
- ),
- }
- },
- .@"orelse" => switch (rl) {
- .ref => return orelseCatchExpr(
- mod,
- scope,
- rl,
- node_datas[node].lhs,
- main_tokens[node],
- .is_null_ptr,
- .optional_payload_unsafe_ptr,
- undefined,
- node_datas[node].rhs,
- null,
- ),
- else => return orelseCatchExpr(
- mod,
- scope,
- rl,
- node_datas[node].lhs,
- main_tokens[node],
- .is_null,
- .optional_payload_unsafe,
- undefined,
- node_datas[node].rhs,
- null,
- ),
- },
-
- .ptr_type_aligned => return ptrType(mod, scope, rl, tree.ptrTypeAligned(node)),
- .ptr_type_sentinel => return ptrType(mod, scope, rl, tree.ptrTypeSentinel(node)),
- .ptr_type => return ptrType(mod, scope, rl, tree.ptrType(node)),
- .ptr_type_bit_range => return ptrType(mod, scope, rl, tree.ptrTypeBitRange(node)),
-
- .container_decl,
- .container_decl_trailing,
- => return containerDecl(mod, scope, rl, tree.containerDecl(node)),
- .container_decl_two, .container_decl_two_trailing => {
- var buffer: [2]ast.Node.Index = undefined;
- return containerDecl(mod, scope, rl, tree.containerDeclTwo(&buffer, node));
- },
- .container_decl_arg,
- .container_decl_arg_trailing,
- => return containerDecl(mod, scope, rl, tree.containerDeclArg(node)),
-
- .tagged_union,
- .tagged_union_trailing,
- => return containerDecl(mod, scope, rl, tree.taggedUnion(node)),
- .tagged_union_two, .tagged_union_two_trailing => {
- var buffer: [2]ast.Node.Index = undefined;
- return containerDecl(mod, scope, rl, tree.taggedUnionTwo(&buffer, node));
- },
- .tagged_union_enum_tag,
- .tagged_union_enum_tag_trailing,
- => return containerDecl(mod, scope, rl, tree.taggedUnionEnumTag(node)),
-
- .@"break" => return breakExpr(mod, scope, rl, node),
- .@"continue" => return continueExpr(mod, scope, rl, node),
- .grouped_expression => return expr(mod, scope, rl, node_datas[node].lhs),
- .array_type => return arrayType(mod, scope, rl, node),
- .array_type_sentinel => return arrayTypeSentinel(mod, scope, rl, node),
- .char_literal => return charLiteral(mod, scope, rl, node),
- .error_set_decl => return errorSetDecl(mod, scope, rl, node),
- .array_access => return arrayAccess(mod, scope, rl, node),
- .@"comptime" => return comptimeExpr(mod, scope, rl, node_datas[node].lhs),
- .@"switch", .switch_comma => return switchExpr(mod, scope, rl, node),
-
- .@"nosuspend" => return nosuspendExpr(mod, scope, rl, node),
- .@"suspend" => return rvalue(mod, scope, rl, try suspendExpr(mod, scope, node)),
- .@"await" => return awaitExpr(mod, scope, rl, node),
- .@"resume" => return rvalue(mod, scope, rl, try resumeExpr(mod, scope, node)),
-
- .@"defer" => return mod.failNode(scope, node, "TODO implement astgen.expr for .defer", .{}),
- .@"errdefer" => return mod.failNode(scope, node, "TODO implement astgen.expr for .errdefer", .{}),
- .@"try" => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}),
-
- .array_init_one,
- .array_init_one_comma,
- .array_init_dot_two,
- .array_init_dot_two_comma,
- .array_init_dot,
- .array_init_dot_comma,
- .array_init,
- .array_init_comma,
- => return mod.failNode(scope, node, "TODO implement astgen.expr for array literals", .{}),
-
- .struct_init_one,
- .struct_init_one_comma,
- .struct_init_dot_two,
- .struct_init_dot_two_comma,
- .struct_init_dot,
- .struct_init_dot_comma,
- .struct_init,
- .struct_init_comma,
- => return mod.failNode(scope, node, "TODO implement astgen.expr for struct literals", .{}),
-
- .@"anytype" => return mod.failNode(scope, node, "TODO implement astgen.expr for .anytype", .{}),
- .fn_proto_simple,
- .fn_proto_multi,
- .fn_proto_one,
- .fn_proto,
- => return mod.failNode(scope, node, "TODO implement astgen.expr for function prototypes", .{}),
- }
-}
-
-pub fn comptimeExpr(
- mod: *Module,
- parent_scope: *Scope,
- rl: ResultLoc,
- node: ast.Node.Index,
-) InnerError!*zir.Inst {
- // If we are already in a comptime scope, no need to make another one.
- if (parent_scope.isComptime()) {
- return expr(mod, parent_scope, rl, node);
- }
-
- const tree = parent_scope.tree();
- const token_starts = tree.tokens.items(.start);
-
- // Make a scope to collect generated instructions in the sub-expression.
- var block_scope: Scope.GenZIR = .{
- .parent = parent_scope,
- .decl = parent_scope.ownerDecl().?,
- .arena = parent_scope.arena(),
- .force_comptime = true,
- .instructions = .{},
- };
- defer block_scope.instructions.deinit(mod.gpa);
-
- // No need to capture the result here because block_comptime_flat implies that the final
- // instruction is the block's result value.
- _ = try expr(mod, &block_scope.base, rl, node);
-
- const src = token_starts[tree.firstToken(node)];
- const block = try addZIRInstBlock(mod, parent_scope, src, .block_comptime_flat, .{
- .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
- });
-
- return &block.base;
-}
-
-fn breakExpr(
- mod: *Module,
- parent_scope: *Scope,
- rl: ResultLoc,
- node: ast.Node.Index,
-) InnerError!*zir.Inst {
- const tree = parent_scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[main_tokens[node]];
- const break_label = node_datas[node].lhs;
- const rhs = node_datas[node].rhs;
-
- // Look for the label in the scope.
- var scope = parent_scope;
- while (true) {
- switch (scope.tag) {
- .gen_zir => {
- const gen_zir = scope.cast(Scope.GenZIR).?;
-
- const block_inst = blk: {
- if (break_label != 0) {
- if (gen_zir.label) |*label| {
- if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) {
- label.used = true;
- break :blk label.block_inst;
- }
- }
- } else if (gen_zir.break_block) |inst| {
- break :blk inst;
- }
- scope = gen_zir.parent;
- continue;
- };
-
- if (rhs == 0) {
- const result = try addZirInstTag(mod, parent_scope, src, .break_void, .{
- .block = block_inst,
- });
- return rvalue(mod, parent_scope, rl, result);
- }
- gen_zir.break_count += 1;
- const prev_rvalue_rl_count = gen_zir.rvalue_rl_count;
- const operand = try expr(mod, parent_scope, gen_zir.break_result_loc, rhs);
- const have_store_to_block = gen_zir.rvalue_rl_count != prev_rvalue_rl_count;
- const br = try addZirInstTag(mod, parent_scope, src, .@"break", .{
- .block = block_inst,
- .operand = operand,
- });
- if (gen_zir.break_result_loc == .block_ptr) {
- try gen_zir.labeled_breaks.append(mod.gpa, br.castTag(.@"break").?);
-
- if (have_store_to_block) {
- const inst_list = parent_scope.getGenZIR().instructions.items;
- const last_inst = inst_list[inst_list.len - 2];
- const store_inst = last_inst.castTag(.store_to_block_ptr).?;
- assert(store_inst.positionals.lhs == gen_zir.rl_ptr.?);
- try gen_zir.labeled_store_to_block_ptr_list.append(mod.gpa, store_inst);
- }
- }
- return rvalue(mod, parent_scope, rl, br);
- },
- .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
- .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
- .gen_suspend => scope = scope.cast(Scope.GenZIR).?.parent,
- .gen_nosuspend => scope = scope.cast(Scope.Nosuspend).?.parent,
- else => if (break_label != 0) {
- const label_name = try mod.identifierTokenString(parent_scope, break_label);
- return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name});
- } else {
- return mod.failTok(parent_scope, src, "break expression outside loop", .{});
- },
- }
- }
-}
-
-fn continueExpr(
- mod: *Module,
- parent_scope: *Scope,
- rl: ResultLoc,
- node: ast.Node.Index,
-) InnerError!*zir.Inst {
- const tree = parent_scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[main_tokens[node]];
- const break_label = node_datas[node].lhs;
-
- // Look for the label in the scope.
- var scope = parent_scope;
- while (true) {
- switch (scope.tag) {
- .gen_zir => {
- const gen_zir = scope.cast(Scope.GenZIR).?;
- const continue_block = gen_zir.continue_block orelse {
- scope = gen_zir.parent;
- continue;
- };
- if (break_label != 0) blk: {
- if (gen_zir.label) |*label| {
- if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) {
- label.used = true;
- break :blk;
- }
- }
- // found continue but either it has a different label, or no label
- scope = gen_zir.parent;
- continue;
- }
-
- const result = try addZirInstTag(mod, parent_scope, src, .break_void, .{
- .block = continue_block,
- });
- return rvalue(mod, parent_scope, rl, result);
- },
- .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
- .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
- .gen_suspend => scope = scope.cast(Scope.GenZIR).?.parent,
- .gen_nosuspend => scope = scope.cast(Scope.Nosuspend).?.parent,
- else => if (break_label != 0) {
- const label_name = try mod.identifierTokenString(parent_scope, break_label);
- return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name});
- } else {
- return mod.failTok(parent_scope, src, "continue expression outside loop", .{});
- },
- }
- }
-}
-
-pub fn blockExpr(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- block_node: ast.Node.Index,
- statements: []const ast.Node.Index,
-) InnerError!*zir.Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_tags = tree.tokens.items(.tag);
-
- const lbrace = main_tokens[block_node];
- if (token_tags[lbrace - 1] == .colon and
- token_tags[lbrace - 2] == .identifier)
- {
- return labeledBlockExpr(mod, scope, rl, block_node, statements, .block);
- }
-
- try blockExprStmts(mod, scope, block_node, statements);
- return rvalueVoid(mod, scope, rl, block_node, {});
-}
-
-fn checkLabelRedefinition(mod: *Module, parent_scope: *Scope, label: ast.TokenIndex) !void {
- // Look for the label in the scope.
- var scope = parent_scope;
- while (true) {
- switch (scope.tag) {
- .gen_zir => {
- const gen_zir = scope.cast(Scope.GenZIR).?;
- if (gen_zir.label) |prev_label| {
- if (try tokenIdentEql(mod, parent_scope, label, prev_label.token)) {
- const tree = parent_scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const label_src = token_starts[label];
- const prev_label_src = token_starts[prev_label.token];
-
- const label_name = try mod.identifierTokenString(parent_scope, label);
- const msg = msg: {
- const msg = try mod.errMsg(
- parent_scope,
- label_src,
- "redefinition of label '{s}'",
- .{label_name},
- );
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(
- parent_scope,
- prev_label_src,
- msg,
- "previous definition is here",
- .{},
- );
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(parent_scope, msg);
- }
- }
- scope = gen_zir.parent;
- },
- .local_val => scope = scope.cast(Scope.LocalVal).?.parent,
- .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
- .gen_suspend => scope = scope.cast(Scope.GenZIR).?.parent,
- .gen_nosuspend => scope = scope.cast(Scope.Nosuspend).?.parent,
- else => return,
- }
- }
-}
-
-fn labeledBlockExpr(
- mod: *Module,
- parent_scope: *Scope,
- rl: ResultLoc,
- block_node: ast.Node.Index,
- statements: []const ast.Node.Index,
- zir_tag: zir.Inst.Tag,
-) InnerError!*zir.Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- assert(zir_tag == .block or zir_tag == .block_comptime);
-
- const tree = parent_scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
- const token_tags = tree.tokens.items(.tag);
-
- const lbrace = main_tokens[block_node];
- const label_token = lbrace - 2;
- assert(token_tags[label_token] == .identifier);
- const src = token_starts[lbrace];
-
- try checkLabelRedefinition(mod, parent_scope, label_token);
-
- // Create the Block ZIR instruction so that we can put it into the GenZIR struct
- // so that break statements can reference it.
- const gen_zir = parent_scope.getGenZIR();
- const block_inst = try gen_zir.arena.create(zir.Inst.Block);
- block_inst.* = .{
- .base = .{
- .tag = zir_tag,
- .src = src,
- },
- .positionals = .{
- .body = .{ .instructions = undefined },
- },
- .kw_args = .{},
- };
-
- var block_scope: Scope.GenZIR = .{
- .parent = parent_scope,
- .decl = parent_scope.ownerDecl().?,
- .arena = gen_zir.arena,
- .force_comptime = parent_scope.isComptime(),
- .instructions = .{},
- // TODO @as here is working around a stage1 miscompilation bug :(
- .label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{
- .token = label_token,
- .block_inst = block_inst,
- }),
- };
- setBlockResultLoc(&block_scope, rl);
- defer block_scope.instructions.deinit(mod.gpa);
- defer block_scope.labeled_breaks.deinit(mod.gpa);
- defer block_scope.labeled_store_to_block_ptr_list.deinit(mod.gpa);
-
- try blockExprStmts(mod, &block_scope.base, block_node, statements);
-
- if (!block_scope.label.?.used) {
- return mod.failTok(parent_scope, label_token, "unused block label", .{});
- }
-
- try gen_zir.instructions.append(mod.gpa, &block_inst.base);
-
- const strat = rlStrategy(rl, &block_scope);
- switch (strat.tag) {
- .break_void => {
- // The code took advantage of the result location as a pointer.
- // Turn the break instructions into break_void instructions.
- for (block_scope.labeled_breaks.items) |br| {
- br.base.tag = .break_void;
- }
- // TODO technically not needed since we changed the tag to break_void but
- // would be better still to elide the ones that are in this list.
- try copyBodyNoEliding(&block_inst.positionals.body, block_scope);
-
- return &block_inst.base;
- },
- .break_operand => {
- // All break operands are values that did not use the result location pointer.
- if (strat.elide_store_to_block_ptr_instructions) {
- for (block_scope.labeled_store_to_block_ptr_list.items) |inst| {
- inst.base.tag = .void_value;
- }
- // TODO technically not needed since we changed the tag to void_value but
- // would be better still to elide the ones that are in this list.
- }
- try copyBodyNoEliding(&block_inst.positionals.body, block_scope);
- switch (rl) {
- .ref => return &block_inst.base,
- else => return rvalue(mod, parent_scope, rl, &block_inst.base),
- }
- },
- }
-}
-
-fn blockExprStmts(
- mod: *Module,
- parent_scope: *Scope,
- node: ast.Node.Index,
- statements: []const ast.Node.Index,
-) !void {
- const tree = parent_scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
- const node_tags = tree.nodes.items(.tag);
-
- var block_arena = std.heap.ArenaAllocator.init(mod.gpa);
- defer block_arena.deinit();
-
- var scope = parent_scope;
- for (statements) |statement| {
- const src = token_starts[tree.firstToken(statement)];
- _ = try addZIRNoOp(mod, scope, src, .dbg_stmt);
- switch (node_tags[statement]) {
- .global_var_decl => scope = try varDecl(mod, scope, &block_arena.allocator, tree.globalVarDecl(statement)),
- .local_var_decl => scope = try varDecl(mod, scope, &block_arena.allocator, tree.localVarDecl(statement)),
- .simple_var_decl => scope = try varDecl(mod, scope, &block_arena.allocator, tree.simpleVarDecl(statement)),
- .aligned_var_decl => scope = try varDecl(mod, scope, &block_arena.allocator, tree.alignedVarDecl(statement)),
-
- .assign => try assign(mod, scope, statement),
- .assign_bit_and => try assignOp(mod, scope, statement, .bit_and),
- .assign_bit_or => try assignOp(mod, scope, statement, .bit_or),
- .assign_bit_shift_left => try assignOp(mod, scope, statement, .shl),
- .assign_bit_shift_right => try assignOp(mod, scope, statement, .shr),
- .assign_bit_xor => try assignOp(mod, scope, statement, .xor),
- .assign_div => try assignOp(mod, scope, statement, .div),
- .assign_sub => try assignOp(mod, scope, statement, .sub),
- .assign_sub_wrap => try assignOp(mod, scope, statement, .subwrap),
- .assign_mod => try assignOp(mod, scope, statement, .mod_rem),
- .assign_add => try assignOp(mod, scope, statement, .add),
- .assign_add_wrap => try assignOp(mod, scope, statement, .addwrap),
- .assign_mul => try assignOp(mod, scope, statement, .mul),
- .assign_mul_wrap => try assignOp(mod, scope, statement, .mulwrap),
-
- else => {
- const possibly_unused_result = try expr(mod, scope, .none, statement);
- if (!possibly_unused_result.tag.isNoReturn()) {
- _ = try addZIRUnOp(mod, scope, src, .ensure_result_used, possibly_unused_result);
- }
- },
- }
- }
-}
-
-fn varDecl(
- mod: *Module,
- scope: *Scope,
- block_arena: *Allocator,
- var_decl: ast.full.VarDecl,
-) InnerError!*Scope {
- if (var_decl.comptime_token) |comptime_token| {
- return mod.failTok(scope, comptime_token, "TODO implement comptime locals", .{});
- }
- if (var_decl.ast.align_node != 0) {
- return mod.failNode(scope, var_decl.ast.align_node, "TODO implement alignment on locals", .{});
- }
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
- const token_tags = tree.tokens.items(.tag);
-
- const name_token = var_decl.ast.mut_token + 1;
- const name_src = token_starts[name_token];
- const ident_name = try mod.identifierTokenString(scope, name_token);
-
- // Local variables shadowing detection, including function parameters.
- {
- var s = scope;
- while (true) switch (s.tag) {
- .local_val => {
- const local_val = s.cast(Scope.LocalVal).?;
- if (mem.eql(u8, local_val.name, ident_name)) {
- const msg = msg: {
- const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{
- ident_name,
- });
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, local_val.inst.src, msg, "previous definition is here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
- s = local_val.parent;
- },
- .local_ptr => {
- const local_ptr = s.cast(Scope.LocalPtr).?;
- if (mem.eql(u8, local_ptr.name, ident_name)) {
- const msg = msg: {
- const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{
- ident_name,
- });
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, local_ptr.ptr.src, msg, "previous definition is here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
- s = local_ptr.parent;
- },
- .gen_zir => s = s.cast(Scope.GenZIR).?.parent,
- .gen_suspend => s = s.cast(Scope.GenZIR).?.parent,
- .gen_nosuspend => s = s.cast(Scope.Nosuspend).?.parent,
- else => break,
- };
- }
-
- // Namespace vars shadowing detection
- if (mod.lookupDeclName(scope, ident_name)) |_| {
- // TODO add note for other definition
- return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name});
- }
- if (var_decl.ast.init_node == 0) {
- return mod.fail(scope, name_src, "variables must be initialized", .{});
- }
-
- switch (token_tags[var_decl.ast.mut_token]) {
- .keyword_const => {
- // Depending on the type of AST the initialization expression is, we may need an lvalue
- // or an rvalue as a result location. If it is an rvalue, we can use the instruction as
- // the variable, no memory location needed.
- if (!nodeMayNeedMemoryLocation(scope, var_decl.ast.init_node)) {
- const result_loc: ResultLoc = if (var_decl.ast.type_node != 0)
- .{ .ty = try typeExpr(mod, scope, var_decl.ast.type_node) }
- else
- .none;
- const init_inst = try expr(mod, scope, result_loc, var_decl.ast.init_node);
- const sub_scope = try block_arena.create(Scope.LocalVal);
- sub_scope.* = .{
- .parent = scope,
- .gen_zir = scope.getGenZIR(),
- .name = ident_name,
- .inst = init_inst,
- };
- return &sub_scope.base;
- }
-
- // Detect whether the initialization expression actually uses the
- // result location pointer.
- var init_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .force_comptime = scope.isComptime(),
- .instructions = .{},
- };
- defer init_scope.instructions.deinit(mod.gpa);
-
- var resolve_inferred_alloc: ?*zir.Inst = null;
- var opt_type_inst: ?*zir.Inst = null;
- if (var_decl.ast.type_node != 0) {
- const type_inst = try typeExpr(mod, &init_scope.base, var_decl.ast.type_node);
- opt_type_inst = type_inst;
- init_scope.rl_ptr = try addZIRUnOp(mod, &init_scope.base, name_src, .alloc, type_inst);
- } else {
- const alloc = try addZIRNoOpT(mod, &init_scope.base, name_src, .alloc_inferred);
- resolve_inferred_alloc = &alloc.base;
- init_scope.rl_ptr = &alloc.base;
- }
- const init_result_loc: ResultLoc = .{ .block_ptr = &init_scope };
- const init_inst = try expr(mod, &init_scope.base, init_result_loc, var_decl.ast.init_node);
- const parent_zir = &scope.getGenZIR().instructions;
- if (init_scope.rvalue_rl_count == 1) {
- // Result location pointer not used. We don't need an alloc for this
- // const local, and type inference becomes trivial.
- // Move the init_scope instructions into the parent scope, eliding
- // the alloc instruction and the store_to_block_ptr instruction.
- const expected_len = parent_zir.items.len + init_scope.instructions.items.len - 2;
- try parent_zir.ensureCapacity(mod.gpa, expected_len);
- for (init_scope.instructions.items) |src_inst| {
- if (src_inst == init_scope.rl_ptr.?) continue;
- if (src_inst.castTag(.store_to_block_ptr)) |store| {
- if (store.positionals.lhs == init_scope.rl_ptr.?) continue;
- }
- parent_zir.appendAssumeCapacity(src_inst);
- }
- assert(parent_zir.items.len == expected_len);
- const casted_init = if (opt_type_inst) |type_inst|
- try addZIRBinOp(mod, scope, type_inst.src, .as, type_inst, init_inst)
- else
- init_inst;
-
- const sub_scope = try block_arena.create(Scope.LocalVal);
- sub_scope.* = .{
- .parent = scope,
- .gen_zir = scope.getGenZIR(),
- .name = ident_name,
- .inst = casted_init,
- };
- return &sub_scope.base;
- }
- // The initialization expression took advantage of the result location
- // of the const local. In this case we will create an alloc and a LocalPtr for it.
- // Move the init_scope instructions into the parent scope, swapping
- // store_to_block_ptr for store_to_inferred_ptr.
- const expected_len = parent_zir.items.len + init_scope.instructions.items.len;
- try parent_zir.ensureCapacity(mod.gpa, expected_len);
- for (init_scope.instructions.items) |src_inst| {
- if (src_inst.castTag(.store_to_block_ptr)) |store| {
- if (store.positionals.lhs == init_scope.rl_ptr.?) {
- src_inst.tag = .store_to_inferred_ptr;
- }
- }
- parent_zir.appendAssumeCapacity(src_inst);
- }
- assert(parent_zir.items.len == expected_len);
- if (resolve_inferred_alloc) |inst| {
- _ = try addZIRUnOp(mod, scope, name_src, .resolve_inferred_alloc, inst);
- }
- const sub_scope = try block_arena.create(Scope.LocalPtr);
- sub_scope.* = .{
- .parent = scope,
- .gen_zir = scope.getGenZIR(),
- .name = ident_name,
- .ptr = init_scope.rl_ptr.?,
- };
- return &sub_scope.base;
- },
- .keyword_var => {
- var resolve_inferred_alloc: ?*zir.Inst = null;
- const var_data: struct {
- result_loc: ResultLoc,
- alloc: *zir.Inst,
- } = if (var_decl.ast.type_node != 0) a: {
- const type_inst = try typeExpr(mod, scope, var_decl.ast.type_node);
- const alloc = try addZIRUnOp(mod, scope, name_src, .alloc_mut, type_inst);
- break :a .{ .alloc = alloc, .result_loc = .{ .ptr = alloc } };
- } else a: {
- const alloc = try addZIRNoOpT(mod, scope, name_src, .alloc_inferred_mut);
- resolve_inferred_alloc = &alloc.base;
- break :a .{ .alloc = &alloc.base, .result_loc = .{ .inferred_ptr = alloc } };
- };
- const init_inst = try expr(mod, scope, var_data.result_loc, var_decl.ast.init_node);
- if (resolve_inferred_alloc) |inst| {
- _ = try addZIRUnOp(mod, scope, name_src, .resolve_inferred_alloc, inst);
- }
- const sub_scope = try block_arena.create(Scope.LocalPtr);
- sub_scope.* = .{
- .parent = scope,
- .gen_zir = scope.getGenZIR(),
- .name = ident_name,
- .ptr = var_data.alloc,
- };
- return &sub_scope.base;
- },
- else => unreachable,
- }
-}
-
-fn assign(mod: *Module, scope: *Scope, infix_node: ast.Node.Index) InnerError!void {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const node_tags = tree.nodes.items(.tag);
-
- const lhs = node_datas[infix_node].lhs;
- const rhs = node_datas[infix_node].rhs;
- if (node_tags[lhs] == .identifier) {
- // This intentionally does not support `@"_"` syntax.
- const ident_name = tree.tokenSlice(main_tokens[lhs]);
- if (mem.eql(u8, ident_name, "_")) {
- _ = try expr(mod, scope, .discard, rhs);
- return;
- }
- }
- const lvalue = try lvalExpr(mod, scope, lhs);
- _ = try expr(mod, scope, .{ .ptr = lvalue }, rhs);
-}
-
-fn assignOp(
- mod: *Module,
- scope: *Scope,
- infix_node: ast.Node.Index,
- op_inst_tag: zir.Inst.Tag,
-) InnerError!void {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const lhs_ptr = try lvalExpr(mod, scope, node_datas[infix_node].lhs);
- const lhs = try addZIRUnOp(mod, scope, lhs_ptr.src, .deref, lhs_ptr);
- const lhs_type = try addZIRUnOp(mod, scope, lhs_ptr.src, .typeof, lhs);
- const rhs = try expr(mod, scope, .{ .ty = lhs_type }, node_datas[infix_node].rhs);
- const src = token_starts[main_tokens[infix_node]];
- const result = try addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs);
- _ = try addZIRBinOp(mod, scope, src, .store, lhs_ptr, result);
-}
-
-fn boolNot(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[main_tokens[node]];
- const bool_type = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.bool_type),
- });
- const operand = try expr(mod, scope, .{ .ty = bool_type }, node_datas[node].lhs);
- return addZIRUnOp(mod, scope, src, .bool_not, operand);
-}
-
-fn bitNot(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[main_tokens[node]];
- const operand = try expr(mod, scope, .none, node_datas[node].lhs);
- return addZIRUnOp(mod, scope, src, .bit_not, operand);
-}
-
-fn negation(
- mod: *Module,
- scope: *Scope,
- node: ast.Node.Index,
- op_inst_tag: zir.Inst.Tag,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[main_tokens[node]];
- const lhs = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.comptime_int),
- .val = Value.initTag(.zero),
- });
- const rhs = try expr(mod, scope, .none, node_datas[node].lhs);
- return addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs);
-}
-
-fn ptrType(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- ptr_info: ast.full.PtrType,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[ptr_info.ast.main_token];
-
- const simple = ptr_info.allowzero_token == null and
- ptr_info.ast.align_node == 0 and
- ptr_info.volatile_token == null and
- ptr_info.ast.sentinel == 0;
-
- if (simple) {
- const child_type = try typeExpr(mod, scope, ptr_info.ast.child_type);
- const mutable = ptr_info.const_token == null;
- const T = zir.Inst.Tag;
- const result = try addZIRUnOp(mod, scope, src, switch (ptr_info.size) {
- .One => if (mutable) T.single_mut_ptr_type else T.single_const_ptr_type,
- .Many => if (mutable) T.many_mut_ptr_type else T.many_const_ptr_type,
- .C => if (mutable) T.c_mut_ptr_type else T.c_const_ptr_type,
- .Slice => if (mutable) T.mut_slice_type else T.const_slice_type,
- }, child_type);
- return rvalue(mod, scope, rl, result);
- }
-
- var kw_args: std.meta.fieldInfo(zir.Inst.PtrType, .kw_args).field_type = .{};
- kw_args.size = ptr_info.size;
- kw_args.@"allowzero" = ptr_info.allowzero_token != null;
- if (ptr_info.ast.align_node != 0) {
- kw_args.@"align" = try expr(mod, scope, .none, ptr_info.ast.align_node);
- if (ptr_info.ast.bit_range_start != 0) {
- kw_args.align_bit_start = try expr(mod, scope, .none, ptr_info.ast.bit_range_start);
- kw_args.align_bit_end = try expr(mod, scope, .none, ptr_info.ast.bit_range_end);
- }
- }
- kw_args.mutable = ptr_info.const_token == null;
- kw_args.@"volatile" = ptr_info.volatile_token != null;
- const child_type = try typeExpr(mod, scope, ptr_info.ast.child_type);
- if (ptr_info.ast.sentinel != 0) {
- kw_args.sentinel = try expr(mod, scope, .{ .ty = child_type }, ptr_info.ast.sentinel);
- }
- const result = try addZIRInst(mod, scope, src, zir.Inst.PtrType, .{ .child_type = child_type }, kw_args);
- return rvalue(mod, scope, rl, result);
-}
-
-fn arrayType(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !*zir.Inst {
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const node_datas = tree.nodes.items(.data);
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[main_tokens[node]];
- const usize_type = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.usize_type),
- });
- const len_node = node_datas[node].lhs;
- const elem_node = node_datas[node].rhs;
- if (len_node == 0) {
- const elem_type = try typeExpr(mod, scope, elem_node);
- const result = try addZIRUnOp(mod, scope, src, .mut_slice_type, elem_type);
- return rvalue(mod, scope, rl, result);
- } else {
- // TODO check for [_]T
- const len = try expr(mod, scope, .{ .ty = usize_type }, len_node);
- const elem_type = try typeExpr(mod, scope, elem_node);
-
- const result = try addZIRBinOp(mod, scope, src, .array_type, len, elem_type);
- return rvalue(mod, scope, rl, result);
- }
-}
-
-fn arrayTypeSentinel(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !*zir.Inst {
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
- const node_datas = tree.nodes.items(.data);
-
- const len_node = node_datas[node].lhs;
- const extra = tree.extraData(node_datas[node].rhs, ast.Node.ArrayTypeSentinel);
- const src = token_starts[main_tokens[node]];
- const usize_type = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.usize_type),
- });
-
- // TODO check for [_]T
- const len = try expr(mod, scope, .{ .ty = usize_type }, len_node);
- const sentinel_uncasted = try expr(mod, scope, .none, extra.sentinel);
- const elem_type = try typeExpr(mod, scope, extra.elem_type);
- const sentinel = try addZIRBinOp(mod, scope, src, .as, elem_type, sentinel_uncasted);
-
- const result = try addZIRInst(mod, scope, src, zir.Inst.ArrayTypeSentinel, .{
- .len = len,
- .sentinel = sentinel,
- .elem_type = elem_type,
- }, .{});
- return rvalue(mod, scope, rl, result);
-}
-
-fn containerField(
- mod: *Module,
- scope: *Scope,
- field: ast.full.ContainerField,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[field.ast.name_token];
- const name = try mod.identifierTokenString(scope, field.ast.name_token);
-
- if (field.comptime_token == null and field.ast.value_expr == 0 and field.ast.align_expr == 0) {
- if (field.ast.type_expr != 0) {
- const ty = try typeExpr(mod, scope, field.ast.type_expr);
- return addZIRInst(mod, scope, src, zir.Inst.ContainerFieldTyped, .{
- .bytes = name,
- .ty = ty,
- }, .{});
- } else {
- return addZIRInst(mod, scope, src, zir.Inst.ContainerFieldNamed, .{
- .bytes = name,
- }, .{});
- }
- }
-
- const ty = if (field.ast.type_expr != 0) try typeExpr(mod, scope, field.ast.type_expr) else null;
- // TODO result location should be alignment type
- const alignment = if (field.ast.align_expr != 0) try expr(mod, scope, .none, field.ast.align_expr) else null;
- // TODO result location should be the field type
- const init = if (field.ast.value_expr != 0) try expr(mod, scope, .none, field.ast.value_expr) else null;
-
- return addZIRInst(mod, scope, src, zir.Inst.ContainerField, .{
- .bytes = name,
- }, .{
- .ty = ty,
- .init = init,
- .alignment = alignment,
- .is_comptime = field.comptime_token != null,
- });
-}
-
-fn containerDecl(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- container_decl: ast.full.ContainerDecl,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const token_starts = tree.tokens.items(.start);
- const node_tags = tree.nodes.items(.tag);
- const token_tags = tree.tokens.items(.tag);
-
- const src = token_starts[container_decl.ast.main_token];
-
- var gen_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .force_comptime = scope.isComptime(),
- .instructions = .{},
- };
- defer gen_scope.instructions.deinit(mod.gpa);
-
- var fields = std.ArrayList(*zir.Inst).init(mod.gpa);
- defer fields.deinit();
-
- for (container_decl.ast.members) |member| {
- // TODO just handle these cases differently since they end up with different ZIR
- // instructions anyway. It will be simpler & have fewer branches.
- const field = switch (node_tags[member]) {
- .container_field_init => try containerField(mod, &gen_scope.base, tree.containerFieldInit(member)),
- .container_field_align => try containerField(mod, &gen_scope.base, tree.containerFieldAlign(member)),
- .container_field => try containerField(mod, &gen_scope.base, tree.containerField(member)),
- else => continue,
- };
- try fields.append(field);
- }
-
- var decl_arena = std.heap.ArenaAllocator.init(mod.gpa);
- errdefer decl_arena.deinit();
- const arena = &decl_arena.allocator;
-
- var layout: std.builtin.TypeInfo.ContainerLayout = .Auto;
- if (container_decl.layout_token) |some| switch (token_tags[some]) {
- .keyword_extern => layout = .Extern,
- .keyword_packed => layout = .Packed,
- else => unreachable,
- };
-
- // TODO this implementation is incorrect. The types must be created in semantic
- // analysis, not astgen, because the same ZIR is re-used for multiple inline function calls,
- // comptime function calls, and generic function instantiations, and these
- // must result in different instances of container types.
- const container_type = switch (token_tags[container_decl.ast.main_token]) {
- .keyword_enum => blk: {
- const tag_type: ?*zir.Inst = if (container_decl.ast.arg != 0)
- try typeExpr(mod, &gen_scope.base, container_decl.ast.arg)
- else
- null;
- const inst = try addZIRInst(mod, &gen_scope.base, src, zir.Inst.EnumType, .{
- .fields = try arena.dupe(*zir.Inst, fields.items),
- }, .{
- .layout = layout,
- .tag_type = tag_type,
- });
- const enum_type = try arena.create(Type.Payload.Enum);
- enum_type.* = .{
- .analysis = .{
- .queued = .{
- .body = .{ .instructions = try arena.dupe(*zir.Inst, gen_scope.instructions.items) },
- .inst = inst,
- },
- },
- .scope = .{
- .file_scope = scope.getFileScope(),
- .ty = Type.initPayload(&enum_type.base),
- },
- };
- break :blk Type.initPayload(&enum_type.base);
- },
- .keyword_struct => blk: {
- assert(container_decl.ast.arg == 0);
- const inst = try addZIRInst(mod, &gen_scope.base, src, zir.Inst.StructType, .{
- .fields = try arena.dupe(*zir.Inst, fields.items),
- }, .{
- .layout = layout,
- });
- const struct_type = try arena.create(Type.Payload.Struct);
- struct_type.* = .{
- .analysis = .{
- .queued = .{
- .body = .{ .instructions = try arena.dupe(*zir.Inst, gen_scope.instructions.items) },
- .inst = inst,
- },
- },
- .scope = .{
- .file_scope = scope.getFileScope(),
- .ty = Type.initPayload(&struct_type.base),
- },
- };
- break :blk Type.initPayload(&struct_type.base);
- },
- .keyword_union => blk: {
- const init_inst: ?*zir.Inst = if (container_decl.ast.arg != 0)
- try typeExpr(mod, &gen_scope.base, container_decl.ast.arg)
- else
- null;
- const has_enum_token = container_decl.ast.enum_token != null;
- const inst = try addZIRInst(mod, &gen_scope.base, src, zir.Inst.UnionType, .{
- .fields = try arena.dupe(*zir.Inst, fields.items),
- }, .{
- .layout = layout,
- .has_enum_token = has_enum_token,
- .init_inst = init_inst,
- });
- const union_type = try arena.create(Type.Payload.Union);
- union_type.* = .{
- .analysis = .{
- .queued = .{
- .body = .{ .instructions = try arena.dupe(*zir.Inst, gen_scope.instructions.items) },
- .inst = inst,
- },
- },
- .scope = .{
- .file_scope = scope.getFileScope(),
- .ty = Type.initPayload(&union_type.base),
- },
- };
- break :blk Type.initPayload(&union_type.base);
- },
- .keyword_opaque => blk: {
- if (fields.items.len > 0) {
- return mod.fail(scope, fields.items[0].src, "opaque types cannot have fields", .{});
- }
- const opaque_type = try arena.create(Type.Payload.Opaque);
- opaque_type.* = .{
- .scope = .{
- .file_scope = scope.getFileScope(),
- .ty = Type.initPayload(&opaque_type.base),
- },
- };
- break :blk Type.initPayload(&opaque_type.base);
- },
- else => unreachable,
- };
- const val = try Value.Tag.ty.create(arena, container_type);
- const decl = try mod.createContainerDecl(scope, container_decl.ast.main_token, &decl_arena, .{
- .ty = Type.initTag(.type),
- .val = val,
- });
- if (rl == .ref) {
- return addZIRInst(mod, scope, src, zir.Inst.DeclRef, .{ .decl = decl }, .{});
- } else {
- return rvalue(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclVal, .{
- .decl = decl,
- }, .{}));
- }
-}
-
-fn errorSetDecl(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- node: ast.Node.Index,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_tags = tree.tokens.items(.tag);
- const token_starts = tree.tokens.items(.start);
-
- // Count how many fields there are.
- const error_token = main_tokens[node];
- const count: usize = count: {
- var tok_i = error_token + 2;
- var count: usize = 0;
- while (true) : (tok_i += 1) {
- switch (token_tags[tok_i]) {
- .doc_comment, .comma => {},
- .identifier => count += 1,
- .r_brace => break :count count,
- else => unreachable,
- }
- } else unreachable; // TODO should not need else unreachable here
- };
-
- const fields = try scope.arena().alloc([]const u8, count);
- {
- var tok_i = error_token + 2;
- var field_i: usize = 0;
- while (true) : (tok_i += 1) {
- switch (token_tags[tok_i]) {
- .doc_comment, .comma => {},
- .identifier => {
- fields[field_i] = try mod.identifierTokenString(scope, tok_i);
- field_i += 1;
- },
- .r_brace => break,
- else => unreachable,
- }
- }
- }
- const src = token_starts[error_token];
- const result = try addZIRInst(mod, scope, src, zir.Inst.ErrorSet, .{ .fields = fields }, .{});
- return rvalue(mod, scope, rl, result);
-}
-
-fn orelseCatchExpr(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- lhs: ast.Node.Index,
- op_token: ast.TokenIndex,
- cond_op: zir.Inst.Tag,
- unwrap_op: zir.Inst.Tag,
- unwrap_code_op: zir.Inst.Tag,
- rhs: ast.Node.Index,
- payload_token: ?ast.TokenIndex,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[op_token];
-
- var block_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .force_comptime = scope.isComptime(),
- .instructions = .{},
- };
- setBlockResultLoc(&block_scope, rl);
- defer block_scope.instructions.deinit(mod.gpa);
-
- // This could be a pointer or value depending on the `operand_rl` parameter.
- // We cannot use `block_scope.break_result_loc` because that has the bare
- // type, whereas this expression has the optional type. Later we make
- // up for this fact by calling rvalue on the else branch.
- block_scope.break_count += 1;
- const operand_rl = try makeOptionalTypeResultLoc(mod, &block_scope.base, src, block_scope.break_result_loc);
- const operand = try expr(mod, &block_scope.base, operand_rl, lhs);
- const cond = try addZIRUnOp(mod, &block_scope.base, src, cond_op, operand);
-
- const condbr = try addZIRInstSpecial(mod, &block_scope.base, src, zir.Inst.CondBr, .{
- .condition = cond,
- .then_body = undefined, // populated below
- .else_body = undefined, // populated below
- }, .{});
-
- const block = try addZIRInstBlock(mod, scope, src, .block, .{
- .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
- });
-
- var then_scope: Scope.GenZIR = .{
- .parent = &block_scope.base,
- .decl = block_scope.decl,
- .arena = block_scope.arena,
- .force_comptime = block_scope.force_comptime,
- .instructions = .{},
- };
- defer then_scope.instructions.deinit(mod.gpa);
-
- var err_val_scope: Scope.LocalVal = undefined;
- const then_sub_scope = blk: {
- const payload = payload_token orelse break :blk &then_scope.base;
- if (mem.eql(u8, tree.tokenSlice(payload), "_")) {
- return mod.failTok(&then_scope.base, payload, "discard of error capture; omit it instead", .{});
- }
- const err_name = try mod.identifierTokenString(scope, payload);
- err_val_scope = .{
- .parent = &then_scope.base,
- .gen_zir = &then_scope,
- .name = err_name,
- .inst = try addZIRUnOp(mod, &then_scope.base, src, unwrap_code_op, operand),
- };
- break :blk &err_val_scope.base;
- };
-
- block_scope.break_count += 1;
- const then_result = try expr(mod, then_sub_scope, block_scope.break_result_loc, rhs);
-
- var else_scope: Scope.GenZIR = .{
- .parent = &block_scope.base,
- .decl = block_scope.decl,
- .arena = block_scope.arena,
- .force_comptime = block_scope.force_comptime,
- .instructions = .{},
- };
- defer else_scope.instructions.deinit(mod.gpa);
-
- // This could be a pointer or value depending on `unwrap_op`.
- const unwrapped_payload = try addZIRUnOp(mod, &else_scope.base, src, unwrap_op, operand);
- const else_result = switch (rl) {
- .ref => unwrapped_payload,
- else => try rvalue(mod, &else_scope.base, block_scope.break_result_loc, unwrapped_payload),
- };
-
- return finishThenElseBlock(
- mod,
- scope,
- rl,
- &block_scope,
- &then_scope,
- &else_scope,
- &condbr.positionals.then_body,
- &condbr.positionals.else_body,
- src,
- src,
- then_result,
- else_result,
- block,
- block,
- );
-}
-
-fn finishThenElseBlock(
- mod: *Module,
- parent_scope: *Scope,
- rl: ResultLoc,
- block_scope: *Scope.GenZIR,
- then_scope: *Scope.GenZIR,
- else_scope: *Scope.GenZIR,
- then_body: *zir.Body,
- else_body: *zir.Body,
- then_src: usize,
- else_src: usize,
- then_result: *zir.Inst,
- else_result: ?*zir.Inst,
- main_block: *zir.Inst.Block,
- then_break_block: *zir.Inst.Block,
-) InnerError!*zir.Inst {
- // We now have enough information to decide whether the result instruction should
- // be communicated via result location pointer or break instructions.
- const strat = rlStrategy(rl, block_scope);
- switch (strat.tag) {
- .break_void => {
- if (!then_result.tag.isNoReturn()) {
- _ = try addZirInstTag(mod, &then_scope.base, then_src, .break_void, .{
- .block = then_break_block,
- });
- }
- if (else_result) |inst| {
- if (!inst.tag.isNoReturn()) {
- _ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{
- .block = main_block,
- });
- }
- } else {
- _ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{
- .block = main_block,
- });
- }
- assert(!strat.elide_store_to_block_ptr_instructions);
- try copyBodyNoEliding(then_body, then_scope.*);
- try copyBodyNoEliding(else_body, else_scope.*);
- return &main_block.base;
- },
- .break_operand => {
- if (!then_result.tag.isNoReturn()) {
- _ = try addZirInstTag(mod, &then_scope.base, then_src, .@"break", .{
- .block = then_break_block,
- .operand = then_result,
- });
- }
- if (else_result) |inst| {
- if (!inst.tag.isNoReturn()) {
- _ = try addZirInstTag(mod, &else_scope.base, else_src, .@"break", .{
- .block = main_block,
- .operand = inst,
- });
- }
- } else {
- _ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{
- .block = main_block,
- });
- }
- if (strat.elide_store_to_block_ptr_instructions) {
- try copyBodyWithElidedStoreBlockPtr(then_body, then_scope.*);
- try copyBodyWithElidedStoreBlockPtr(else_body, else_scope.*);
- } else {
- try copyBodyNoEliding(then_body, then_scope.*);
- try copyBodyNoEliding(else_body, else_scope.*);
- }
- switch (rl) {
- .ref => return &main_block.base,
- else => return rvalue(mod, parent_scope, rl, &main_block.base),
- }
- },
- }
-}
-
-/// Return whether the identifier names of two tokens are equal. Resolves @""
-/// tokens without allocating.
-/// OK in theory it could do it without allocating. This implementation
-/// allocates when the @"" form is used.
-fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool {
- const ident_name_1 = try mod.identifierTokenString(scope, token1);
- const ident_name_2 = try mod.identifierTokenString(scope, token2);
- return mem.eql(u8, ident_name_1, ident_name_2);
-}
-
-pub fn fieldAccess(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- const token_starts = tree.tokens.items(.start);
- const main_tokens = tree.nodes.items(.main_token);
- const node_datas = tree.nodes.items(.data);
-
- const dot_token = main_tokens[node];
- const src = token_starts[dot_token];
- const field_ident = dot_token + 1;
- const field_name = try mod.identifierTokenString(scope, field_ident);
- if (rl == .ref) {
- return addZirInstTag(mod, scope, src, .field_ptr, .{
- .object = try expr(mod, scope, .ref, node_datas[node].lhs),
- .field_name = field_name,
- });
- } else {
- return rvalue(mod, scope, rl, try addZirInstTag(mod, scope, src, .field_val, .{
- .object = try expr(mod, scope, .none, node_datas[node].lhs),
- .field_name = field_name,
- }));
- }
-}
-
-fn arrayAccess(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- node: ast.Node.Index,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
- const node_datas = tree.nodes.items(.data);
-
- const src = token_starts[main_tokens[node]];
- const usize_type = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.usize_type),
- });
- const index_rl: ResultLoc = .{ .ty = usize_type };
- switch (rl) {
- .ref => return addZirInstTag(mod, scope, src, .elem_ptr, .{
- .array = try expr(mod, scope, .ref, node_datas[node].lhs),
- .index = try expr(mod, scope, index_rl, node_datas[node].rhs),
- }),
- else => return rvalue(mod, scope, rl, try addZirInstTag(mod, scope, src, .elem_val, .{
- .array = try expr(mod, scope, .none, node_datas[node].lhs),
- .index = try expr(mod, scope, index_rl, node_datas[node].rhs),
- })),
- }
-}
-
-fn sliceExpr(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- slice: ast.full.Slice,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[slice.ast.lbracket];
-
- const usize_type = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.usize_type),
- });
-
- const array_ptr = try expr(mod, scope, .ref, slice.ast.sliced);
- const start = try expr(mod, scope, .{ .ty = usize_type }, slice.ast.start);
-
- if (slice.ast.sentinel == 0) {
- if (slice.ast.end == 0) {
- const result = try addZIRBinOp(mod, scope, src, .slice_start, array_ptr, start);
- return rvalue(mod, scope, rl, result);
- } else {
- const end = try expr(mod, scope, .{ .ty = usize_type }, slice.ast.end);
- // TODO a ZIR slice_open instruction
- const result = try addZIRInst(mod, scope, src, zir.Inst.Slice, .{
- .array_ptr = array_ptr,
- .start = start,
- }, .{ .end = end });
- return rvalue(mod, scope, rl, result);
- }
- }
-
- const end = try expr(mod, scope, .{ .ty = usize_type }, slice.ast.end);
- // TODO pass the proper result loc to this expression using a ZIR instruction
- // "get the child element type for a slice target".
- const sentinel = try expr(mod, scope, .none, slice.ast.sentinel);
- const result = try addZIRInst(mod, scope, src, zir.Inst.Slice, .{
- .array_ptr = array_ptr,
- .start = start,
- }, .{
- .end = end,
- .sentinel = sentinel,
- });
- return rvalue(mod, scope, rl, result);
-}
-
-fn simpleBinOp(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- infix_node: ast.Node.Index,
- op_inst_tag: zir.Inst.Tag,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const lhs = try expr(mod, scope, .none, node_datas[infix_node].lhs);
- const rhs = try expr(mod, scope, .none, node_datas[infix_node].rhs);
- const src = token_starts[main_tokens[infix_node]];
- const result = try addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs);
- return rvalue(mod, scope, rl, result);
-}
-
-fn boolBinOp(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- infix_node: ast.Node.Index,
- is_bool_and: bool,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[main_tokens[infix_node]];
- const bool_type = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.bool_type),
- });
-
- var block_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .force_comptime = scope.isComptime(),
- .instructions = .{},
- };
- defer block_scope.instructions.deinit(mod.gpa);
-
- const lhs = try expr(mod, scope, .{ .ty = bool_type }, node_datas[infix_node].lhs);
- const condbr = try addZIRInstSpecial(mod, &block_scope.base, src, zir.Inst.CondBr, .{
- .condition = lhs,
- .then_body = undefined, // populated below
- .else_body = undefined, // populated below
- }, .{});
-
- const block = try addZIRInstBlock(mod, scope, src, .block, .{
- .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
- });
-
- var rhs_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = block_scope.decl,
- .arena = block_scope.arena,
- .force_comptime = block_scope.force_comptime,
- .instructions = .{},
- };
- defer rhs_scope.instructions.deinit(mod.gpa);
-
- const rhs = try expr(mod, &rhs_scope.base, .{ .ty = bool_type }, node_datas[infix_node].rhs);
- _ = try addZIRInst(mod, &rhs_scope.base, src, zir.Inst.Break, .{
- .block = block,
- .operand = rhs,
- }, .{});
-
- var const_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = block_scope.decl,
- .arena = block_scope.arena,
- .force_comptime = block_scope.force_comptime,
- .instructions = .{},
- };
- defer const_scope.instructions.deinit(mod.gpa);
-
- _ = try addZIRInst(mod, &const_scope.base, src, zir.Inst.Break, .{
- .block = block,
- .operand = try addZIRInstConst(mod, &const_scope.base, src, .{
- .ty = Type.initTag(.bool),
- .val = if (is_bool_and) Value.initTag(.bool_false) else Value.initTag(.bool_true),
- }),
- }, .{});
-
- if (is_bool_and) {
- // if lhs // AND
- // break rhs
- // else
- // break false
- condbr.positionals.then_body = .{ .instructions = try rhs_scope.arena.dupe(*zir.Inst, rhs_scope.instructions.items) };
- condbr.positionals.else_body = .{ .instructions = try const_scope.arena.dupe(*zir.Inst, const_scope.instructions.items) };
- } else {
- // if lhs // OR
- // break true
- // else
- // break rhs
- condbr.positionals.then_body = .{ .instructions = try const_scope.arena.dupe(*zir.Inst, const_scope.instructions.items) };
- condbr.positionals.else_body = .{ .instructions = try rhs_scope.arena.dupe(*zir.Inst, rhs_scope.instructions.items) };
- }
-
- return rvalue(mod, scope, rl, &block.base);
-}
-
-fn ifExpr(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- if_full: ast.full.If,
-) InnerError!*zir.Inst {
- var block_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .force_comptime = scope.isComptime(),
- .instructions = .{},
- };
- setBlockResultLoc(&block_scope, rl);
- defer block_scope.instructions.deinit(mod.gpa);
-
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const if_src = token_starts[if_full.ast.if_token];
-
- const cond = c: {
- // TODO https://github.com/ziglang/zig/issues/7929
- if (if_full.error_token) |error_token| {
- return mod.failTok(scope, error_token, "TODO implement if error union", .{});
- } else if (if_full.payload_token) |payload_token| {
- return mod.failTok(scope, payload_token, "TODO implement if optional", .{});
- } else {
- const bool_type = try addZIRInstConst(mod, &block_scope.base, if_src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.bool_type),
- });
- break :c try expr(mod, &block_scope.base, .{ .ty = bool_type }, if_full.ast.cond_expr);
- }
- };
-
- const condbr = try addZIRInstSpecial(mod, &block_scope.base, if_src, zir.Inst.CondBr, .{
- .condition = cond,
- .then_body = undefined, // populated below
- .else_body = undefined, // populated below
- }, .{});
-
- const block = try addZIRInstBlock(mod, scope, if_src, .block, .{
- .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
- });
-
- const then_src = token_starts[tree.lastToken(if_full.ast.then_expr)];
- var then_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = block_scope.decl,
- .arena = block_scope.arena,
- .force_comptime = block_scope.force_comptime,
- .instructions = .{},
- };
- defer then_scope.instructions.deinit(mod.gpa);
-
- // declare payload to the then_scope
- const then_sub_scope = &then_scope.base;
-
- block_scope.break_count += 1;
- const then_result = try expr(mod, then_sub_scope, block_scope.break_result_loc, if_full.ast.then_expr);
- // We hold off on the break instructions as well as copying the then/else
- // instructions into place until we know whether to keep store_to_block_ptr
- // instructions or not.
-
- var else_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = block_scope.decl,
- .arena = block_scope.arena,
- .force_comptime = block_scope.force_comptime,
- .instructions = .{},
- };
- defer else_scope.instructions.deinit(mod.gpa);
-
- const else_node = if_full.ast.else_expr;
- const else_info: struct { src: usize, result: ?*zir.Inst } = if (else_node != 0) blk: {
- block_scope.break_count += 1;
- const sub_scope = &else_scope.base;
- break :blk .{
- .src = token_starts[tree.lastToken(else_node)],
- .result = try expr(mod, sub_scope, block_scope.break_result_loc, else_node),
- };
- } else .{
- .src = token_starts[tree.lastToken(if_full.ast.then_expr)],
- .result = null,
- };
-
- return finishThenElseBlock(
- mod,
- scope,
- rl,
- &block_scope,
- &then_scope,
- &else_scope,
- &condbr.positionals.then_body,
- &condbr.positionals.else_body,
- then_src,
- else_info.src,
- then_result,
- else_info.result,
- block,
- block,
- );
-}
-
-/// Expects to find exactly 1 .store_to_block_ptr instruction.
-fn copyBodyWithElidedStoreBlockPtr(body: *zir.Body, scope: Module.Scope.GenZIR) !void {
- body.* = .{
- .instructions = try scope.arena.alloc(*zir.Inst, scope.instructions.items.len - 1),
- };
- var dst_index: usize = 0;
- for (scope.instructions.items) |src_inst| {
- if (src_inst.tag != .store_to_block_ptr) {
- body.instructions[dst_index] = src_inst;
- dst_index += 1;
- }
- }
- assert(dst_index == body.instructions.len);
-}
-
-fn copyBodyNoEliding(body: *zir.Body, scope: Module.Scope.GenZIR) !void {
- body.* = .{
- .instructions = try scope.arena.dupe(*zir.Inst, scope.instructions.items),
- };
-}
-
-fn whileExpr(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- while_full: ast.full.While,
-) InnerError!*zir.Inst {
- if (while_full.label_token) |label_token| {
- try checkLabelRedefinition(mod, scope, label_token);
- }
- if (while_full.inline_token) |inline_token| {
- return mod.failTok(scope, inline_token, "TODO inline while", .{});
- }
-
- var loop_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .force_comptime = scope.isComptime(),
- .instructions = .{},
- };
- setBlockResultLoc(&loop_scope, rl);
- defer loop_scope.instructions.deinit(mod.gpa);
-
- var continue_scope: Scope.GenZIR = .{
- .parent = &loop_scope.base,
- .decl = loop_scope.decl,
- .arena = loop_scope.arena,
- .force_comptime = loop_scope.force_comptime,
- .instructions = .{},
- };
- defer continue_scope.instructions.deinit(mod.gpa);
-
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const while_src = token_starts[while_full.ast.while_token];
- const void_type = try addZIRInstConst(mod, scope, while_src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.void_type),
- });
- const cond = c: {
- // TODO https://github.com/ziglang/zig/issues/7929
- if (while_full.error_token) |error_token| {
- return mod.failTok(scope, error_token, "TODO implement while error union", .{});
- } else if (while_full.payload_token) |payload_token| {
- return mod.failTok(scope, payload_token, "TODO implement while optional", .{});
- } else {
- const bool_type = try addZIRInstConst(mod, &continue_scope.base, while_src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.bool_type),
- });
- break :c try expr(mod, &continue_scope.base, .{ .ty = bool_type }, while_full.ast.cond_expr);
- }
- };
-
- const condbr = try addZIRInstSpecial(mod, &continue_scope.base, while_src, zir.Inst.CondBr, .{
- .condition = cond,
- .then_body = undefined, // populated below
- .else_body = undefined, // populated below
- }, .{});
- const cond_block = try addZIRInstBlock(mod, &loop_scope.base, while_src, .block, .{
- .instructions = try loop_scope.arena.dupe(*zir.Inst, continue_scope.instructions.items),
- });
- // TODO avoid emitting the continue expr when there
- // are no jumps to it. This happens when the last statement of a while body is noreturn
- // and there are no `continue` statements.
- // The "repeat" at the end of a loop body is implied.
- if (while_full.ast.cont_expr != 0) {
- _ = try expr(mod, &loop_scope.base, .{ .ty = void_type }, while_full.ast.cont_expr);
- }
- const loop = try scope.arena().create(zir.Inst.Loop);
- loop.* = .{
- .base = .{
- .tag = .loop,
- .src = while_src,
- },
- .positionals = .{
- .body = .{
- .instructions = try scope.arena().dupe(*zir.Inst, loop_scope.instructions.items),
- },
- },
- .kw_args = .{},
- };
- const while_block = try addZIRInstBlock(mod, scope, while_src, .block, .{
- .instructions = try scope.arena().dupe(*zir.Inst, &[1]*zir.Inst{&loop.base}),
- });
- loop_scope.break_block = while_block;
- loop_scope.continue_block = cond_block;
- if (while_full.label_token) |label_token| {
- loop_scope.label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{
- .token = label_token,
- .block_inst = while_block,
- });
- }
-
- const then_src = token_starts[tree.lastToken(while_full.ast.then_expr)];
- var then_scope: Scope.GenZIR = .{
- .parent = &continue_scope.base,
- .decl = continue_scope.decl,
- .arena = continue_scope.arena,
- .force_comptime = continue_scope.force_comptime,
- .instructions = .{},
- };
- defer then_scope.instructions.deinit(mod.gpa);
-
- const then_sub_scope = &then_scope.base;
-
- loop_scope.break_count += 1;
- const then_result = try expr(mod, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr);
-
- var else_scope: Scope.GenZIR = .{
- .parent = &continue_scope.base,
- .decl = continue_scope.decl,
- .arena = continue_scope.arena,
- .force_comptime = continue_scope.force_comptime,
- .instructions = .{},
- };
- defer else_scope.instructions.deinit(mod.gpa);
-
- const else_node = while_full.ast.else_expr;
- const else_info: struct { src: usize, result: ?*zir.Inst } = if (else_node != 0) blk: {
- loop_scope.break_count += 1;
- const sub_scope = &else_scope.base;
- break :blk .{
- .src = token_starts[tree.lastToken(else_node)],
- .result = try expr(mod, sub_scope, loop_scope.break_result_loc, else_node),
- };
- } else .{
- .src = token_starts[tree.lastToken(while_full.ast.then_expr)],
- .result = null,
- };
-
- if (loop_scope.label) |some| {
- if (!some.used) {
- return mod.fail(scope, token_starts[some.token], "unused while loop label", .{});
- }
- }
- return finishThenElseBlock(
- mod,
- scope,
- rl,
- &loop_scope,
- &then_scope,
- &else_scope,
- &condbr.positionals.then_body,
- &condbr.positionals.else_body,
- then_src,
- else_info.src,
- then_result,
- else_info.result,
- while_block,
- cond_block,
- );
-}
-
-fn forExpr(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- for_full: ast.full.While,
-) InnerError!*zir.Inst {
- if (for_full.label_token) |label_token| {
- try checkLabelRedefinition(mod, scope, label_token);
- }
-
- if (for_full.inline_token) |inline_token| {
- return mod.failTok(scope, inline_token, "TODO inline for", .{});
- }
-
- // Set up variables and constants.
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
- const token_tags = tree.tokens.items(.tag);
-
- const for_src = token_starts[for_full.ast.while_token];
- const index_ptr = blk: {
- const usize_type = try addZIRInstConst(mod, scope, for_src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.usize_type),
- });
- const index_ptr = try addZIRUnOp(mod, scope, for_src, .alloc, usize_type);
- // initialize to zero
- const zero = try addZIRInstConst(mod, scope, for_src, .{
- .ty = Type.initTag(.usize),
- .val = Value.initTag(.zero),
- });
- _ = try addZIRBinOp(mod, scope, for_src, .store, index_ptr, zero);
- break :blk index_ptr;
- };
- const array_ptr = try expr(mod, scope, .ref, for_full.ast.cond_expr);
- const cond_src = token_starts[tree.firstToken(for_full.ast.cond_expr)];
- const len = try addZIRUnOp(mod, scope, cond_src, .indexable_ptr_len, array_ptr);
-
- var loop_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .force_comptime = scope.isComptime(),
- .instructions = .{},
- };
- setBlockResultLoc(&loop_scope, rl);
- defer loop_scope.instructions.deinit(mod.gpa);
-
- var cond_scope: Scope.GenZIR = .{
- .parent = &loop_scope.base,
- .decl = loop_scope.decl,
- .arena = loop_scope.arena,
- .force_comptime = loop_scope.force_comptime,
- .instructions = .{},
- };
- defer cond_scope.instructions.deinit(mod.gpa);
-
- // check condition i < array_expr.len
- const index = try addZIRUnOp(mod, &cond_scope.base, cond_src, .deref, index_ptr);
- const cond = try addZIRBinOp(mod, &cond_scope.base, cond_src, .cmp_lt, index, len);
-
- const condbr = try addZIRInstSpecial(mod, &cond_scope.base, for_src, zir.Inst.CondBr, .{
- .condition = cond,
- .then_body = undefined, // populated below
- .else_body = undefined, // populated below
- }, .{});
- const cond_block = try addZIRInstBlock(mod, &loop_scope.base, for_src, .block, .{
- .instructions = try loop_scope.arena.dupe(*zir.Inst, cond_scope.instructions.items),
- });
-
- // increment index variable
- const one = try addZIRInstConst(mod, &loop_scope.base, for_src, .{
- .ty = Type.initTag(.usize),
- .val = Value.initTag(.one),
- });
- const index_2 = try addZIRUnOp(mod, &loop_scope.base, cond_src, .deref, index_ptr);
- const index_plus_one = try addZIRBinOp(mod, &loop_scope.base, for_src, .add, index_2, one);
- _ = try addZIRBinOp(mod, &loop_scope.base, for_src, .store, index_ptr, index_plus_one);
-
- const loop = try scope.arena().create(zir.Inst.Loop);
- loop.* = .{
- .base = .{
- .tag = .loop,
- .src = for_src,
- },
- .positionals = .{
- .body = .{
- .instructions = try scope.arena().dupe(*zir.Inst, loop_scope.instructions.items),
- },
- },
- .kw_args = .{},
- };
- const for_block = try addZIRInstBlock(mod, scope, for_src, .block, .{
- .instructions = try scope.arena().dupe(*zir.Inst, &[1]*zir.Inst{&loop.base}),
- });
- loop_scope.break_block = for_block;
- loop_scope.continue_block = cond_block;
- if (for_full.label_token) |label_token| {
- loop_scope.label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{
- .token = label_token,
- .block_inst = for_block,
- });
- }
-
- // while body
- const then_src = token_starts[tree.lastToken(for_full.ast.then_expr)];
- var then_scope: Scope.GenZIR = .{
- .parent = &cond_scope.base,
- .decl = cond_scope.decl,
- .arena = cond_scope.arena,
- .force_comptime = cond_scope.force_comptime,
- .instructions = .{},
- };
- defer then_scope.instructions.deinit(mod.gpa);
-
- var index_scope: Scope.LocalPtr = undefined;
- const then_sub_scope = blk: {
- const payload_token = for_full.payload_token.?;
- const ident = if (token_tags[payload_token] == .asterisk)
- payload_token + 1
- else
- payload_token;
- const is_ptr = ident != payload_token;
- const value_name = tree.tokenSlice(ident);
- if (!mem.eql(u8, value_name, "_")) {
- return mod.failNode(&then_scope.base, ident, "TODO implement for loop value payload", .{});
- } else if (is_ptr) {
- return mod.failTok(&then_scope.base, payload_token, "pointer modifier invalid on discard", .{});
- }
-
- const index_token = if (token_tags[ident + 1] == .comma)
- ident + 2
- else
- break :blk &then_scope.base;
- if (mem.eql(u8, tree.tokenSlice(index_token), "_")) {
- return mod.failTok(&then_scope.base, index_token, "discard of index capture; omit it instead", .{});
- }
- const index_name = try mod.identifierTokenString(&then_scope.base, index_token);
- index_scope = .{
- .parent = &then_scope.base,
- .gen_zir = &then_scope,
- .name = index_name,
- .ptr = index_ptr,
- };
- break :blk &index_scope.base;
- };
-
- loop_scope.break_count += 1;
- const then_result = try expr(mod, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr);
-
- // else branch
- var else_scope: Scope.GenZIR = .{
- .parent = &cond_scope.base,
- .decl = cond_scope.decl,
- .arena = cond_scope.arena,
- .force_comptime = cond_scope.force_comptime,
- .instructions = .{},
- };
- defer else_scope.instructions.deinit(mod.gpa);
-
- const else_node = for_full.ast.else_expr;
- const else_info: struct { src: usize, result: ?*zir.Inst } = if (else_node != 0) blk: {
- loop_scope.break_count += 1;
- const sub_scope = &else_scope.base;
- break :blk .{
- .src = token_starts[tree.lastToken(else_node)],
- .result = try expr(mod, sub_scope, loop_scope.break_result_loc, else_node),
- };
- } else .{
- .src = token_starts[tree.lastToken(for_full.ast.then_expr)],
- .result = null,
- };
-
- if (loop_scope.label) |some| {
- if (!some.used) {
- return mod.fail(scope, token_starts[some.token], "unused for loop label", .{});
- }
- }
- return finishThenElseBlock(
- mod,
- scope,
- rl,
- &loop_scope,
- &then_scope,
- &else_scope,
- &condbr.positionals.then_body,
- &condbr.positionals.else_body,
- then_src,
- else_info.src,
- then_result,
- else_info.result,
- for_block,
- cond_block,
- );
-}
-
-fn getRangeNode(
- node_tags: []const ast.Node.Tag,
- node_datas: []const ast.Node.Data,
- start_node: ast.Node.Index,
-) ?ast.Node.Index {
- var node = start_node;
- while (true) {
- switch (node_tags[node]) {
- .switch_range => return node,
- .grouped_expression => node = node_datas[node].lhs,
- else => return null,
- }
- }
-}
-
-fn switchExpr(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- switch_node: ast.Node.Index,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_tags = tree.tokens.items(.tag);
- const token_starts = tree.tokens.items(.start);
- const node_tags = tree.nodes.items(.tag);
-
- const switch_token = main_tokens[switch_node];
- const target_node = node_datas[switch_node].lhs;
- const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange);
- const case_nodes = tree.extra_data[extra.start..extra.end];
-
- const switch_src = token_starts[switch_token];
-
- var block_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .force_comptime = scope.isComptime(),
- .instructions = .{},
- };
- setBlockResultLoc(&block_scope, rl);
- defer block_scope.instructions.deinit(mod.gpa);
-
- var items = std.ArrayList(*zir.Inst).init(mod.gpa);
- defer items.deinit();
-
- // First we gather all the switch items and check else/'_' prongs.
- var else_src: ?usize = null;
- var underscore_src: ?usize = null;
- var first_range: ?*zir.Inst = null;
- var simple_case_count: usize = 0;
- var any_payload_is_ref = false;
- for (case_nodes) |case_node| {
- const case = switch (node_tags[case_node]) {
- .switch_case_one => tree.switchCaseOne(case_node),
- .switch_case => tree.switchCase(case_node),
- else => unreachable,
- };
- if (case.payload_token) |payload_token| {
- if (token_tags[payload_token] == .asterisk) {
- any_payload_is_ref = true;
- }
- }
- // Check for else/_ prong, those are handled last.
- if (case.ast.values.len == 0) {
- const case_src = token_starts[case.ast.arrow_token - 1];
- if (else_src) |src| {
- const msg = msg: {
- const msg = try mod.errMsg(
- scope,
- case_src,
- "multiple else prongs in switch expression",
- .{},
- );
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, src, msg, "previous else prong is here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
- else_src = case_src;
- continue;
- } else if (case.ast.values.len == 1 and
- node_tags[case.ast.values[0]] == .identifier and
- mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
- {
- const case_src = token_starts[case.ast.arrow_token - 1];
- if (underscore_src) |src| {
- const msg = msg: {
- const msg = try mod.errMsg(
- scope,
- case_src,
- "multiple '_' prongs in switch expression",
- .{},
- );
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
- underscore_src = case_src;
- continue;
- }
-
- if (else_src) |some_else| {
- if (underscore_src) |some_underscore| {
- const msg = msg: {
- const msg = try mod.errMsg(
- scope,
- switch_src,
- "else and '_' prong in switch expression",
- .{},
- );
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, some_else, msg, "else prong is here", .{});
- try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
- }
-
- if (case.ast.values.len == 1 and
- getRangeNode(node_tags, node_datas, case.ast.values[0]) == null)
- {
- simple_case_count += 1;
- }
-
- // Generate all the switch items as comptime expressions.
- for (case.ast.values) |item| {
- if (getRangeNode(node_tags, node_datas, item)) |range| {
- const start = try comptimeExpr(mod, &block_scope.base, .none, node_datas[range].lhs);
- const end = try comptimeExpr(mod, &block_scope.base, .none, node_datas[range].rhs);
- const range_src = token_starts[main_tokens[range]];
- const range_inst = try addZIRBinOp(mod, &block_scope.base, range_src, .switch_range, start, end);
- try items.append(range_inst);
- } else {
- const item_inst = try comptimeExpr(mod, &block_scope.base, .none, item);
- try items.append(item_inst);
- }
- }
- }
-
- var special_prong: zir.Inst.SwitchBr.SpecialProng = .none;
- if (else_src != null) special_prong = .@"else";
- if (underscore_src != null) special_prong = .underscore;
- var cases = try block_scope.arena.alloc(zir.Inst.SwitchBr.Case, simple_case_count);
-
- const rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref)
- .{
- .rl = .ref,
- .tag = .switchbr_ref,
- }
- else
- .{
- .rl = .none,
- .tag = .switchbr,
- };
- const target = try expr(mod, &block_scope.base, rl_and_tag.rl, target_node);
- const switch_inst = try addZirInstT(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, rl_and_tag.tag, .{
- .target = target,
- .cases = cases,
- .items = try block_scope.arena.dupe(*zir.Inst, items.items),
- .else_body = undefined, // populated below
- .range = first_range,
- .special_prong = special_prong,
- });
- const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{
- .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
- });
-
- var case_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = block_scope.decl,
- .arena = block_scope.arena,
- .force_comptime = block_scope.force_comptime,
- .instructions = .{},
- };
- defer case_scope.instructions.deinit(mod.gpa);
-
- var else_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = case_scope.decl,
- .arena = case_scope.arena,
- .force_comptime = case_scope.force_comptime,
- .instructions = .{},
- };
- defer else_scope.instructions.deinit(mod.gpa);
-
- // Now generate all but the special cases.
- var special_case: ?ast.full.SwitchCase = null;
- var items_index: usize = 0;
- var case_index: usize = 0;
- for (case_nodes) |case_node| {
- const case = switch (node_tags[case_node]) {
- .switch_case_one => tree.switchCaseOne(case_node),
- .switch_case => tree.switchCase(case_node),
- else => unreachable,
- };
- const case_src = token_starts[main_tokens[case_node]];
- case_scope.instructions.shrinkRetainingCapacity(0);
-
- // Check for else/_ prong, those are handled last.
- if (case.ast.values.len == 0) {
- special_case = case;
- continue;
- } else if (case.ast.values.len == 1 and
- node_tags[case.ast.values[0]] == .identifier and
- mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
- {
- special_case = case;
- continue;
- }
-
- // If this is a simple one item prong then it is handled by the switchbr.
- if (case.ast.values.len == 1 and
- getRangeNode(node_tags, node_datas, case.ast.values[0]) == null)
- {
- const item = items.items[items_index];
- items_index += 1;
- try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target);
-
- cases[case_index] = .{
- .item = item,
- .body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) },
- };
- case_index += 1;
- continue;
- }
-
- // Check if the target matches any of the items.
- // 1, 2, 3..6 will result in
- // target == 1 or target == 2 or (target >= 3 and target <= 6)
- // TODO handle multiple items as switch prongs rather than along with ranges.
- var any_ok: ?*zir.Inst = null;
- for (case.ast.values) |item| {
- if (getRangeNode(node_tags, node_datas, item)) |range| {
- const range_src = token_starts[main_tokens[range]];
- const range_inst = items.items[items_index].castTag(.switch_range).?;
- items_index += 1;
-
- // target >= start and target <= end
- const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, range_inst.positionals.lhs);
- const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, range_inst.positionals.rhs);
- const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok);
-
- if (any_ok) |some| {
- any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok);
- } else {
- any_ok = range_ok;
- }
- continue;
- }
-
- const item_inst = items.items[items_index];
- items_index += 1;
- const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst);
-
- if (any_ok) |some| {
- any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok);
- } else {
- any_ok = cpm_ok;
- }
- }
-
- const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{
- .condition = any_ok.?,
- .then_body = undefined, // populated below
- .else_body = undefined, // populated below
- }, .{});
- const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{
- .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
- });
-
- // reset cond_scope for then_body
- case_scope.instructions.items.len = 0;
- try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target);
- condbr.positionals.then_body = .{
- .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
- };
-
- // reset cond_scope for else_body
- case_scope.instructions.items.len = 0;
- _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{
- .block = cond_block,
- }, .{});
- condbr.positionals.else_body = .{
- .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items),
- };
- }
-
- // Finally generate else block or a break.
- if (special_case) |case| {
- try switchCaseExpr(mod, &else_scope.base, block_scope.break_result_loc, block, case, target);
- } else {
- // Not handling all possible cases is a compile error.
- _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe);
- }
- switch_inst.positionals.else_body = .{
- .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
- };
-
- return &block.base;
-}
-
-fn switchCaseExpr(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- block: *zir.Inst.Block,
- case: ast.full.SwitchCase,
- target: *zir.Inst,
-) !void {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
- const token_tags = tree.tokens.items(.tag);
-
- const case_src = token_starts[case.ast.arrow_token];
- const sub_scope = blk: {
- const payload_token = case.payload_token orelse break :blk scope;
- const ident = if (token_tags[payload_token] == .asterisk)
- payload_token + 1
- else
- payload_token;
- const is_ptr = ident != payload_token;
- const value_name = tree.tokenSlice(ident);
- if (mem.eql(u8, value_name, "_")) {
- if (is_ptr) {
- return mod.failTok(scope, payload_token, "pointer modifier invalid on discard", .{});
- }
- break :blk scope;
- }
- return mod.failTok(scope, ident, "TODO implement switch value payload", .{});
- };
-
- const case_body = try expr(mod, sub_scope, rl, case.ast.target_expr);
- if (!case_body.tag.isNoReturn()) {
- _ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{
- .block = block,
- .operand = case_body,
- }, .{});
- }
-}
-
-fn ret(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[main_tokens[node]];
- const rhs_node = node_datas[node].lhs;
- if (rhs_node != 0) {
- if (nodeMayNeedMemoryLocation(scope, rhs_node)) {
- const ret_ptr = try addZIRNoOp(mod, scope, src, .ret_ptr);
- const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node);
- return addZIRUnOp(mod, scope, src, .@"return", operand);
- } else {
- const fn_ret_ty = try addZIRNoOp(mod, scope, src, .ret_type);
- const operand = try expr(mod, scope, .{ .ty = fn_ret_ty }, rhs_node);
- return addZIRUnOp(mod, scope, src, .@"return", operand);
- }
- } else {
- return addZIRNoOp(mod, scope, src, .return_void);
- }
-}
-
-fn identifier(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- ident: ast.Node.Index,
-) InnerError!*zir.Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const ident_token = main_tokens[ident];
- const ident_name = try mod.identifierTokenString(scope, ident_token);
- const src = token_starts[ident_token];
- if (mem.eql(u8, ident_name, "_")) {
- return mod.failNode(scope, ident, "TODO implement '_' identifier", .{});
- }
-
- if (simple_types.get(ident_name)) |val_tag| {
- const result = try addZIRInstConst(mod, scope, src, TypedValue{
- .ty = Type.initTag(.type),
- .val = Value.initTag(val_tag),
- });
- return rvalue(mod, scope, rl, result);
- }
-
- if (ident_name.len >= 2) integer: {
- const first_c = ident_name[0];
- if (first_c == 'i' or first_c == 'u') {
- const is_signed = first_c == 'i';
- const bit_count = std.fmt.parseInt(u16, ident_name[1..], 10) catch |err| switch (err) {
- error.Overflow => return mod.failNode(
- scope,
- ident,
- "primitive integer type '{s}' exceeds maximum bit width of 65535",
- .{ident_name},
- ),
- error.InvalidCharacter => break :integer,
- };
- const val = switch (bit_count) {
- 8 => if (is_signed) Value.initTag(.i8_type) else Value.initTag(.u8_type),
- 16 => if (is_signed) Value.initTag(.i16_type) else Value.initTag(.u16_type),
- 32 => if (is_signed) Value.initTag(.i32_type) else Value.initTag(.u32_type),
- 64 => if (is_signed) Value.initTag(.i64_type) else Value.initTag(.u64_type),
- else => {
- return rvalue(mod, scope, rl, try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = try Value.Tag.int_type.create(scope.arena(), .{
- .signed = is_signed,
- .bits = bit_count,
- }),
- }));
- },
- };
- const result = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = val,
- });
- return rvalue(mod, scope, rl, result);
- }
- }
-
- // Local variables, including function parameters.
- {
- var s = scope;
- while (true) switch (s.tag) {
- .local_val => {
- const local_val = s.cast(Scope.LocalVal).?;
- if (mem.eql(u8, local_val.name, ident_name)) {
- return rvalue(mod, scope, rl, local_val.inst);
- }
- s = local_val.parent;
- },
- .local_ptr => {
- const local_ptr = s.cast(Scope.LocalPtr).?;
- if (mem.eql(u8, local_ptr.name, ident_name)) {
- if (rl == .ref) return local_ptr.ptr;
- const loaded = try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr);
- return rvalue(mod, scope, rl, loaded);
- }
- s = local_ptr.parent;
- },
- .gen_zir => s = s.cast(Scope.GenZIR).?.parent,
- .gen_suspend => s = s.cast(Scope.GenZIR).?.parent,
- .gen_nosuspend => s = s.cast(Scope.Nosuspend).?.parent,
- else => break,
- };
- }
-
- if (mod.lookupDeclName(scope, ident_name)) |decl| {
- if (rl == .ref) {
- return addZIRInst(mod, scope, src, zir.Inst.DeclRef, .{ .decl = decl }, .{});
- } else {
- return rvalue(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclVal, .{
- .decl = decl,
- }, .{}));
- }
- }
-
- return mod.failNode(scope, ident, "use of undeclared identifier '{s}'", .{ident_name});
-}
-
-fn parseStringLiteral(mod: *Module, scope: *Scope, token: ast.TokenIndex) ![]u8 {
- const tree = scope.tree();
- const token_tags = tree.tokens.items(.tag);
- const token_starts = tree.tokens.items(.start);
- assert(token_tags[token] == .string_literal);
- const unparsed = tree.tokenSlice(token);
- const arena = scope.arena();
- var bad_index: usize = undefined;
- const bytes = std.zig.parseStringLiteral(arena, unparsed, &bad_index) catch |err| switch (err) {
- error.InvalidCharacter => {
- const bad_byte = unparsed[bad_index];
- const src = token_starts[token];
- return mod.fail(scope, src + bad_index, "invalid string literal character: '{c}'", .{
- bad_byte,
- });
- },
- else => |e| return e,
- };
- return bytes;
-}
-
-fn stringLiteral(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- str_lit: ast.Node.Index,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const str_lit_token = main_tokens[str_lit];
- const bytes = try parseStringLiteral(mod, scope, str_lit_token);
- const src = token_starts[str_lit_token];
- const str_inst = try addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{});
- return rvalue(mod, scope, rl, str_inst);
-}
-
-fn multilineStringLiteral(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- str_lit: ast.Node.Index,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const start = node_datas[str_lit].lhs;
- const end = node_datas[str_lit].rhs;
-
- // Count the number of bytes to allocate.
- const len: usize = len: {
- var tok_i = start;
- var len: usize = end - start + 1;
- while (tok_i <= end) : (tok_i += 1) {
- // 2 for the '//' + 1 for '\n'
- len += tree.tokenSlice(tok_i).len - 3;
- }
- break :len len;
- };
- const bytes = try scope.arena().alloc(u8, len);
- // First line: do not append a newline.
- var byte_i: usize = 0;
- var tok_i = start;
- {
- const slice = tree.tokenSlice(tok_i);
- const line_bytes = slice[2 .. slice.len - 1];
- mem.copy(u8, bytes[byte_i..], line_bytes);
- byte_i += line_bytes.len;
- tok_i += 1;
- }
- // Following lines: each line prepends a newline.
- while (tok_i <= end) : (tok_i += 1) {
- bytes[byte_i] = '\n';
- byte_i += 1;
- const slice = tree.tokenSlice(tok_i);
- const line_bytes = slice[2 .. slice.len - 1];
- mem.copy(u8, bytes[byte_i..], line_bytes);
- byte_i += line_bytes.len;
- }
- const src = token_starts[start];
- const str_inst = try addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{});
- return rvalue(mod, scope, rl, str_inst);
-}
-
-fn charLiteral(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !*zir.Inst {
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const main_token = main_tokens[node];
- const token_starts = tree.tokens.items(.start);
-
- const src = token_starts[main_token];
- const slice = tree.tokenSlice(main_token);
-
- var bad_index: usize = undefined;
- const value = std.zig.parseCharLiteral(slice, &bad_index) catch |err| switch (err) {
- error.InvalidCharacter => {
- const bad_byte = slice[bad_index];
- return mod.fail(scope, src + bad_index, "invalid character: '{c}'\n", .{bad_byte});
- },
- };
- const result = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.comptime_int),
- .val = try Value.Tag.int_u64.create(scope.arena(), value),
- });
- return rvalue(mod, scope, rl, result);
-}
-
-fn integerLiteral(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- int_lit: ast.Node.Index,
-) InnerError!*zir.Inst {
- const arena = scope.arena();
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const int_token = main_tokens[int_lit];
- const prefixed_bytes = tree.tokenSlice(int_token);
- const base: u8 = if (mem.startsWith(u8, prefixed_bytes, "0x"))
- 16
- else if (mem.startsWith(u8, prefixed_bytes, "0o"))
- 8
- else if (mem.startsWith(u8, prefixed_bytes, "0b"))
- 2
- else
- @as(u8, 10);
-
- const bytes = if (base == 10)
- prefixed_bytes
- else
- prefixed_bytes[2..];
-
- if (std.fmt.parseInt(u64, bytes, base)) |small_int| {
- const src = token_starts[int_token];
- const result = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.comptime_int),
- .val = try Value.Tag.int_u64.create(arena, small_int),
- });
- return rvalue(mod, scope, rl, result);
- } else |err| {
- return mod.failTok(scope, int_token, "TODO implement int literals that don't fit in a u64", .{});
- }
-}
-
-fn floatLiteral(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- float_lit: ast.Node.Index,
-) InnerError!*zir.Inst {
- const arena = scope.arena();
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const main_token = main_tokens[float_lit];
- const bytes = tree.tokenSlice(main_token);
- if (bytes.len > 2 and bytes[1] == 'x') {
- return mod.failTok(scope, main_token, "TODO implement hex floats", .{});
- }
- const float_number = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) {
- error.InvalidCharacter => unreachable, // validated by tokenizer
- };
- const src = token_starts[main_token];
- const result = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.comptime_float),
- .val = try Value.Tag.float_128.create(arena, float_number),
- });
- return rvalue(mod, scope, rl, result);
-}
-
-fn asmExpr(mod: *Module, scope: *Scope, rl: ResultLoc, full: ast.full.Asm) InnerError!*zir.Inst {
- const arena = scope.arena();
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
- const node_datas = tree.nodes.items(.data);
-
- if (full.outputs.len != 0) {
- return mod.failTok(scope, full.ast.asm_token, "TODO implement asm with an output", .{});
- }
-
- const inputs = try arena.alloc([]const u8, full.inputs.len);
- const args = try arena.alloc(*zir.Inst, full.inputs.len);
-
- const src = token_starts[full.ast.asm_token];
- const str_type = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.const_slice_u8_type),
- });
- const str_type_rl: ResultLoc = .{ .ty = str_type };
-
- for (full.inputs) |input, i| {
- // TODO semantically analyze constraints
- const constraint_token = main_tokens[input] + 2;
- inputs[i] = try parseStringLiteral(mod, scope, constraint_token);
- args[i] = try expr(mod, scope, .none, node_datas[input].lhs);
- }
-
- const return_type = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.void_type),
- });
- const asm_inst = try addZIRInst(mod, scope, src, zir.Inst.Asm, .{
- .asm_source = try expr(mod, scope, str_type_rl, full.ast.template),
- .return_type = return_type,
- }, .{
- .@"volatile" = full.volatile_token != null,
- //.clobbers = TODO handle clobbers
- .inputs = inputs,
- .args = args,
- });
- return rvalue(mod, scope, rl, asm_inst);
-}
-
-fn as(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- builtin_token: ast.TokenIndex,
- src: usize,
- lhs: ast.Node.Index,
- rhs: ast.Node.Index,
-) InnerError!*zir.Inst {
- const dest_type = try typeExpr(mod, scope, lhs);
- switch (rl) {
- .none, .discard, .ref, .ty => {
- const result = try expr(mod, scope, .{ .ty = dest_type }, rhs);
- return rvalue(mod, scope, rl, result);
- },
-
- .ptr => |result_ptr| {
- return asRlPtr(mod, scope, rl, src, result_ptr, rhs, dest_type);
- },
- .block_ptr => |block_scope| {
- return asRlPtr(mod, scope, rl, src, block_scope.rl_ptr.?, rhs, dest_type);
- },
-
- .bitcasted_ptr => |bitcasted_ptr| {
- // TODO here we should be able to resolve the inference; we now have a type for the result.
- return mod.failTok(scope, builtin_token, "TODO implement @as with result location @bitCast", .{});
- },
- .inferred_ptr => |result_alloc| {
- // TODO here we should be able to resolve the inference; we now have a type for the result.
- return mod.failTok(scope, builtin_token, "TODO implement @as with inferred-type result location pointer", .{});
- },
- }
-}
-
-fn asRlPtr(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- src: usize,
- result_ptr: *zir.Inst,
- operand_node: ast.Node.Index,
- dest_type: *zir.Inst,
-) InnerError!*zir.Inst {
- // Detect whether this expr() call goes into rvalue() to store the result into the
- // result location. If it does, elide the coerce_result_ptr instruction
- // as well as the store instruction, instead passing the result as an rvalue.
- var as_scope: Scope.GenZIR = .{
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .force_comptime = scope.isComptime(),
- .instructions = .{},
- };
- defer as_scope.instructions.deinit(mod.gpa);
-
- as_scope.rl_ptr = try addZIRBinOp(mod, &as_scope.base, src, .coerce_result_ptr, dest_type, result_ptr);
- const result = try expr(mod, &as_scope.base, .{ .block_ptr = &as_scope }, operand_node);
- const parent_zir = &scope.getGenZIR().instructions;
- if (as_scope.rvalue_rl_count == 1) {
- // Busted! This expression didn't actually need a pointer.
- const expected_len = parent_zir.items.len + as_scope.instructions.items.len - 2;
- try parent_zir.ensureCapacity(mod.gpa, expected_len);
- for (as_scope.instructions.items) |src_inst| {
- if (src_inst == as_scope.rl_ptr.?) continue;
- if (src_inst.castTag(.store_to_block_ptr)) |store| {
- if (store.positionals.lhs == as_scope.rl_ptr.?) continue;
- }
- parent_zir.appendAssumeCapacity(src_inst);
- }
- assert(parent_zir.items.len == expected_len);
- const casted_result = try addZIRBinOp(mod, scope, dest_type.src, .as, dest_type, result);
- return rvalue(mod, scope, rl, casted_result);
- } else {
- try parent_zir.appendSlice(mod.gpa, as_scope.instructions.items);
- return result;
- }
-}
-
-fn bitCast(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- builtin_token: ast.TokenIndex,
- src: usize,
- lhs: ast.Node.Index,
- rhs: ast.Node.Index,
-) InnerError!*zir.Inst {
- const dest_type = try typeExpr(mod, scope, lhs);
- switch (rl) {
- .none => {
- const operand = try expr(mod, scope, .none, rhs);
- return addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand);
- },
- .discard => {
- const operand = try expr(mod, scope, .none, rhs);
- const result = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand);
- _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result);
- return result;
- },
- .ref => {
- const operand = try expr(mod, scope, .ref, rhs);
- const result = try addZIRBinOp(mod, scope, src, .bitcast_ref, dest_type, operand);
- return result;
- },
- .ty => |result_ty| {
- const result = try expr(mod, scope, .none, rhs);
- const bitcasted = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, result);
- return addZIRBinOp(mod, scope, src, .as, result_ty, bitcasted);
- },
- .ptr => |result_ptr| {
- const casted_result_ptr = try addZIRUnOp(mod, scope, src, .bitcast_result_ptr, result_ptr);
- return expr(mod, scope, .{ .bitcasted_ptr = casted_result_ptr.castTag(.bitcast_result_ptr).? }, rhs);
- },
- .bitcasted_ptr => |bitcasted_ptr| {
- return mod.failTok(scope, builtin_token, "TODO implement @bitCast with result location another @bitCast", .{});
- },
- .block_ptr => |block_ptr| {
- return mod.failTok(scope, builtin_token, "TODO implement @bitCast with result location inferred peer types", .{});
- },
- .inferred_ptr => |result_alloc| {
- // TODO here we should be able to resolve the inference; we now have a type for the result.
- return mod.failTok(scope, builtin_token, "TODO implement @bitCast with inferred-type result location pointer", .{});
- },
- }
-}
-
-fn typeOf(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- builtin_token: ast.TokenIndex,
- src: usize,
- params: []const ast.Node.Index,
-) InnerError!*zir.Inst {
- if (params.len < 1) {
- return mod.failTok(scope, builtin_token, "expected at least 1 argument, found 0", .{});
- }
- if (params.len == 1) {
- return rvalue(mod, scope, rl, try addZIRUnOp(mod, scope, src, .typeof, try expr(mod, scope, .none, params[0])));
- }
- const arena = scope.arena();
- var items = try arena.alloc(*zir.Inst, params.len);
- for (params) |param, param_i|
- items[param_i] = try expr(mod, scope, .none, param);
- return rvalue(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.TypeOfPeer, .{ .items = items }, .{}));
-}
-
-fn builtinCall(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- call: ast.Node.Index,
- params: []const ast.Node.Index,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const builtin_token = main_tokens[call];
- const builtin_name = tree.tokenSlice(builtin_token);
-
- // We handle the different builtins manually because they have different semantics depending
- // on the function. For example, `@as` and others participate in result location semantics,
- // and `@cImport` creates a special scope that collects a .c source code text buffer.
- // Also, some builtins have a variable number of parameters.
-
- const info = BuiltinFn.list.get(builtin_name) orelse {
- return mod.failTok(scope, builtin_token, "invalid builtin function: '{s}'", .{
- builtin_name,
- });
- };
- if (info.param_count) |expected| {
- if (expected != params.len) {
- const s = if (expected == 1) "" else "s";
- return mod.failTok(scope, builtin_token, "expected {d} parameter{s}, found {d}", .{
- expected, s, params.len,
- });
- }
- }
- const src = token_starts[builtin_token];
-
- switch (info.tag) {
- .ptr_to_int => {
- const operand = try expr(mod, scope, .none, params[0]);
- const result = try addZIRUnOp(mod, scope, src, .ptrtoint, operand);
- return rvalue(mod, scope, rl, result);
- },
- .float_cast => {
- const dest_type = try typeExpr(mod, scope, params[0]);
- const rhs = try expr(mod, scope, .none, params[1]);
- const result = try addZIRBinOp(mod, scope, src, .floatcast, dest_type, rhs);
- return rvalue(mod, scope, rl, result);
- },
- .int_cast => {
- const dest_type = try typeExpr(mod, scope, params[0]);
- const rhs = try expr(mod, scope, .none, params[1]);
- const result = try addZIRBinOp(mod, scope, src, .intcast, dest_type, rhs);
- return rvalue(mod, scope, rl, result);
- },
- .breakpoint => {
- const result = try addZIRNoOp(mod, scope, src, .breakpoint);
- return rvalue(mod, scope, rl, result);
- },
- .import => {
- const target = try expr(mod, scope, .none, params[0]);
- const result = try addZIRUnOp(mod, scope, src, .import, target);
- return rvalue(mod, scope, rl, result);
- },
- .compile_error => {
- const target = try expr(mod, scope, .none, params[0]);
- const result = try addZIRUnOp(mod, scope, src, .compile_error, target);
- return rvalue(mod, scope, rl, result);
- },
- .set_eval_branch_quota => {
- const u32_type = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.u32_type),
- });
- const quota = try expr(mod, scope, .{ .ty = u32_type }, params[0]);
- const result = try addZIRUnOp(mod, scope, src, .set_eval_branch_quota, quota);
- return rvalue(mod, scope, rl, result);
- },
- .compile_log => {
- const arena = scope.arena();
- var targets = try arena.alloc(*zir.Inst, params.len);
- for (params) |param, param_i|
- targets[param_i] = try expr(mod, scope, .none, param);
- const result = try addZIRInst(mod, scope, src, zir.Inst.CompileLog, .{ .to_log = targets }, .{});
- return rvalue(mod, scope, rl, result);
- },
- .field => {
- const string_type = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.const_slice_u8_type),
- });
- const string_rl: ResultLoc = .{ .ty = string_type };
-
- if (rl == .ref) {
- return addZirInstTag(mod, scope, src, .field_ptr_named, .{
- .object = try expr(mod, scope, .ref, params[0]),
- .field_name = try comptimeExpr(mod, scope, string_rl, params[1]),
- });
- }
- return rvalue(mod, scope, rl, try addZirInstTag(mod, scope, src, .field_val_named, .{
- .object = try expr(mod, scope, .none, params[0]),
- .field_name = try comptimeExpr(mod, scope, string_rl, params[1]),
- }));
- },
- .as => return as(mod, scope, rl, builtin_token, src, params[0], params[1]),
- .bit_cast => return bitCast(mod, scope, rl, builtin_token, src, params[0], params[1]),
- .TypeOf => return typeOf(mod, scope, rl, builtin_token, src, params),
-
- .add_with_overflow,
- .align_cast,
- .align_of,
- .async_call,
- .atomic_load,
- .atomic_rmw,
- .atomic_store,
- .bit_offset_of,
- .bool_to_int,
- .bit_size_of,
- .mul_add,
- .byte_swap,
- .bit_reverse,
- .byte_offset_of,
- .call,
- .c_define,
- .c_import,
- .c_include,
- .clz,
- .cmpxchg_strong,
- .cmpxchg_weak,
- .ctz,
- .c_undef,
- .div_exact,
- .div_floor,
- .div_trunc,
- .embed_file,
- .enum_to_int,
- .error_name,
- .error_return_trace,
- .error_to_int,
- .err_set_cast,
- .@"export",
- .fence,
- .field_parent_ptr,
- .float_to_int,
- .frame,
- .Frame,
- .frame_address,
- .frame_size,
- .has_decl,
- .has_field,
- .int_to_enum,
- .int_to_error,
- .int_to_float,
- .int_to_ptr,
- .memcpy,
- .memset,
- .wasm_memory_size,
- .wasm_memory_grow,
- .mod,
- .mul_with_overflow,
- .panic,
- .pop_count,
- .ptr_cast,
- .rem,
- .return_address,
- .set_align_stack,
- .set_cold,
- .set_float_mode,
- .set_runtime_safety,
- .shl_exact,
- .shl_with_overflow,
- .shr_exact,
- .shuffle,
- .size_of,
- .splat,
- .reduce,
- .src,
- .sqrt,
- .sin,
- .cos,
- .exp,
- .exp2,
- .log,
- .log2,
- .log10,
- .fabs,
- .floor,
- .ceil,
- .trunc,
- .round,
- .sub_with_overflow,
- .tag_name,
- .This,
- .truncate,
- .Type,
- .type_info,
- .type_name,
- .union_init,
- => return mod.failTok(scope, builtin_token, "TODO: implement builtin function {s}", .{
- builtin_name,
- }),
- }
-}
-
-fn callExpr(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- call: ast.full.Call,
-) InnerError!*zir.Inst {
- if (call.async_token) |async_token| {
- return mod.failTok(scope, async_token, "TODO implement async fn call", .{});
- }
-
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const token_starts = tree.tokens.items(.start);
-
- const lhs = try expr(mod, scope, .none, call.ast.fn_expr);
-
- const args = try scope.getGenZIR().arena.alloc(*zir.Inst, call.ast.params.len);
- for (call.ast.params) |param_node, i| {
- const param_src = token_starts[tree.firstToken(param_node)];
- const param_type = try addZIRInst(mod, scope, param_src, zir.Inst.ParamType, .{
- .func = lhs,
- .arg_index = i,
- }, .{});
- args[i] = try expr(mod, scope, .{ .ty = param_type }, param_node);
- }
-
- const src = token_starts[call.ast.lparen];
- var modifier: std.builtin.CallOptions.Modifier = .auto;
- if (call.async_token) |_| modifier = .async_kw;
-
- const result = try addZIRInst(mod, scope, src, zir.Inst.Call, .{
- .func = lhs,
- .args = args,
- .modifier = modifier,
- }, .{});
- // TODO function call with result location
- return rvalue(mod, scope, rl, result);
-}
-
-fn suspendExpr(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- const src = tree.tokens.items(.start)[tree.nodes.items(.main_token)[node]];
-
- if (scope.getNosuspend()) |some| {
- const msg = msg: {
- const msg = try mod.errMsg(scope, src, "suspend in nosuspend block", .{});
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, some.src, msg, "nosuspend block here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
-
- if (scope.getSuspend()) |some| {
- const msg = msg: {
- const msg = try mod.errMsg(scope, src, "cannot suspend inside suspend block", .{});
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, some.src, msg, "other suspend block here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
-
- var suspend_scope: Scope.GenZIR = .{
- .base = .{ .tag = .gen_suspend },
- .parent = scope,
- .decl = scope.ownerDecl().?,
- .arena = scope.arena(),
- .force_comptime = scope.isComptime(),
- .instructions = .{},
- };
- defer suspend_scope.instructions.deinit(mod.gpa);
-
- const operand = tree.nodes.items(.data)[node].lhs;
- if (operand != 0) {
- const possibly_unused_result = try expr(mod, &suspend_scope.base, .none, operand);
- if (!possibly_unused_result.tag.isNoReturn()) {
- _ = try addZIRUnOp(mod, &suspend_scope.base, src, .ensure_result_used, possibly_unused_result);
- }
- } else {
- return addZIRNoOp(mod, scope, src, .@"suspend");
- }
-
- const block = try addZIRInstBlock(mod, scope, src, .suspend_block, .{
- .instructions = try scope.arena().dupe(*zir.Inst, suspend_scope.instructions.items),
- });
- return &block.base;
-}
-
-fn nosuspendExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- var child_scope = Scope.Nosuspend{
- .parent = scope,
- .gen_zir = scope.getGenZIR(),
- .src = tree.tokens.items(.start)[tree.nodes.items(.main_token)[node]],
- };
-
- return expr(mod, &child_scope.base, rl, tree.nodes.items(.data)[node].lhs);
-}
-
-fn awaitExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- const src = tree.tokens.items(.start)[tree.nodes.items(.main_token)[node]];
- const is_nosuspend = scope.getNosuspend() != null;
-
- // TODO some @asyncCall stuff
-
- if (scope.getSuspend()) |some| {
- const msg = msg: {
- const msg = try mod.errMsg(scope, src, "cannot await inside suspend block", .{});
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(scope, some.src, msg, "suspend block here", .{});
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
-
- const operand = try expr(mod, scope, .ref, tree.nodes.items(.data)[node].lhs);
- // TODO pass result location
- return addZIRUnOp(mod, scope, src, if (is_nosuspend) .nosuspend_await else .@"await", operand);
-}
-
-fn resumeExpr(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir.Inst {
- const tree = scope.tree();
- const src = tree.tokens.items(.start)[tree.nodes.items(.main_token)[node]];
-
- const operand = try expr(mod, scope, .ref, tree.nodes.items(.data)[node].lhs);
- return addZIRUnOp(mod, scope, src, .@"resume", operand);
-}
-
-pub const simple_types = std.ComptimeStringMap(Value.Tag, .{
- .{ "u8", .u8_type },
- .{ "i8", .i8_type },
- .{ "isize", .isize_type },
- .{ "usize", .usize_type },
- .{ "c_short", .c_short_type },
- .{ "c_ushort", .c_ushort_type },
- .{ "c_int", .c_int_type },
- .{ "c_uint", .c_uint_type },
- .{ "c_long", .c_long_type },
- .{ "c_ulong", .c_ulong_type },
- .{ "c_longlong", .c_longlong_type },
- .{ "c_ulonglong", .c_ulonglong_type },
- .{ "c_longdouble", .c_longdouble_type },
- .{ "f16", .f16_type },
- .{ "f32", .f32_type },
- .{ "f64", .f64_type },
- .{ "f128", .f128_type },
- .{ "c_void", .c_void_type },
- .{ "bool", .bool_type },
- .{ "void", .void_type },
- .{ "type", .type_type },
- .{ "anyerror", .anyerror_type },
- .{ "comptime_int", .comptime_int_type },
- .{ "comptime_float", .comptime_float_type },
- .{ "noreturn", .noreturn_type },
-});
-
-fn nodeMayNeedMemoryLocation(scope: *Scope, start_node: ast.Node.Index) bool {
- const tree = scope.tree();
- const node_tags = tree.nodes.items(.tag);
- const node_datas = tree.nodes.items(.data);
- const main_tokens = tree.nodes.items(.main_token);
- const token_tags = tree.tokens.items(.tag);
-
- var node = start_node;
- while (true) {
- switch (node_tags[node]) {
- .root,
- .@"usingnamespace",
- .test_decl,
- .switch_case,
- .switch_case_one,
- .container_field_init,
- .container_field_align,
- .container_field,
- .asm_output,
- .asm_input,
- => unreachable,
-
- .@"return",
- .@"break",
- .@"continue",
- .bit_not,
- .bool_not,
- .global_var_decl,
- .local_var_decl,
- .simple_var_decl,
- .aligned_var_decl,
- .@"defer",
- .@"errdefer",
- .address_of,
- .optional_type,
- .negation,
- .negation_wrap,
- .@"resume",
- .array_type,
- .array_type_sentinel,
- .ptr_type_aligned,
- .ptr_type_sentinel,
- .ptr_type,
- .ptr_type_bit_range,
- .@"suspend",
- .@"anytype",
- .fn_proto_simple,
- .fn_proto_multi,
- .fn_proto_one,
- .fn_proto,
- .fn_decl,
- .anyframe_type,
- .anyframe_literal,
- .integer_literal,
- .float_literal,
- .enum_literal,
- .string_literal,
- .multiline_string_literal,
- .char_literal,
- .true_literal,
- .false_literal,
- .null_literal,
- .undefined_literal,
- .unreachable_literal,
- .identifier,
- .error_set_decl,
- .container_decl,
- .container_decl_trailing,
- .container_decl_two,
- .container_decl_two_trailing,
- .container_decl_arg,
- .container_decl_arg_trailing,
- .tagged_union,
- .tagged_union_trailing,
- .tagged_union_two,
- .tagged_union_two_trailing,
- .tagged_union_enum_tag,
- .tagged_union_enum_tag_trailing,
- .@"asm",
- .asm_simple,
- .add,
- .add_wrap,
- .array_cat,
- .array_mult,
- .assign,
- .assign_bit_and,
- .assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_right,
- .assign_bit_xor,
- .assign_div,
- .assign_sub,
- .assign_sub_wrap,
- .assign_mod,
- .assign_add,
- .assign_add_wrap,
- .assign_mul,
- .assign_mul_wrap,
- .bang_equal,
- .bit_and,
- .bit_or,
- .bit_shift_left,
- .bit_shift_right,
- .bit_xor,
- .bool_and,
- .bool_or,
- .div,
- .equal_equal,
- .error_union,
- .greater_or_equal,
- .greater_than,
- .less_or_equal,
- .less_than,
- .merge_error_sets,
- .mod,
- .mul,
- .mul_wrap,
- .switch_range,
- .field_access,
- .sub,
- .sub_wrap,
- .slice,
- .slice_open,
- .slice_sentinel,
- .deref,
- .array_access,
- .error_value,
- .while_simple, // This variant cannot have an else expression.
- .while_cont, // This variant cannot have an else expression.
- .for_simple, // This variant cannot have an else expression.
- .if_simple, // This variant cannot have an else expression.
- => return false,
-
- // Forward the question to the LHS sub-expression.
- .grouped_expression,
- .@"try",
- .@"await",
- .@"comptime",
- .@"nosuspend",
- .unwrap_optional,
- => node = node_datas[node].lhs,
-
- // Forward the question to the RHS sub-expression.
- .@"catch",
- .@"orelse",
- => node = node_datas[node].rhs,
-
- // True because these are exactly the expressions we need memory locations for.
- .array_init_one,
- .array_init_one_comma,
- .array_init_dot_two,
- .array_init_dot_two_comma,
- .array_init_dot,
- .array_init_dot_comma,
- .array_init,
- .array_init_comma,
- .struct_init_one,
- .struct_init_one_comma,
- .struct_init_dot_two,
- .struct_init_dot_two_comma,
- .struct_init_dot,
- .struct_init_dot_comma,
- .struct_init,
- .struct_init_comma,
- => return true,
-
- // True because depending on comptime conditions, sub-expressions
- // may be the kind that need memory locations.
- .@"while", // This variant always has an else expression.
- .@"if", // This variant always has an else expression.
- .@"for", // This variant always has an else expression.
- .@"switch",
- .switch_comma,
- .call_one,
- .call_one_comma,
- .async_call_one,
- .async_call_one_comma,
- .call,
- .call_comma,
- .async_call,
- .async_call_comma,
- => return true,
-
- .block_two,
- .block_two_semicolon,
- .block,
- .block_semicolon,
- => {
- const lbrace = main_tokens[node];
- if (token_tags[lbrace - 1] == .colon) {
- // Labeled blocks may need a memory location to forward
- // to their break statements.
- return true;
- } else {
- return false;
- }
- },
-
- .builtin_call,
- .builtin_call_comma,
- .builtin_call_two,
- .builtin_call_two_comma,
- => {
- const builtin_token = main_tokens[node];
- const builtin_name = tree.tokenSlice(builtin_token);
- // If the builtin is an invalid name, we don't cause an error here; instead
- // let it pass, and the error will be "invalid builtin function" later.
- const builtin_info = BuiltinFn.list.get(builtin_name) orelse return false;
- return builtin_info.needs_mem_loc;
- },
- }
- }
-}
-
-/// Applies `rl` semantics to `inst`. Expressions which do not do their own handling of
-/// result locations must call this function on their result.
-/// As an example, if the `ResultLoc` is `ptr`, it will write the result to the pointer.
-/// If the `ResultLoc` is `ty`, it will coerce the result to the type.
-fn rvalue(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerError!*zir.Inst {
- switch (rl) {
- .none => return result,
- .discard => {
- // Emit a compile error for discarding error values.
- _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result);
- return result;
- },
- .ref => {
- // We need a pointer but we have a value.
- return addZIRUnOp(mod, scope, result.src, .ref, result);
- },
- .ty => |ty_inst| return addZIRBinOp(mod, scope, result.src, .as, ty_inst, result),
- .ptr => |ptr_inst| {
- _ = try addZIRBinOp(mod, scope, result.src, .store, ptr_inst, result);
- return result;
- },
- .bitcasted_ptr => |bitcasted_ptr| {
- return mod.fail(scope, result.src, "TODO implement rvalue .bitcasted_ptr", .{});
- },
- .inferred_ptr => |alloc| {
- _ = try addZIRBinOp(mod, scope, result.src, .store_to_inferred_ptr, &alloc.base, result);
- return result;
- },
- .block_ptr => |block_scope| {
- block_scope.rvalue_rl_count += 1;
- _ = try addZIRBinOp(mod, scope, result.src, .store_to_block_ptr, block_scope.rl_ptr.?, result);
- return result;
- },
- }
-}
-
-/// TODO when reworking ZIR memory layout, make the void value correspond to a hard coded
-/// index; that way this does not actually need to allocate anything.
-fn rvalueVoid(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- node: ast.Node.Index,
- result: void,
-) InnerError!*zir.Inst {
- const tree = scope.tree();
- const main_tokens = tree.nodes.items(.main_token);
- const src = tree.tokens.items(.start)[tree.firstToken(node)];
- const void_inst = try addZIRInstConst(mod, scope, src, .{
- .ty = Type.initTag(.void),
- .val = Value.initTag(.void_value),
- });
- return rvalue(mod, scope, rl, void_inst);
-}
-
-fn rlStrategy(rl: ResultLoc, block_scope: *Scope.GenZIR) ResultLoc.Strategy {
- var elide_store_to_block_ptr_instructions = false;
- switch (rl) {
- // In this branch there will not be any store_to_block_ptr instructions.
- .discard, .none, .ty, .ref => return .{
- .tag = .break_operand,
- .elide_store_to_block_ptr_instructions = false,
- },
- // The pointer got passed through to the sub-expressions, so we will use
- // break_void here.
- // In this branch there will not be any store_to_block_ptr instructions.
- .ptr => return .{
- .tag = .break_void,
- .elide_store_to_block_ptr_instructions = false,
- },
- .inferred_ptr, .bitcasted_ptr, .block_ptr => {
- if (block_scope.rvalue_rl_count == block_scope.break_count) {
- // Neither prong of the if consumed the result location, so we can
- // use break instructions to create an rvalue.
- return .{
- .tag = .break_operand,
- .elide_store_to_block_ptr_instructions = true,
- };
- } else {
- // Allow the store_to_block_ptr instructions to remain so that
- // semantic analysis can turn them into bitcasts.
- return .{
- .tag = .break_void,
- .elide_store_to_block_ptr_instructions = false,
- };
- }
- },
- }
-}
-
-/// If the input ResultLoc is ref, returns ResultLoc.ref. Otherwise:
-/// Returns ResultLoc.ty, where the type is determined by the input
-/// ResultLoc type, wrapped in an optional type. If the input ResultLoc
-/// has no type, .none is returned.
-fn makeOptionalTypeResultLoc(mod: *Module, scope: *Scope, src: usize, rl: ResultLoc) !ResultLoc {
- switch (rl) {
- .ref => return ResultLoc.ref,
- .discard, .none, .block_ptr, .inferred_ptr, .bitcasted_ptr => return ResultLoc.none,
- .ty => |elem_ty| {
- const wrapped_ty = try addZIRUnOp(mod, scope, src, .optional_type, elem_ty);
- return ResultLoc{ .ty = wrapped_ty };
- },
- .ptr => |ptr_ty| {
- const wrapped_ty = try addZIRUnOp(mod, scope, src, .optional_type_from_ptr_elem, ptr_ty);
- return ResultLoc{ .ty = wrapped_ty };
- },
- }
-}
-
-fn setBlockResultLoc(block_scope: *Scope.GenZIR, parent_rl: ResultLoc) void {
- // Depending on whether the result location is a pointer or value, different
- // ZIR needs to be generated. In the former case we rely on storing to the
- // pointer to communicate the result, and use breakvoid; in the latter case
- // the block break instructions will have the result values.
- // One more complication: when the result location is a pointer, we detect
- // the scenario where the result location is not consumed. In this case
- // we emit ZIR for the block break instructions to have the result values,
- // and then rvalue() on that to pass the value to the result location.
- switch (parent_rl) {
- .discard, .none, .ty, .ptr, .ref => {
- block_scope.break_result_loc = parent_rl;
- },
-
- .inferred_ptr => |ptr| {
- block_scope.rl_ptr = &ptr.base;
- block_scope.break_result_loc = .{ .block_ptr = block_scope };
- },
-
- .bitcasted_ptr => |ptr| {
- block_scope.rl_ptr = &ptr.base;
- block_scope.break_result_loc = .{ .block_ptr = block_scope };
- },
-
- .block_ptr => |parent_block_scope| {
- block_scope.rl_ptr = parent_block_scope.rl_ptr.?;
- block_scope.break_result_loc = .{ .block_ptr = block_scope };
- },
- }
-}
-
-pub fn addZirInstTag(
- mod: *Module,
- scope: *Scope,
- src: usize,
- comptime tag: zir.Inst.Tag,
- positionals: std.meta.fieldInfo(tag.Type(), .positionals).field_type,
-) !*zir.Inst {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(tag.Type());
- inst.* = .{
- .base = .{
- .tag = tag,
- .src = src,
- },
- .positionals = positionals,
- .kw_args = .{},
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return &inst.base;
-}
-
-pub fn addZirInstT(
- mod: *Module,
- scope: *Scope,
- src: usize,
- comptime T: type,
- tag: zir.Inst.Tag,
- positionals: std.meta.fieldInfo(T, .positionals).field_type,
-) !*T {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(T);
- inst.* = .{
- .base = .{
- .tag = tag,
- .src = src,
- },
- .positionals = positionals,
- .kw_args = .{},
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return inst;
-}
-
-pub fn addZIRInstSpecial(
- mod: *Module,
- scope: *Scope,
- src: usize,
- comptime T: type,
- positionals: std.meta.fieldInfo(T, .positionals).field_type,
- kw_args: std.meta.fieldInfo(T, .kw_args).field_type,
-) !*T {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(T);
- inst.* = .{
- .base = .{
- .tag = T.base_tag,
- .src = src,
- },
- .positionals = positionals,
- .kw_args = kw_args,
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return inst;
-}
-
-pub fn addZIRNoOpT(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst.NoOp {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(zir.Inst.NoOp);
- inst.* = .{
- .base = .{
- .tag = tag,
- .src = src,
- },
- .positionals = .{},
- .kw_args = .{},
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return inst;
-}
-
-pub fn addZIRNoOp(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst {
- const inst = try addZIRNoOpT(mod, scope, src, tag);
- return &inst.base;
-}
-
-pub fn addZIRUnOp(
- mod: *Module,
- scope: *Scope,
- src: usize,
- tag: zir.Inst.Tag,
- operand: *zir.Inst,
-) !*zir.Inst {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(zir.Inst.UnOp);
- inst.* = .{
- .base = .{
- .tag = tag,
- .src = src,
- },
- .positionals = .{
- .operand = operand,
- },
- .kw_args = .{},
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return &inst.base;
-}
-
-pub fn addZIRBinOp(
- mod: *Module,
- scope: *Scope,
- src: usize,
- tag: zir.Inst.Tag,
- lhs: *zir.Inst,
- rhs: *zir.Inst,
-) !*zir.Inst {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(zir.Inst.BinOp);
- inst.* = .{
- .base = .{
- .tag = tag,
- .src = src,
- },
- .positionals = .{
- .lhs = lhs,
- .rhs = rhs,
- },
- .kw_args = .{},
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return &inst.base;
-}
-
-pub fn addZIRInstBlock(
- mod: *Module,
- scope: *Scope,
- src: usize,
- tag: zir.Inst.Tag,
- body: zir.Body,
-) !*zir.Inst.Block {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(zir.Inst.Block);
- inst.* = .{
- .base = .{
- .tag = tag,
- .src = src,
- },
- .positionals = .{
- .body = body,
- },
- .kw_args = .{},
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return inst;
-}
-
-pub fn addZIRInst(
- mod: *Module,
- scope: *Scope,
- src: usize,
- comptime T: type,
- positionals: std.meta.fieldInfo(T, .positionals).field_type,
- kw_args: std.meta.fieldInfo(T, .kw_args).field_type,
-) !*zir.Inst {
- const inst_special = try addZIRInstSpecial(mod, scope, src, T, positionals, kw_args);
- return &inst_special.base;
-}
-
-/// TODO The existence of this function is a workaround for a bug in stage1.
-pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst {
- const P = std.meta.fieldInfo(zir.Inst.Const, .positionals).field_type;
- return addZIRInst(mod, scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{});
-}
-
-/// TODO The existence of this function is a workaround for a bug in stage1.
-pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Body) !*zir.Inst.Loop {
- const P = std.meta.fieldInfo(zir.Inst.Loop, .positionals).field_type;
- return addZIRInstSpecial(mod, scope, src, zir.Inst.Loop, P{ .body = body }, .{});
-}
diff --git a/src/codegen.zig b/src/codegen.zig
@@ -17,6 +17,8 @@ const DW = std.dwarf;
const leb128 = std.leb;
const log = std.log.scoped(.codegen);
const build_options = @import("build_options");
+const LazySrcLoc = Module.LazySrcLoc;
+const RegisterManager = @import("register_manager.zig").RegisterManager;
/// The codegen-related data that is stored in `ir.Inst.Block` instructions.
pub const BlockData = struct {
@@ -285,11 +287,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
/// across each runtime branch upon joining.
branch_stack: *std.ArrayList(Branch),
- /// The key must be canonical register.
- registers: std.AutoHashMapUnmanaged(Register, *ir.Inst) = .{},
- free_registers: FreeRegInt = math.maxInt(FreeRegInt),
- /// Tracks all registers allocated in the course of this function
- allocated_registers: FreeRegInt = 0,
+ register_manager: RegisterManager(Self, Register, &callee_preserved_regs) = .{},
/// Maps offset to what is stored there.
stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{},
@@ -381,49 +379,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
};
- fn markRegUsed(self: *Self, reg: Register) void {
- if (FreeRegInt == u0) return;
- const index = reg.allocIndex() orelse return;
- const ShiftInt = math.Log2Int(FreeRegInt);
- const shift = @intCast(ShiftInt, index);
- const mask = @as(FreeRegInt, 1) << shift;
- self.free_registers &= ~mask;
- self.allocated_registers |= mask;
- }
-
- fn markRegFree(self: *Self, reg: Register) void {
- if (FreeRegInt == u0) return;
- const index = reg.allocIndex() orelse return;
- const ShiftInt = math.Log2Int(FreeRegInt);
- const shift = @intCast(ShiftInt, index);
- self.free_registers |= @as(FreeRegInt, 1) << shift;
- }
-
- /// Before calling, must ensureCapacity + 1 on self.registers.
- /// Returns `null` if all registers are allocated.
- fn allocReg(self: *Self, inst: *ir.Inst) ?Register {
- const free_index = @ctz(FreeRegInt, self.free_registers);
- if (free_index >= callee_preserved_regs.len) {
- return null;
- }
- const mask = @as(FreeRegInt, 1) << free_index;
- self.free_registers &= ~mask;
- self.allocated_registers |= mask;
- const reg = callee_preserved_regs[free_index];
- self.registers.putAssumeCapacityNoClobber(reg, inst);
- log.debug("alloc {} => {*}", .{ reg, inst });
- return reg;
- }
-
- /// Does not track the register.
- fn findUnusedReg(self: *Self) ?Register {
- const free_index = @ctz(FreeRegInt, self.free_registers);
- if (free_index >= callee_preserved_regs.len) {
- return null;
- }
- return callee_preserved_regs[free_index];
- }
-
const StackAllocation = struct {
inst: *ir.Inst,
/// TODO do we need size? should be determined by inst.ty.abiSize()
@@ -494,11 +449,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.rbrace_src = src_data.rbrace_src,
.source = src_data.source,
};
- defer function.registers.deinit(bin_file.allocator);
+ defer function.register_manager.deinit(bin_file.allocator);
defer function.stack.deinit(bin_file.allocator);
defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
- var call_info = function.resolveCallingConventionValues(src_loc.byte_offset, fn_type) catch |err| switch (err) {
+ var call_info = function.resolveCallingConventionValues(src_loc.lazy, fn_type) catch |err| switch (err) {
error.CodegenFail => return Result{ .fail = function.err_msg.? },
else => |e| return e,
};
@@ -606,10 +561,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.r14 = true, // lr
};
inline for (callee_preserved_regs) |reg, i| {
- const ShiftInt = math.Log2Int(FreeRegInt);
- const shift = @intCast(ShiftInt, i);
- const mask = @as(FreeRegInt, 1) << shift;
- if (self.allocated_registers & mask != 0) {
+ if (self.register_manager.isRegAllocated(reg)) {
@field(saved_regs, @tagName(reg)) = true;
}
}
@@ -791,8 +743,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn dbgAdvancePCAndLine(self: *Self, src: usize) InnerError!void {
- self.prev_di_src = src;
+ fn dbgAdvancePCAndLine(self: *Self, abs_byte_off: usize) InnerError!void {
+ self.prev_di_src = abs_byte_off;
self.prev_di_pc = self.code.items.len;
switch (self.debug_output) {
.dwarf => |dbg_out| {
@@ -800,7 +752,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// lookup table, and changing ir.Inst from storing byte offset to token. Currently
// this involves scanning over the source code for newlines
// (but only from the previous byte offset to the new one).
- const delta_line = std.zig.lineDelta(self.source, self.prev_di_src, src);
+ const delta_line = std.zig.lineDelta(self.source, self.prev_di_src, abs_byte_off);
const delta_pc = self.code.items.len - self.prev_di_pc;
// TODO Look into using the DWARF special opcodes to compress this data. It lets you emit
// single-byte opcodes that add different numbers to both the PC and the line number
@@ -828,8 +780,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (prev_value) {
.register => |reg| {
const canon_reg = toCanonicalReg(reg);
- _ = self.registers.remove(canon_reg);
- self.markRegFree(canon_reg);
+ self.register_manager.freeReg(canon_reg);
},
else => {}, // TODO process stack allocation death
}
@@ -897,6 +848,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.is_null_ptr => return self.genIsNullPtr(inst.castTag(.is_null_ptr).?),
.is_err => return self.genIsErr(inst.castTag(.is_err).?),
.is_err_ptr => return self.genIsErrPtr(inst.castTag(.is_err_ptr).?),
+ .error_to_int => return self.genErrorToInt(inst.castTag(.error_to_int).?),
+ .int_to_error => return self.genIntToError(inst.castTag(.int_to_error).?),
.load => return self.genLoad(inst.castTag(.load).?),
.loop => return self.genLoop(inst.castTag(.loop).?),
.not => return self.genNot(inst.castTag(.not).?),
@@ -907,6 +860,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.ret => return self.genRet(inst.castTag(.ret).?),
.retvoid => return self.genRetVoid(inst.castTag(.retvoid).?),
.store => return self.genStore(inst.castTag(.store).?),
+ .struct_field_ptr => return self.genStructFieldPtr(inst.castTag(.struct_field_ptr).?),
.sub => return self.genSub(inst.castTag(.sub).?),
.subwrap => return self.genSubWrap(inst.castTag(.subwrap).?),
.switchbr => return self.genSwitch(inst.castTag(.switchbr).?),
@@ -965,8 +919,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const ptr_bits = arch.ptrBitWidth();
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
if (abi_size <= ptr_bytes) {
- try self.registers.ensureCapacity(self.gpa, self.registers.count() + 1);
- if (self.allocReg(inst)) |reg| {
+ try self.register_manager.registers.ensureCapacity(self.gpa, self.register_manager.registers.count() + 1);
+ if (self.register_manager.tryAllocReg(inst)) |reg| {
return MCValue{ .register = registerAlias(reg, abi_size) };
}
}
@@ -975,26 +929,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return MCValue{ .stack_offset = stack_offset };
}
+ pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: Register, inst: *ir.Inst) !void {
+ const stack_mcv = try self.allocRegOrMem(inst, false);
+ const reg_mcv = self.getResolvedInstValue(inst);
+ assert(reg == toCanonicalReg(reg_mcv.register));
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ try branch.inst_table.put(self.gpa, inst, stack_mcv);
+ try self.genSetStack(src, inst.ty, stack_mcv.stack_offset, reg_mcv);
+ }
+
/// Copies a value to a register without tracking the register. The register is not considered
/// allocated. A second call to `copyToTmpRegister` may return the same register.
/// This can have a side effect of spilling instructions to the stack to free up a register.
- fn copyToTmpRegister(self: *Self, src: usize, ty: Type, mcv: MCValue) !Register {
- const reg = self.findUnusedReg() orelse b: {
- // We'll take over the first register. Move the instruction that was previously
- // there to a stack allocation.
- const reg = callee_preserved_regs[0];
- const regs_entry = self.registers.remove(reg).?;
- const spilled_inst = regs_entry.value;
-
- const stack_mcv = try self.allocRegOrMem(spilled_inst, false);
- const reg_mcv = self.getResolvedInstValue(spilled_inst);
- assert(reg == toCanonicalReg(reg_mcv.register));
- const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
- try branch.inst_table.put(self.gpa, spilled_inst, stack_mcv);
- try self.genSetStack(src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv);
-
- break :b reg;
- };
+ fn copyToTmpRegister(self: *Self, src: LazySrcLoc, ty: Type, mcv: MCValue) !Register {
+ const reg = try self.register_manager.allocRegWithoutTracking();
try self.genSetReg(src, ty, reg, mcv);
return reg;
}
@@ -1003,25 +951,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
/// `reg_owner` is the instruction that gets associated with the register in the register table.
/// This can have a side effect of spilling instructions to the stack to free up a register.
fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue {
- try self.registers.ensureCapacity(self.gpa, @intCast(u32, self.registers.count() + 1));
-
- const reg = self.allocReg(reg_owner) orelse b: {
- // We'll take over the first register. Move the instruction that was previously
- // there to a stack allocation.
- const reg = callee_preserved_regs[0];
- const regs_entry = self.registers.getEntry(reg).?;
- const spilled_inst = regs_entry.value;
- regs_entry.value = reg_owner;
-
- const stack_mcv = try self.allocRegOrMem(spilled_inst, false);
- const reg_mcv = self.getResolvedInstValue(spilled_inst);
- assert(reg == toCanonicalReg(reg_mcv.register));
- const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
- try branch.inst_table.put(self.gpa, spilled_inst, stack_mcv);
- try self.genSetStack(reg_owner.src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv);
-
- break :b reg;
- };
+ try self.register_manager.registers.ensureCapacity(self.gpa, @intCast(u32, self.register_manager.registers.count() + 1));
+
+ const reg = try self.register_manager.allocReg(reg_owner);
try self.genSetReg(reg_owner.src, reg_owner.ty, reg, mcv);
return MCValue{ .register = reg };
}
@@ -1298,7 +1230,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.register => |reg| {
// If it's in the registers table, need to associate the register with the
// new instruction.
- if (self.registers.getEntry(toCanonicalReg(reg))) |entry| {
+ if (self.register_manager.registers.getEntry(toCanonicalReg(reg))) |entry| {
entry.value = inst;
}
log.debug("reusing {} => {*}", .{ reg, inst });
@@ -1400,6 +1332,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return .none;
}
+ fn genStructFieldPtr(self: *Self, inst: *ir.Inst.StructFieldPtr) !MCValue {
+ return self.fail(inst.base.src, "TODO implement codegen struct_field_ptr", .{});
+ }
+
fn genSub(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
@@ -1457,7 +1393,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn genArmBinOpCode(
self: *Self,
- src: usize,
+ src: LazySrcLoc,
dst_reg: Register,
lhs_mcv: MCValue,
rhs_mcv: MCValue,
@@ -1620,7 +1556,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn genX8664BinMathCode(
self: *Self,
- src: usize,
+ src: LazySrcLoc,
dst_ty: Type,
dst_mcv: MCValue,
src_mcv: MCValue,
@@ -1706,7 +1642,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn genX8664ModRMRegToStack(self: *Self, src: usize, ty: Type, off: u32, reg: Register, opcode: u8) !void {
+ fn genX8664ModRMRegToStack(self: *Self, src: LazySrcLoc, ty: Type, off: u32, reg: Register, opcode: u8) !void {
const abi_size = ty.abiSize(self.target.*);
const adj_off = off + abi_size;
try self.code.ensureCapacity(self.code.items.len + 7);
@@ -1787,7 +1723,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const arg_index = self.arg_index;
self.arg_index += 1;
- if (FreeRegInt == u0) {
+ if (callee_preserved_regs.len == 0) {
return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch});
}
@@ -1799,15 +1735,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (result) {
.register => |reg| {
- try self.registers.putNoClobber(self.gpa, toCanonicalReg(reg), &inst.base);
- self.markRegUsed(reg);
+ try self.register_manager.getRegAssumeFree(toCanonicalReg(reg), &inst.base);
},
else => {},
}
return result;
}
- fn genBreakpoint(self: *Self, src: usize) !MCValue {
+ fn genBreakpoint(self: *Self, src: LazySrcLoc) !MCValue {
switch (arch) {
.i386, .x86_64 => {
try self.code.append(0xcc); // int3
@@ -2194,6 +2129,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
unreachable;
}
+ switch (info.return_value) {
+ .register => |reg| {
+ if (Register.allocIndex(reg) == null) {
+ // Save function return value in a callee saved register
+ return try self.copyToNewRegister(&inst.base, info.return_value);
+ }
+ },
+ else => {},
+ }
+
return info.return_value;
}
@@ -2224,7 +2169,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn ret(self: *Self, src: usize, mcv: MCValue) !MCValue {
+ fn ret(self: *Self, src: LazySrcLoc, mcv: MCValue) !MCValue {
const ret_ty = self.fn_type.fnReturnType();
try self.setRegOrMem(src, ret_ty, self.ret_mcv, mcv);
switch (arch) {
@@ -2314,8 +2259,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn genDbgStmt(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
- try self.dbgAdvancePCAndLine(inst.base.src);
+ fn genDbgStmt(self: *Self, inst: *ir.Inst.DbgStmt) !MCValue {
+ // TODO when reworking tzir memory layout, rework source locations here as
+ // well to be more efficient, as well as support inlined function calls correctly.
+ // For now we convert LazySrcLoc to absolute byte offset, to match what the
+ // existing codegen code expects.
+ try self.dbgAdvancePCAndLine(inst.byte_offset);
assert(inst.base.isUnused());
return MCValue.dead;
}
@@ -2409,10 +2358,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// Capture the state of register and stack allocation state so that we can revert to it.
const parent_next_stack_offset = self.next_stack_offset;
- const parent_free_registers = self.free_registers;
+ const parent_free_registers = self.register_manager.free_registers;
var parent_stack = try self.stack.clone(self.gpa);
defer parent_stack.deinit(self.gpa);
- var parent_registers = try self.registers.clone(self.gpa);
+ var parent_registers = try self.register_manager.registers.clone(self.gpa);
defer parent_registers.deinit(self.gpa);
try self.branch_stack.append(.{});
@@ -2429,8 +2378,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
var saved_then_branch = self.branch_stack.pop();
defer saved_then_branch.deinit(self.gpa);
- self.registers.deinit(self.gpa);
- self.registers = parent_registers;
+ self.register_manager.registers.deinit(self.gpa);
+ self.register_manager.registers = parent_registers;
parent_registers = .{};
self.stack.deinit(self.gpa);
@@ -2438,7 +2387,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
parent_stack = .{};
self.next_stack_offset = parent_next_stack_offset;
- self.free_registers = parent_free_registers;
+ self.register_manager.free_registers = parent_free_registers;
try self.performReloc(inst.base.src, reloc);
const else_branch = self.branch_stack.addOneAssumeCapacity();
@@ -2552,6 +2501,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail(inst.base.src, "TODO load the operand and call genIsErr", .{});
}
+ fn genErrorToInt(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+ return self.resolveInst(inst.operand);
+ }
+
+ fn genIntToError(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+ return self.resolveInst(inst.operand);
+ }
+
fn genLoop(self: *Self, inst: *ir.Inst.Loop) !MCValue {
// A loop is a setup to be able to jump back to the beginning.
const start_index = self.code.items.len;
@@ -2561,7 +2518,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
/// Send control flow to the `index` of `self.code`.
- fn jump(self: *Self, src: usize, index: usize) !void {
+ fn jump(self: *Self, src: LazySrcLoc, index: usize) !void {
switch (arch) {
.i386, .x86_64 => {
try self.code.ensureCapacity(self.code.items.len + 5);
@@ -2618,7 +2575,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn performReloc(self: *Self, src: usize, reloc: Reloc) !void {
+ fn performReloc(self: *Self, src: LazySrcLoc, reloc: Reloc) !void {
switch (reloc) {
.rel32 => |pos| {
const amt = self.code.items.len - (pos + 4);
@@ -2682,7 +2639,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn br(self: *Self, src: usize, block: *ir.Inst.Block, operand: *ir.Inst) !MCValue {
+ fn br(self: *Self, src: LazySrcLoc, block: *ir.Inst.Block, operand: *ir.Inst) !MCValue {
if (operand.ty.hasCodeGenBits()) {
const operand_mcv = try self.resolveInst(operand);
const block_mcv = @bitCast(MCValue, block.codegen.mcv);
@@ -2695,7 +2652,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.brVoid(src, block);
}
- fn brVoid(self: *Self, src: usize, block: *ir.Inst.Block) !MCValue {
+ fn brVoid(self: *Self, src: LazySrcLoc, block: *ir.Inst.Block) !MCValue {
// Emit a jump with a relocation. It will be patched up after the block ends.
try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1);
@@ -2757,7 +2714,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail(inst.base.src, "TODO implement support for more arm assembly instructions", .{});
}
- if (inst.output) |output| {
+ if (inst.output_name) |output| {
if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
}
@@ -2789,7 +2746,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail(inst.base.src, "TODO implement support for more aarch64 assembly instructions", .{});
}
- if (inst.output) |output| {
+ if (inst.output_name) |output| {
if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
}
@@ -2819,7 +2776,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail(inst.base.src, "TODO implement support for more riscv64 assembly instructions", .{});
}
- if (inst.output) |output| {
+ if (inst.output_name) |output| {
if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
}
@@ -2849,7 +2806,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{});
}
- if (inst.output) |output| {
+ if (inst.output_name) |output| {
if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
}
@@ -2899,7 +2856,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
/// Sets the value without any modifications to register allocation metadata or stack allocation metadata.
- fn setRegOrMem(self: *Self, src: usize, ty: Type, loc: MCValue, val: MCValue) !void {
+ fn setRegOrMem(self: *Self, src: LazySrcLoc, ty: Type, loc: MCValue, val: MCValue) !void {
switch (loc) {
.none => return,
.register => |reg| return self.genSetReg(src, ty, reg, val),
@@ -2911,7 +2868,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn genSetStack(self: *Self, src: usize, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
+ fn genSetStack(self: *Self, src: LazySrcLoc, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
switch (arch) {
.arm, .armeb => switch (mcv) {
.dead => unreachable,
@@ -3111,7 +3068,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const adj_off = stack_offset + abi_size;
switch (abi_size) {
- 4, 8 => {
+ 1, 2, 4, 8 => {
const offset = if (math.cast(i9, adj_off)) |imm|
Instruction.LoadStoreOffset.imm_post_index(-imm)
else |_|
@@ -3121,8 +3078,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.aarch64_32 => .w29,
else => unreachable,
};
+ const str = switch (abi_size) {
+ 1 => Instruction.strb,
+ 2 => Instruction.strh,
+ 4, 8 => Instruction.str,
+ else => unreachable, // unexpected abi size
+ };
- writeInt(u32, try self.code.addManyAsArray(4), Instruction.str(reg, rn, .{
+ writeInt(u32, try self.code.addManyAsArray(4), str(reg, rn, .{
.offset = offset,
}).toU32());
},
@@ -3144,7 +3107,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn genSetReg(self: *Self, src: usize, ty: Type, reg: Register, mcv: MCValue) InnerError!void {
+ fn genSetReg(self: *Self, src: LazySrcLoc, ty: Type, reg: Register, mcv: MCValue) InnerError!void {
switch (arch) {
.arm, .armeb => switch (mcv) {
.dead => unreachable,
@@ -3687,7 +3650,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return mcv;
}
- fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) InnerError!MCValue {
+ fn genTypedValue(self: *Self, src: LazySrcLoc, typed_value: TypedValue) InnerError!MCValue {
if (typed_value.val.isUndef())
return MCValue{ .undef = {} };
const ptr_bits = self.target.cpu.arch.ptrBitWidth();
@@ -3762,7 +3725,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
};
/// Caller must call `CallMCValues.deinit`.
- fn resolveCallingConventionValues(self: *Self, src: usize, fn_ty: Type) !CallMCValues {
+ fn resolveCallingConventionValues(self: *Self, src: LazySrcLoc, fn_ty: Type) !CallMCValues {
const cc = fn_ty.fnCallingConvention();
const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
defer self.gpa.free(param_types);
@@ -3976,13 +3939,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
};
}
- fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) InnerError {
+ fn fail(self: *Self, src: LazySrcLoc, comptime format: []const u8, args: anytype) InnerError {
@setCold(true);
assert(self.err_msg == null);
- self.err_msg = try ErrorMsg.create(self.bin_file.allocator, .{
- .file_scope = self.src_loc.file_scope,
- .byte_offset = src,
- }, format, args);
+ const src_loc = if (src != .unneeded)
+ src.toSrcLocWithDecl(self.mod_fn.owner_decl)
+ else
+ self.src_loc;
+ self.err_msg = try ErrorMsg.create(self.bin_file.allocator, src_loc, format, args);
return error.CodegenFail;
}
@@ -4012,9 +3976,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
},
};
- /// An integer whose bits represent all the registers and whether they are free.
- const FreeRegInt = std.meta.Int(.unsigned, callee_preserved_regs.len);
-
fn parseRegName(name: []const u8) ?Register {
if (@hasDecl(Register, "parseRegName")) {
return Register.parseRegName(name);
diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig
@@ -484,7 +484,23 @@ pub const Instruction = union(enum) {
}
};
- fn loadStoreRegister(rt: Register, rn: Register, offset: LoadStoreOffset, load: bool) Instruction {
+ /// Which kind of load/store to perform
+ const LoadStoreVariant = enum {
+ /// 32-bit or 64-bit
+ normal,
+ /// 16-bit
+ half,
+ /// 8-bit
+ byte,
+ };
+
+ fn loadStoreRegister(
+ rt: Register,
+ rn: Register,
+ offset: LoadStoreOffset,
+ variant: LoadStoreVariant,
+ load: bool,
+ ) Instruction {
const off = offset.toU12();
const op1: u2 = blk: {
switch (offset) {
@@ -497,35 +513,27 @@ pub const Instruction = union(enum) {
break :blk 0b00;
};
const opc: u2 = if (load) 0b01 else 0b00;
- switch (rt.size()) {
- 32 => {
- return Instruction{
- .LoadStoreRegister = .{
- .rt = rt.id(),
- .rn = rn.id(),
- .offset = offset.toU12(),
- .opc = opc,
- .op1 = op1,
- .v = 0,
- .size = 0b10,
- },
- };
- },
- 64 => {
- return Instruction{
- .LoadStoreRegister = .{
- .rt = rt.id(),
- .rn = rn.id(),
- .offset = offset.toU12(),
- .opc = opc,
- .op1 = op1,
- .v = 0,
- .size = 0b11,
- },
- };
+ return Instruction{
+ .LoadStoreRegister = .{
+ .rt = rt.id(),
+ .rn = rn.id(),
+ .offset = off,
+ .opc = opc,
+ .op1 = op1,
+ .v = 0,
+ .size = blk: {
+ switch (variant) {
+ .normal => switch (rt.size()) {
+ 32 => break :blk 0b10,
+ 64 => break :blk 0b11,
+ else => unreachable, // unexpected register size
+ },
+ .half => break :blk 0b01,
+ .byte => break :blk 0b00,
+ }
+ },
},
- else => unreachable, // unexpected register size
- }
+ };
}
fn loadStorePairOfRegisters(
@@ -748,7 +756,7 @@ pub const Instruction = union(enum) {
pub fn ldr(rt: Register, args: LdrArgs) Instruction {
switch (args) {
- .register => |info| return loadStoreRegister(rt, info.rn, info.offset, true),
+ .register => |info| return loadStoreRegister(rt, info.rn, info.offset, .normal, true),
.literal => |literal| return loadLiteral(rt, literal),
}
}
@@ -758,7 +766,15 @@ pub const Instruction = union(enum) {
};
pub fn str(rt: Register, rn: Register, args: StrArgs) Instruction {
- return loadStoreRegister(rt, rn, args.offset, false);
+ return loadStoreRegister(rt, rn, args.offset, .normal, false);
+ }
+
+ pub fn strh(rt: Register, rn: Register, args: StrArgs) Instruction {
+ return loadStoreRegister(rt, rn, args.offset, .half, false);
+ }
+
+ pub fn strb(rt: Register, rn: Register, args: StrArgs) Instruction {
+ return loadStoreRegister(rt, rn, args.offset, .byte, false);
}
// Load or store pair of registers
@@ -996,6 +1012,14 @@ test "serialize instructions" {
.inst = Instruction.str(.x2, .x1, .{ .offset = Instruction.LoadStoreOffset.reg(.x3) }),
.expected = 0b11_111_0_00_00_1_00011_011_0_10_00001_00010,
},
+ .{ // strh w0, [x1]
+ .inst = Instruction.strh(.w0, .x1, .{}),
+ .expected = 0b01_111_0_01_00_000000000000_00001_00000,
+ },
+ .{ // strb w8, [x9]
+ .inst = Instruction.strb(.w8, .x9, .{}),
+ .expected = 0b00_111_0_01_00_000000000000_01001_01000,
+ },
.{ // adr x2, #0x8
.inst = Instruction.adr(.x2, 0x8),
.expected = 0b0_00_10000_0000000000000000010_00010,
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
@@ -14,6 +14,7 @@ const TypedValue = @import("../TypedValue.zig");
const C = link.File.C;
const Decl = Module.Decl;
const trace = @import("../tracy.zig").trace;
+const LazySrcLoc = Module.LazySrcLoc;
const Mutability = enum { Const, Mut };
@@ -145,11 +146,10 @@ pub const DeclGen = struct {
error_msg: ?*Module.ErrorMsg,
typedefs: TypedefMap,
- fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
- dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{
- .file_scope = dg.decl.getFileScope(),
- .byte_offset = src,
- }, format, args);
+ fn fail(dg: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
+ @setCold(true);
+ const src_loc = src.toSrcLocWithDecl(dg.decl);
+ dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, src_loc, format, args);
return error.AnalysisFail;
}
@@ -160,7 +160,7 @@ pub const DeclGen = struct {
val: Value,
) error{ OutOfMemory, AnalysisFail }!void {
if (val.isUndef()) {
- return dg.fail(dg.decl.src(), "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{});
+ return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{});
}
switch (t.zigTypeTag()) {
.Int => {
@@ -193,7 +193,7 @@ pub const DeclGen = struct {
try writer.print("{s}", .{decl.name});
},
else => |e| return dg.fail(
- dg.decl.src(),
+ .{ .node_offset = 0 },
"TODO: C backend: implement Pointer value {s}",
.{@tagName(e)},
),
@@ -276,7 +276,7 @@ pub const DeclGen = struct {
try writer.writeAll(", .error = 0 }");
}
},
- else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{
+ else => |e| return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement value {s}", .{
@tagName(e),
}),
}
@@ -350,7 +350,7 @@ pub const DeclGen = struct {
break;
}
} else {
- return dg.fail(dg.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{});
+ return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement integer types larger than 128 bits", .{});
}
},
else => unreachable,
@@ -358,7 +358,7 @@ pub const DeclGen = struct {
},
.Pointer => {
if (t.isSlice()) {
- return dg.fail(dg.decl.src(), "TODO: C backend: implement slices", .{});
+ return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement slices", .{});
} else {
try dg.renderType(w, t.elemType());
try w.writeAll(" *");
@@ -431,7 +431,7 @@ pub const DeclGen = struct {
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
},
.Null, .Undefined => unreachable, // must be const or comptime
- else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
+ else => |e| return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type {s}", .{
@tagName(e),
}),
}
@@ -569,13 +569,15 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
.optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload_ptr).?),
.is_err => try genIsErr(o, inst.castTag(.is_err).?),
.is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?),
+ .error_to_int => try genErrorToInt(o, inst.castTag(.error_to_int).?),
+ .int_to_error => try genIntToError(o, inst.castTag(.int_to_error).?),
.unwrap_errunion_payload => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload).?),
.unwrap_errunion_err => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err).?),
.unwrap_errunion_payload_ptr => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload_ptr).?),
.unwrap_errunion_err_ptr => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err_ptr).?),
.wrap_errunion_payload => try genWrapErrUnionPay(o, inst.castTag(.wrap_errunion_payload).?),
.wrap_errunion_err => try genWrapErrUnionErr(o, inst.castTag(.wrap_errunion_err).?),
- else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
+ else => |e| return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for {}", .{e}),
};
switch (result_value) {
.none => {},
@@ -756,11 +758,11 @@ fn genCall(o: *Object, inst: *Inst.Call) !CValue {
try writer.writeAll(");\n");
return result_local;
} else {
- return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement function pointers", .{});
+ return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement function pointers", .{});
}
}
-fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue {
+fn genDbgStmt(o: *Object, inst: *Inst.DbgStmt) !CValue {
// TODO emit #line directive here with line number and filename
return CValue.none;
}
@@ -913,13 +915,13 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
try o.writeCValue(writer, arg_c_value);
try writer.writeAll(";\n");
} else {
- return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{});
+ return o.dg.fail(.{ .node_offset = 0 }, "TODO non-explicit inline asm regs", .{});
}
}
const volatile_string: []const u8 = if (as.is_volatile) "volatile " else "";
try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source });
if (as.output) |_| {
- return o.dg.fail(o.dg.decl.src(), "TODO inline asm output", .{});
+ return o.dg.fail(.{ .node_offset = 0 }, "TODO inline asm output", .{});
}
if (as.inputs.len > 0) {
if (as.output == null) {
@@ -945,7 +947,7 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
if (as.base.isUnused())
return CValue.none;
- return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{});
+ return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: inline asm expression result used", .{});
}
fn genIsNull(o: *Object, inst: *Inst.UnOp) !CValue {
@@ -1072,6 +1074,14 @@ fn genIsErr(o: *Object, inst: *Inst.UnOp) !CValue {
return local;
}
+fn genIntToError(o: *Object, inst: *Inst.UnOp) !CValue {
+ return o.resolveInst(inst.operand);
+}
+
+fn genErrorToInt(o: *Object, inst: *Inst.UnOp) !CValue {
+ return o.resolveInst(inst.operand);
+}
+
fn IndentWriter(comptime UnderlyingWriter: type) type {
return struct {
const Self = @This();
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
@@ -15,6 +15,8 @@ const Inst = ir.Inst;
const Value = @import("../value.zig").Value;
const Type = @import("../type.zig").Type;
+const LazySrcLoc = Module.LazySrcLoc;
+
pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 {
const llvm_arch = switch (target.cpu.arch) {
.arm => "arm",
@@ -146,79 +148,42 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 {
return std.fmt.allocPrintZ(allocator, "{s}-unknown-{s}-{s}", .{ llvm_arch, llvm_os, llvm_abi });
}
-pub const LLVMIRModule = struct {
- module: *Module,
+pub const Object = struct {
llvm_module: *const llvm.Module,
context: *const llvm.Context,
target_machine: *const llvm.TargetMachine,
- builder: *const llvm.Builder,
-
- object_path: []const u8,
-
- gpa: *Allocator,
- err_msg: ?*Module.ErrorMsg = null,
-
- // TODO: The fields below should really move into a different struct,
- // because they are only valid when generating a function
-
- /// This stores the LLVM values used in a function, such that they can be
- /// referred to in other instructions. This table is cleared before every function is generated.
- /// TODO: Change this to a stack of Branch. Currently we store all the values from all the blocks
- /// in here, however if a block ends, the instructions can be thrown away.
- func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value) = .{},
-
- /// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction.
- args: []*const llvm.Value = &[_]*const llvm.Value{},
- arg_index: usize = 0,
-
- entry_block: *const llvm.BasicBlock = undefined,
- /// This fields stores the last alloca instruction, such that we can append more alloca instructions
- /// to the top of the function.
- latest_alloca_inst: ?*const llvm.Value = null,
+ object_pathZ: [:0]const u8,
- llvm_func: *const llvm.Value = undefined,
-
- /// This data structure is used to implement breaking to blocks.
- blocks: std.AutoHashMapUnmanaged(*Inst.Block, struct {
- parent_bb: *const llvm.BasicBlock,
- break_bbs: *BreakBasicBlocks,
- break_vals: *BreakValues,
- }) = .{},
-
- src_loc: Module.SrcLoc,
-
- const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock);
- const BreakValues = std.ArrayListUnmanaged(*const llvm.Value);
-
- pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*LLVMIRModule {
- const self = try allocator.create(LLVMIRModule);
+ pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Object {
+ const self = try allocator.create(Object);
errdefer allocator.destroy(self);
- const gpa = options.module.?.gpa;
-
- const obj_basename = try std.zig.binNameAlloc(gpa, .{
+ const obj_basename = try std.zig.binNameAlloc(allocator, .{
.root_name = options.root_name,
.target = options.target,
.output_mode = .Obj,
});
- defer gpa.free(obj_basename);
+ defer allocator.free(obj_basename);
const o_directory = options.module.?.zig_cache_artifact_directory;
- const object_path = try o_directory.join(gpa, &[_][]const u8{obj_basename});
- errdefer gpa.free(object_path);
+ const object_path = try o_directory.join(allocator, &[_][]const u8{obj_basename});
+ defer allocator.free(object_path);
+
+ const object_pathZ = try allocator.dupeZ(u8, object_path);
+ errdefer allocator.free(object_pathZ);
const context = llvm.Context.create();
errdefer context.dispose();
initializeLLVMTargets();
- const root_nameZ = try gpa.dupeZ(u8, options.root_name);
- defer gpa.free(root_nameZ);
+ const root_nameZ = try allocator.dupeZ(u8, options.root_name);
+ defer allocator.free(root_nameZ);
const llvm_module = llvm.Module.createWithName(root_nameZ.ptr, context);
errdefer llvm_module.dispose();
- const llvm_target_triple = try targetTriple(gpa, options.target);
- defer gpa.free(llvm_target_triple);
+ const llvm_target_triple = try targetTriple(allocator, options.target);
+ defer allocator.free(llvm_target_triple);
var error_message: [*:0]const u8 = undefined;
var target: *const llvm.Target = undefined;
@@ -253,34 +218,21 @@ pub const LLVMIRModule = struct {
);
errdefer target_machine.dispose();
- const builder = context.createBuilder();
- errdefer builder.dispose();
-
self.* = .{
- .module = options.module.?,
.llvm_module = llvm_module,
.context = context,
.target_machine = target_machine,
- .builder = builder,
- .object_path = object_path,
- .gpa = gpa,
- // TODO move this field into a struct that is only instantiated per gen() call
- .src_loc = undefined,
+ .object_pathZ = object_pathZ,
};
return self;
}
- pub fn deinit(self: *LLVMIRModule, allocator: *Allocator) void {
- self.builder.dispose();
+ pub fn deinit(self: *Object, allocator: *Allocator) void {
self.target_machine.dispose();
self.llvm_module.dispose();
self.context.dispose();
- self.func_inst_table.deinit(self.gpa);
- self.gpa.free(self.object_path);
-
- self.blocks.deinit(self.gpa);
-
+ allocator.free(self.object_pathZ);
allocator.destroy(self);
}
@@ -292,7 +244,7 @@ pub const LLVMIRModule = struct {
llvm.initializeAllAsmParsers();
}
- pub fn flushModule(self: *LLVMIRModule, comp: *Compilation) !void {
+ pub fn flushModule(self: *Object, comp: *Compilation) !void {
if (comp.verbose_llvm_ir) {
const dump = self.llvm_module.printToString();
defer llvm.disposeMessage(dump);
@@ -313,13 +265,10 @@ pub const LLVMIRModule = struct {
}
}
- const object_pathZ = try self.gpa.dupeZ(u8, self.object_path);
- defer self.gpa.free(object_pathZ);
-
var error_message: [*:0]const u8 = undefined;
if (self.target_machine.emitToFile(
self.llvm_module,
- object_pathZ.ptr,
+ self.object_pathZ.ptr,
.ObjectFile,
&error_message,
).toBool()) {
@@ -331,44 +280,68 @@ pub const LLVMIRModule = struct {
}
}
- pub fn updateDecl(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void {
- self.gen(module, decl) catch |err| switch (err) {
+ pub fn updateDecl(self: *Object, module: *Module, decl: *Module.Decl) !void {
+ var dg: DeclGen = .{
+ .object = self,
+ .module = module,
+ .decl = decl,
+ .err_msg = null,
+ .gpa = module.gpa,
+ };
+ dg.genDecl() catch |err| switch (err) {
error.CodegenFail => {
decl.analysis = .codegen_failure;
- try module.failed_decls.put(module.gpa, decl, self.err_msg.?);
- self.err_msg = null;
+ try module.failed_decls.put(module.gpa, decl, dg.err_msg.?);
+ dg.err_msg = null;
return;
},
else => |e| return e,
};
}
+};
- fn gen(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void {
- const typed_value = decl.typed_value.most_recent.typed_value;
- const src = decl.src();
+pub const DeclGen = struct {
+ object: *Object,
+ module: *Module,
+ decl: *Module.Decl,
+ err_msg: ?*Module.ErrorMsg,
- self.src_loc = decl.srcLoc();
+ gpa: *Allocator,
+
+ fn todo(self: *DeclGen, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } {
+ @setCold(true);
+ assert(self.err_msg == null);
+ const src_loc = @as(LazySrcLoc, .{ .node_offset = 0 }).toSrcLocWithDecl(self.decl);
+ self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, "TODO (LLVM): " ++ format, args);
+ return error.CodegenFail;
+ }
+
+ fn llvmModule(self: *DeclGen) *const llvm.Module {
+ return self.object.llvm_module;
+ }
+
+ fn context(self: *DeclGen) *const llvm.Context {
+ return self.object.context;
+ }
+
+ fn genDecl(self: *DeclGen) !void {
+ const decl = self.decl;
+ const typed_value = decl.typed_value.most_recent.typed_value;
log.debug("gen: {s} type: {}, value: {}", .{ decl.name, typed_value.ty, typed_value.val });
if (typed_value.val.castTag(.function)) |func_payload| {
const func = func_payload.data;
- const llvm_func = try self.resolveLLVMFunction(func.owner_decl, src);
+ const llvm_func = try self.resolveLLVMFunction(func.owner_decl);
// This gets the LLVM values from the function and stores them in `self.args`.
const fn_param_len = func.owner_decl.typed_value.most_recent.typed_value.ty.fnParamLen();
var args = try self.gpa.alloc(*const llvm.Value, fn_param_len);
- defer self.gpa.free(args);
for (args) |*arg, i| {
arg.* = llvm.getParam(llvm_func, @intCast(c_uint, i));
}
- self.args = args;
- self.arg_index = 0;
-
- // Make sure no other LLVM values from other functions can be referenced
- self.func_inst_table.clearRetainingCapacity();
// We remove all the basic blocks of a function to support incremental
// compilation!
@@ -377,20 +350,293 @@ pub const LLVMIRModule = struct {
bb.deleteBasicBlock();
}
- self.entry_block = self.context.appendBasicBlock(llvm_func, "Entry");
- self.builder.positionBuilderAtEnd(self.entry_block);
- self.latest_alloca_inst = null;
- self.llvm_func = llvm_func;
+ const builder = self.context().createBuilder();
+
+ const entry_block = self.context().appendBasicBlock(llvm_func, "Entry");
+ builder.positionBuilderAtEnd(entry_block);
+
+ var fg: FuncGen = .{
+ .dg = self,
+ .builder = builder,
+ .args = args,
+ .arg_index = 0,
+ .func_inst_table = .{},
+ .entry_block = entry_block,
+ .latest_alloca_inst = null,
+ .llvm_func = llvm_func,
+ .blocks = .{},
+ };
+ defer fg.deinit();
- try self.genBody(func.body);
+ try fg.genBody(func.body);
} else if (typed_value.val.castTag(.extern_fn)) |extern_fn| {
- _ = try self.resolveLLVMFunction(extern_fn.data, src);
+ _ = try self.resolveLLVMFunction(extern_fn.data);
} else {
- _ = try self.resolveGlobalDecl(decl, src);
+ _ = try self.resolveGlobalDecl(decl);
+ }
+ }
+
+ /// If the llvm function does not exist, create it
+ fn resolveLLVMFunction(self: *DeclGen, func: *Module.Decl) !*const llvm.Value {
+ // TODO: do we want to store this in our own datastructure?
+ if (self.llvmModule().getNamedFunction(func.name)) |llvm_fn| return llvm_fn;
+
+ const zig_fn_type = func.typed_value.most_recent.typed_value.ty;
+ const return_type = zig_fn_type.fnReturnType();
+
+ const fn_param_len = zig_fn_type.fnParamLen();
+
+ const fn_param_types = try self.gpa.alloc(Type, fn_param_len);
+ defer self.gpa.free(fn_param_types);
+ zig_fn_type.fnParamTypes(fn_param_types);
+
+ const llvm_param = try self.gpa.alloc(*const llvm.Type, fn_param_len);
+ defer self.gpa.free(llvm_param);
+
+ for (fn_param_types) |fn_param, i| {
+ llvm_param[i] = try self.getLLVMType(fn_param);
+ }
+
+ const fn_type = llvm.Type.functionType(
+ try self.getLLVMType(return_type),
+ if (fn_param_len == 0) null else llvm_param.ptr,
+ @intCast(c_uint, fn_param_len),
+ .False,
+ );
+ const llvm_fn = self.llvmModule().addFunction(func.name, fn_type);
+
+ if (return_type.tag() == .noreturn) {
+ self.addFnAttr(llvm_fn, "noreturn");
+ }
+
+ return llvm_fn;
+ }
+
+ fn resolveGlobalDecl(self: *DeclGen, decl: *Module.Decl) error{ OutOfMemory, CodegenFail }!*const llvm.Value {
+ // TODO: do we want to store this in our own datastructure?
+ if (self.llvmModule().getNamedGlobal(decl.name)) |val| return val;
+
+ const typed_value = decl.typed_value.most_recent.typed_value;
+
+ // TODO: remove this redundant `getLLVMType`, it is also called in `genTypedValue`.
+ const llvm_type = try self.getLLVMType(typed_value.ty);
+ const val = try self.genTypedValue(typed_value, null);
+ const global = self.llvmModule().addGlobal(llvm_type, decl.name);
+ llvm.setInitializer(global, val);
+
+ // TODO ask the Decl if it is const
+ // https://github.com/ziglang/zig/issues/7582
+
+ return global;
+ }
+
+ fn getLLVMType(self: *DeclGen, t: Type) error{ OutOfMemory, CodegenFail }!*const llvm.Type {
+ switch (t.zigTypeTag()) {
+ .Void => return self.context().voidType(),
+ .NoReturn => return self.context().voidType(),
+ .Int => {
+ const info = t.intInfo(self.module.getTarget());
+ return self.context().intType(info.bits);
+ },
+ .Bool => return self.context().intType(1),
+ .Pointer => {
+ if (t.isSlice()) {
+ return self.todo("implement slices", .{});
+ } else {
+ const elem_type = try self.getLLVMType(t.elemType());
+ return elem_type.pointerType(0);
+ }
+ },
+ .Array => {
+ const elem_type = try self.getLLVMType(t.elemType());
+ return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget())));
+ },
+ .Optional => {
+ if (!t.isPtrLikeOptional()) {
+ var buf: Type.Payload.ElemType = undefined;
+ const child_type = t.optionalChild(&buf);
+
+ var optional_types: [2]*const llvm.Type = .{
+ try self.getLLVMType(child_type),
+ self.context().intType(1),
+ };
+ return self.context().structType(&optional_types, 2, .False);
+ } else {
+ return self.todo("implement optional pointers as actual pointers", .{});
+ }
+ },
+ else => return self.todo("implement getLLVMType for type '{}'", .{t}),
+ }
+ }
+
+ // TODO: figure out a way to remove the FuncGen argument
+ fn genTypedValue(self: *DeclGen, tv: TypedValue, fg: ?*FuncGen) error{ OutOfMemory, CodegenFail }!*const llvm.Value {
+ const llvm_type = try self.getLLVMType(tv.ty);
+
+ if (tv.val.isUndef())
+ return llvm_type.getUndef();
+
+ switch (tv.ty.zigTypeTag()) {
+ .Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(),
+ .Int => {
+ var bigint_space: Value.BigIntSpace = undefined;
+ const bigint = tv.val.toBigInt(&bigint_space);
+
+ if (bigint.eqZero()) return llvm_type.constNull();
+
+ if (bigint.limbs.len != 1) {
+ return self.todo("implement bigger bigint", .{});
+ }
+ const llvm_int = llvm_type.constInt(bigint.limbs[0], .False);
+ if (!bigint.positive) {
+ return llvm.constNeg(llvm_int);
+ }
+ return llvm_int;
+ },
+ .Pointer => switch (tv.val.tag()) {
+ .decl_ref => {
+ const decl = tv.val.castTag(.decl_ref).?.data;
+ const val = try self.resolveGlobalDecl(decl);
+
+ const usize_type = try self.getLLVMType(Type.initTag(.usize));
+
+ // TODO: second index should be the index into the memory!
+ var indices: [2]*const llvm.Value = .{
+ usize_type.constNull(),
+ usize_type.constNull(),
+ };
+
+ // TODO: consider using buildInBoundsGEP2 for opaque pointers
+ return fg.?.builder.buildInBoundsGEP(val, &indices, 2, "");
+ },
+ .ref_val => {
+ const elem_value = tv.val.castTag(.ref_val).?.data;
+ const elem_type = tv.ty.castPointer().?.data;
+ const alloca = fg.?.buildAlloca(try self.getLLVMType(elem_type));
+ _ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca);
+ return alloca;
+ },
+ else => return self.todo("implement const of pointer type '{}'", .{tv.ty}),
+ },
+ .Array => {
+ if (tv.val.castTag(.bytes)) |payload| {
+ const zero_sentinel = if (tv.ty.sentinel()) |sentinel| blk: {
+ if (sentinel.tag() == .zero) break :blk true;
+ return self.todo("handle other sentinel values", .{});
+ } else false;
+
+ return self.context().constString(payload.data.ptr, @intCast(c_uint, payload.data.len), llvm.Bool.fromBool(!zero_sentinel));
+ } else {
+ return self.todo("handle more array values", .{});
+ }
+ },
+ .Optional => {
+ if (!tv.ty.isPtrLikeOptional()) {
+ var buf: Type.Payload.ElemType = undefined;
+ const child_type = tv.ty.optionalChild(&buf);
+ const llvm_child_type = try self.getLLVMType(child_type);
+
+ if (tv.val.tag() == .null_value) {
+ var optional_values: [2]*const llvm.Value = .{
+ llvm_child_type.constNull(),
+ self.context().intType(1).constNull(),
+ };
+ return self.context().constStruct(&optional_values, 2, .False);
+ } else {
+ var optional_values: [2]*const llvm.Value = .{
+ try self.genTypedValue(.{ .ty = child_type, .val = tv.val }, fg),
+ self.context().intType(1).constAllOnes(),
+ };
+ return self.context().constStruct(&optional_values, 2, .False);
+ }
+ } else {
+ return self.todo("implement const of optional pointer", .{});
+ }
+ },
+ else => return self.todo("implement const of type '{}'", .{tv.ty}),
+ }
+ }
+
+ // Helper functions
+ fn addAttr(self: *DeclGen, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void {
+ const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len);
+ assert(kind_id != 0);
+ const llvm_attr = self.context().createEnumAttribute(kind_id, 0);
+ val.addAttributeAtIndex(index, llvm_attr);
+ }
+
+ fn addFnAttr(self: *DeclGen, val: *const llvm.Value, attr_name: []const u8) void {
+ // TODO: improve this API, `addAttr(-1, attr_name)`
+ self.addAttr(val, std.math.maxInt(llvm.AttributeIndex), attr_name);
+ }
+};
+
+pub const FuncGen = struct {
+ dg: *DeclGen,
+
+ builder: *const llvm.Builder,
+
+ /// This stores the LLVM values used in a function, such that they can be
+ /// referred to in other instructions. This table is cleared before every function is generated.
+ /// TODO: Change this to a stack of Branch. Currently we store all the values from all the blocks
+ /// in here, however if a block ends, the instructions can be thrown away.
+ func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value),
+
+ /// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction.
+ args: []*const llvm.Value,
+ arg_index: usize,
+
+ entry_block: *const llvm.BasicBlock,
+ /// This fields stores the last alloca instruction, such that we can append more alloca instructions
+ /// to the top of the function.
+ latest_alloca_inst: ?*const llvm.Value,
+
+ llvm_func: *const llvm.Value,
+
+ /// This data structure is used to implement breaking to blocks.
+ blocks: std.AutoHashMapUnmanaged(*Inst.Block, struct {
+ parent_bb: *const llvm.BasicBlock,
+ break_bbs: *BreakBasicBlocks,
+ break_vals: *BreakValues,
+ }),
+
+ const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock);
+ const BreakValues = std.ArrayListUnmanaged(*const llvm.Value);
+
+ fn deinit(self: *FuncGen) void {
+ self.builder.dispose();
+ self.func_inst_table.deinit(self.gpa());
+ self.gpa().free(self.args);
+ self.blocks.deinit(self.gpa());
+ }
+
+ fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } {
+ @setCold(true);
+ return self.dg.todo(format, args);
+ }
+
+ fn llvmModule(self: *FuncGen) *const llvm.Module {
+ return self.dg.object.llvm_module;
+ }
+
+ fn context(self: *FuncGen) *const llvm.Context {
+ return self.dg.object.context;
+ }
+
+ fn gpa(self: *FuncGen) *Allocator {
+ return self.dg.gpa;
+ }
+
+ fn resolveInst(self: *FuncGen, inst: *ir.Inst) !*const llvm.Value {
+ if (inst.value()) |val| {
+ return self.dg.genTypedValue(.{ .ty = inst.ty, .val = val }, self);
}
+ if (self.func_inst_table.get(inst)) |value| return value;
+
+ return self.todo("implement global llvm values (or the value is not in the func_inst_table table)", .{});
}
- fn genBody(self: *LLVMIRModule, body: ir.Body) error{ OutOfMemory, CodegenFail }!void {
+ fn genBody(self: *FuncGen, body: ir.Body) error{ OutOfMemory, CodegenFail }!void {
for (body.instructions) |inst| {
const opt_value = switch (inst.tag) {
.add => try self.genAdd(inst.castTag(.add).?),
@@ -428,13 +674,13 @@ pub const LLVMIRModule = struct {
// TODO: implement debug info
break :blk null;
},
- else => |tag| return self.fail(inst.src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}),
+ else => |tag| return self.todo("implement TZIR instruction: {}", .{tag}),
};
- if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val);
+ if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa(), inst, val);
}
}
- fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !?*const llvm.Value {
+ fn genCall(self: *FuncGen, inst: *Inst.Call) !?*const llvm.Value {
if (inst.func.value()) |func_value| {
const fn_decl = if (func_value.castTag(.extern_fn)) |extern_fn|
extern_fn.data
@@ -444,12 +690,12 @@ pub const LLVMIRModule = struct {
unreachable;
const zig_fn_type = fn_decl.typed_value.most_recent.typed_value.ty;
- const llvm_fn = try self.resolveLLVMFunction(fn_decl, inst.base.src);
+ const llvm_fn = try self.dg.resolveLLVMFunction(fn_decl);
const num_args = inst.args.len;
- const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, num_args);
- defer self.gpa.free(llvm_param_vals);
+ const llvm_param_vals = try self.gpa().alloc(*const llvm.Value, num_args);
+ defer self.gpa().free(llvm_param_vals);
for (inst.args) |arg, i| {
llvm_param_vals[i] = try self.resolveInst(arg);
@@ -474,27 +720,32 @@ pub const LLVMIRModule = struct {
return call;
} else {
- return self.fail(inst.base.src, "TODO implement calling runtime known function pointer LLVM backend", .{});
+ return self.todo("implement calling runtime known function pointer", .{});
}
}
- fn genRetVoid(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.Value {
+ fn genRetVoid(self: *FuncGen, inst: *Inst.NoOp) ?*const llvm.Value {
_ = self.builder.buildRetVoid();
return null;
}
- fn genRet(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value {
+ fn genRet(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
+ if (!inst.operand.ty.hasCodeGenBits()) {
+ // TODO: in astgen these instructions should turn into `retvoid` instructions.
+ _ = self.builder.buildRetVoid();
+ return null;
+ }
_ = self.builder.buildRet(try self.resolveInst(inst.operand));
return null;
}
- fn genCmp(self: *LLVMIRModule, inst: *Inst.BinOp, op: math.CompareOperator) !?*const llvm.Value {
+ fn genCmp(self: *FuncGen, inst: *Inst.BinOp, op: math.CompareOperator) !?*const llvm.Value {
const lhs = try self.resolveInst(inst.lhs);
const rhs = try self.resolveInst(inst.rhs);
if (!inst.base.ty.isInt())
if (inst.base.ty.tag() != .bool)
- return self.fail(inst.base.src, "TODO implement 'genCmp' for type {}", .{inst.base.ty});
+ return self.todo("implement 'genCmp' for type {}", .{inst.base.ty});
const is_signed = inst.base.ty.isSignedInt();
const operation = switch (op) {
@@ -509,21 +760,21 @@ pub const LLVMIRModule = struct {
return self.builder.buildICmp(operation, lhs, rhs, "");
}
- fn genBlock(self: *LLVMIRModule, inst: *Inst.Block) !?*const llvm.Value {
- const parent_bb = self.context.createBasicBlock("Block");
+ fn genBlock(self: *FuncGen, inst: *Inst.Block) !?*const llvm.Value {
+ const parent_bb = self.context().createBasicBlock("Block");
// 5 breaks to a block seems like a reasonable default.
- var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa, 5);
- var break_vals = try BreakValues.initCapacity(self.gpa, 5);
- try self.blocks.putNoClobber(self.gpa, inst, .{
+ var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa(), 5);
+ var break_vals = try BreakValues.initCapacity(self.gpa(), 5);
+ try self.blocks.putNoClobber(self.gpa(), inst, .{
.parent_bb = parent_bb,
.break_bbs = &break_bbs,
.break_vals = &break_vals,
});
defer {
self.blocks.removeAssertDiscard(inst);
- break_bbs.deinit(self.gpa);
- break_vals.deinit(self.gpa);
+ break_bbs.deinit(self.gpa());
+ break_vals.deinit(self.gpa());
}
try self.genBody(inst.body);
@@ -534,7 +785,7 @@ pub const LLVMIRModule = struct {
// If the block does not return a value, we dont have to create a phi node.
if (!inst.base.ty.hasCodeGenBits()) return null;
- const phi_node = self.builder.buildPhi(try self.getLLVMType(inst.base.ty, inst.base.src), "");
+ const phi_node = self.builder.buildPhi(try self.dg.getLLVMType(inst.base.ty), "");
phi_node.addIncoming(
break_vals.items.ptr,
break_bbs.items.ptr,
@@ -543,7 +794,7 @@ pub const LLVMIRModule = struct {
return phi_node;
}
- fn genBr(self: *LLVMIRModule, inst: *Inst.Br) !?*const llvm.Value {
+ fn genBr(self: *FuncGen, inst: *Inst.Br) !?*const llvm.Value {
var block = self.blocks.get(inst.block).?;
// If the break doesn't break a value, then we don't have to add
@@ -556,25 +807,25 @@ pub const LLVMIRModule = struct {
// For the phi node, we need the basic blocks and the values of the
// break instructions.
- try block.break_bbs.append(self.gpa, self.builder.getInsertBlock());
- try block.break_vals.append(self.gpa, val);
+ try block.break_bbs.append(self.gpa(), self.builder.getInsertBlock());
+ try block.break_vals.append(self.gpa(), val);
_ = self.builder.buildBr(block.parent_bb);
}
return null;
}
- fn genBrVoid(self: *LLVMIRModule, inst: *Inst.BrVoid) !?*const llvm.Value {
+ fn genBrVoid(self: *FuncGen, inst: *Inst.BrVoid) !?*const llvm.Value {
var block = self.blocks.get(inst.block).?;
_ = self.builder.buildBr(block.parent_bb);
return null;
}
- fn genCondBr(self: *LLVMIRModule, inst: *Inst.CondBr) !?*const llvm.Value {
+ fn genCondBr(self: *FuncGen, inst: *Inst.CondBr) !?*const llvm.Value {
const condition_value = try self.resolveInst(inst.condition);
- const then_block = self.context.appendBasicBlock(self.llvm_func, "Then");
- const else_block = self.context.appendBasicBlock(self.llvm_func, "Else");
+ const then_block = self.context().appendBasicBlock(self.llvm_func, "Then");
+ const else_block = self.context().appendBasicBlock(self.llvm_func, "Else");
{
const prev_block = self.builder.getInsertBlock();
defer self.builder.positionBuilderAtEnd(prev_block);
@@ -589,8 +840,8 @@ pub const LLVMIRModule = struct {
return null;
}
- fn genLoop(self: *LLVMIRModule, inst: *Inst.Loop) !?*const llvm.Value {
- const loop_block = self.context.appendBasicBlock(self.llvm_func, "Loop");
+ fn genLoop(self: *FuncGen, inst: *Inst.Loop) !?*const llvm.Value {
+ const loop_block = self.context().appendBasicBlock(self.llvm_func, "Loop");
_ = self.builder.buildBr(loop_block);
self.builder.positionBuilderAtEnd(loop_block);
@@ -600,20 +851,20 @@ pub const LLVMIRModule = struct {
return null;
}
- fn genNot(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value {
+ fn genNot(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
return self.builder.buildNot(try self.resolveInst(inst.operand), "");
}
- fn genUnreach(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.Value {
+ fn genUnreach(self: *FuncGen, inst: *Inst.NoOp) ?*const llvm.Value {
_ = self.builder.buildUnreachable();
return null;
}
- fn genIsNonNull(self: *LLVMIRModule, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
+ fn genIsNonNull(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
const operand = try self.resolveInst(inst.operand);
if (operand_is_ptr) {
- const index_type = self.context.intType(32);
+ const index_type = self.context().intType(32);
var indices: [2]*const llvm.Value = .{
index_type.constNull(),
@@ -626,15 +877,15 @@ pub const LLVMIRModule = struct {
}
}
- fn genIsNull(self: *LLVMIRModule, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
+ fn genIsNull(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
return self.builder.buildNot((try self.genIsNonNull(inst, operand_is_ptr)).?, "");
}
- fn genOptionalPayload(self: *LLVMIRModule, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
+ fn genOptionalPayload(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
const operand = try self.resolveInst(inst.operand);
if (operand_is_ptr) {
- const index_type = self.context.intType(32);
+ const index_type = self.context().intType(32);
var indices: [2]*const llvm.Value = .{
index_type.constNull(),
@@ -647,12 +898,12 @@ pub const LLVMIRModule = struct {
}
}
- fn genAdd(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value {
+ fn genAdd(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value {
const lhs = try self.resolveInst(inst.lhs);
const rhs = try self.resolveInst(inst.rhs);
if (!inst.base.ty.isInt())
- return self.fail(inst.base.src, "TODO implement 'genAdd' for type {}", .{inst.base.ty});
+ return self.todo("implement 'genAdd' for type {}", .{inst.base.ty});
return if (inst.base.ty.isSignedInt())
self.builder.buildNSWAdd(lhs, rhs, "")
@@ -660,12 +911,12 @@ pub const LLVMIRModule = struct {
self.builder.buildNUWAdd(lhs, rhs, "");
}
- fn genSub(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value {
+ fn genSub(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value {
const lhs = try self.resolveInst(inst.lhs);
const rhs = try self.resolveInst(inst.rhs);
if (!inst.base.ty.isInt())
- return self.fail(inst.base.src, "TODO implement 'genSub' for type {}", .{inst.base.ty});
+ return self.todo("implement 'genSub' for type {}", .{inst.base.ty});
return if (inst.base.ty.isSignedInt())
self.builder.buildNSWSub(lhs, rhs, "")
@@ -673,44 +924,44 @@ pub const LLVMIRModule = struct {
self.builder.buildNUWSub(lhs, rhs, "");
}
- fn genIntCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value {
+ fn genIntCast(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
const val = try self.resolveInst(inst.operand);
const signed = inst.base.ty.isSignedInt();
// TODO: Should we use intcast here or just a simple bitcast?
// LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes
- return self.builder.buildIntCast2(val, try self.getLLVMType(inst.base.ty, inst.base.src), llvm.Bool.fromBool(signed), "");
+ return self.builder.buildIntCast2(val, try self.dg.getLLVMType(inst.base.ty), llvm.Bool.fromBool(signed), "");
}
- fn genBitCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value {
+ fn genBitCast(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
const val = try self.resolveInst(inst.operand);
- const dest_type = try self.getLLVMType(inst.base.ty, inst.base.src);
+ const dest_type = try self.dg.getLLVMType(inst.base.ty);
return self.builder.buildBitCast(val, dest_type, "");
}
- fn genArg(self: *LLVMIRModule, inst: *Inst.Arg) !?*const llvm.Value {
+ fn genArg(self: *FuncGen, inst: *Inst.Arg) !?*const llvm.Value {
const arg_val = self.args[self.arg_index];
self.arg_index += 1;
- const ptr_val = self.buildAlloca(try self.getLLVMType(inst.base.ty, inst.base.src));
+ const ptr_val = self.buildAlloca(try self.dg.getLLVMType(inst.base.ty));
_ = self.builder.buildStore(arg_val, ptr_val);
return self.builder.buildLoad(ptr_val, "");
}
- fn genAlloc(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.Value {
+ fn genAlloc(self: *FuncGen, inst: *Inst.NoOp) !?*const llvm.Value {
// buildAlloca expects the pointee type, not the pointer type, so assert that
// a Payload.PointerSimple is passed to the alloc instruction.
const pointee_type = inst.base.ty.castPointer().?.data;
// TODO: figure out a way to get the name of the var decl.
// TODO: set alignment and volatile
- return self.buildAlloca(try self.getLLVMType(pointee_type, inst.base.src));
+ return self.buildAlloca(try self.dg.getLLVMType(pointee_type));
}
/// Use this instead of builder.buildAlloca, because this function makes sure to
/// put the alloca instruction at the top of the function!
- fn buildAlloca(self: *LLVMIRModule, t: *const llvm.Type) *const llvm.Value {
+ fn buildAlloca(self: *FuncGen, t: *const llvm.Type) *const llvm.Value {
const prev_block = self.builder.getInsertBlock();
defer self.builder.positionBuilderAtEnd(prev_block);
@@ -732,242 +983,30 @@ pub const LLVMIRModule = struct {
return val;
}
- fn genStore(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value {
+ fn genStore(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value {
const val = try self.resolveInst(inst.rhs);
const ptr = try self.resolveInst(inst.lhs);
_ = self.builder.buildStore(val, ptr);
return null;
}
- fn genLoad(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value {
+ fn genLoad(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
const ptr_val = try self.resolveInst(inst.operand);
return self.builder.buildLoad(ptr_val, "");
}
- fn genBreakpoint(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.Value {
+ fn genBreakpoint(self: *FuncGen, inst: *Inst.NoOp) !?*const llvm.Value {
const llvn_fn = self.getIntrinsic("llvm.debugtrap");
_ = self.builder.buildCall(llvn_fn, null, 0, "");
return null;
}
- fn getIntrinsic(self: *LLVMIRModule, name: []const u8) *const llvm.Value {
+ fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value {
const id = llvm.lookupIntrinsicID(name.ptr, name.len);
assert(id != 0);
// TODO: add support for overload intrinsics by passing the prefix of the intrinsic
// to `lookupIntrinsicID` and then passing the correct types to
// `getIntrinsicDeclaration`
- return self.llvm_module.getIntrinsicDeclaration(id, null, 0);
- }
-
- fn resolveInst(self: *LLVMIRModule, inst: *ir.Inst) !*const llvm.Value {
- if (inst.value()) |val| {
- return self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = val });
- }
- if (self.func_inst_table.get(inst)) |value| return value;
-
- return self.fail(inst.src, "TODO implement global llvm values (or the value is not in the func_inst_table table)", .{});
- }
-
- fn genTypedValue(self: *LLVMIRModule, src: usize, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value {
- const llvm_type = try self.getLLVMType(tv.ty, src);
-
- if (tv.val.isUndef())
- return llvm_type.getUndef();
-
- switch (tv.ty.zigTypeTag()) {
- .Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(),
- .Int => {
- var bigint_space: Value.BigIntSpace = undefined;
- const bigint = tv.val.toBigInt(&bigint_space);
-
- if (bigint.eqZero()) return llvm_type.constNull();
-
- if (bigint.limbs.len != 1) {
- return self.fail(src, "TODO implement bigger bigint", .{});
- }
- const llvm_int = llvm_type.constInt(bigint.limbs[0], .False);
- if (!bigint.positive) {
- return llvm.constNeg(llvm_int);
- }
- return llvm_int;
- },
- .Pointer => switch (tv.val.tag()) {
- .decl_ref => {
- const decl = tv.val.castTag(.decl_ref).?.data;
- const val = try self.resolveGlobalDecl(decl, src);
-
- const usize_type = try self.getLLVMType(Type.initTag(.usize), src);
-
- // TODO: second index should be the index into the memory!
- var indices: [2]*const llvm.Value = .{
- usize_type.constNull(),
- usize_type.constNull(),
- };
-
- // TODO: consider using buildInBoundsGEP2 for opaque pointers
- return self.builder.buildInBoundsGEP(val, &indices, 2, "");
- },
- .ref_val => {
- const elem_value = tv.val.castTag(.ref_val).?.data;
- const elem_type = tv.ty.castPointer().?.data;
- const alloca = self.buildAlloca(try self.getLLVMType(elem_type, src));
- _ = self.builder.buildStore(try self.genTypedValue(src, .{ .ty = elem_type, .val = elem_value }), alloca);
- return alloca;
- },
- else => return self.fail(src, "TODO implement const of pointer type '{}'", .{tv.ty}),
- },
- .Array => {
- if (tv.val.castTag(.bytes)) |payload| {
- const zero_sentinel = if (tv.ty.sentinel()) |sentinel| blk: {
- if (sentinel.tag() == .zero) break :blk true;
- return self.fail(src, "TODO handle other sentinel values", .{});
- } else false;
-
- return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), llvm.Bool.fromBool(!zero_sentinel));
- } else {
- return self.fail(src, "TODO handle more array values", .{});
- }
- },
- .Optional => {
- if (!tv.ty.isPtrLikeOptional()) {
- var buf: Type.Payload.ElemType = undefined;
- const child_type = tv.ty.optionalChild(&buf);
- const llvm_child_type = try self.getLLVMType(child_type, src);
-
- if (tv.val.tag() == .null_value) {
- var optional_values: [2]*const llvm.Value = .{
- llvm_child_type.constNull(),
- self.context.intType(1).constNull(),
- };
- return self.context.constStruct(&optional_values, 2, .False);
- } else {
- var optional_values: [2]*const llvm.Value = .{
- try self.genTypedValue(src, .{ .ty = child_type, .val = tv.val }),
- self.context.intType(1).constAllOnes(),
- };
- return self.context.constStruct(&optional_values, 2, .False);
- }
- } else {
- return self.fail(src, "TODO implement const of optional pointer", .{});
- }
- },
- else => return self.fail(src, "TODO implement const of type '{}'", .{tv.ty}),
- }
- }
-
- fn getLLVMType(self: *LLVMIRModule, t: Type, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.Type {
- switch (t.zigTypeTag()) {
- .Void => return self.context.voidType(),
- .NoReturn => return self.context.voidType(),
- .Int => {
- const info = t.intInfo(self.module.getTarget());
- return self.context.intType(info.bits);
- },
- .Bool => return self.context.intType(1),
- .Pointer => {
- if (t.isSlice()) {
- return self.fail(src, "TODO: LLVM backend: implement slices", .{});
- } else {
- const elem_type = try self.getLLVMType(t.elemType(), src);
- return elem_type.pointerType(0);
- }
- },
- .Array => {
- const elem_type = try self.getLLVMType(t.elemType(), src);
- return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget())));
- },
- .Optional => {
- if (!t.isPtrLikeOptional()) {
- var buf: Type.Payload.ElemType = undefined;
- const child_type = t.optionalChild(&buf);
-
- var optional_types: [2]*const llvm.Type = .{
- try self.getLLVMType(child_type, src),
- self.context.intType(1),
- };
- return self.context.structType(&optional_types, 2, .False);
- } else {
- return self.fail(src, "TODO implement optional pointers as actual pointers", .{});
- }
- },
- else => return self.fail(src, "TODO implement getLLVMType for type '{}'", .{t}),
- }
- }
-
- fn resolveGlobalDecl(self: *LLVMIRModule, decl: *Module.Decl, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.Value {
- // TODO: do we want to store this in our own datastructure?
- if (self.llvm_module.getNamedGlobal(decl.name)) |val| return val;
-
- const typed_value = decl.typed_value.most_recent.typed_value;
-
- // TODO: remove this redundant `getLLVMType`, it is also called in `genTypedValue`.
- const llvm_type = try self.getLLVMType(typed_value.ty, src);
- const val = try self.genTypedValue(src, typed_value);
- const global = self.llvm_module.addGlobal(llvm_type, decl.name);
- llvm.setInitializer(global, val);
-
- // TODO ask the Decl if it is const
- // https://github.com/ziglang/zig/issues/7582
-
- return global;
- }
-
- /// If the llvm function does not exist, create it
- fn resolveLLVMFunction(self: *LLVMIRModule, func: *Module.Decl, src: usize) !*const llvm.Value {
- // TODO: do we want to store this in our own datastructure?
- if (self.llvm_module.getNamedFunction(func.name)) |llvm_fn| return llvm_fn;
-
- const zig_fn_type = func.typed_value.most_recent.typed_value.ty;
- const return_type = zig_fn_type.fnReturnType();
-
- const fn_param_len = zig_fn_type.fnParamLen();
-
- const fn_param_types = try self.gpa.alloc(Type, fn_param_len);
- defer self.gpa.free(fn_param_types);
- zig_fn_type.fnParamTypes(fn_param_types);
-
- const llvm_param = try self.gpa.alloc(*const llvm.Type, fn_param_len);
- defer self.gpa.free(llvm_param);
-
- for (fn_param_types) |fn_param, i| {
- llvm_param[i] = try self.getLLVMType(fn_param, src);
- }
-
- const fn_type = llvm.Type.functionType(
- try self.getLLVMType(return_type, src),
- if (fn_param_len == 0) null else llvm_param.ptr,
- @intCast(c_uint, fn_param_len),
- .False,
- );
- const llvm_fn = self.llvm_module.addFunction(func.name, fn_type);
-
- if (return_type.tag() == .noreturn) {
- self.addFnAttr(llvm_fn, "noreturn");
- }
-
- return llvm_fn;
- }
-
- // Helper functions
- fn addAttr(self: LLVMIRModule, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void {
- const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len);
- assert(kind_id != 0);
- const llvm_attr = self.context.createEnumAttribute(kind_id, 0);
- val.addAttributeAtIndex(index, llvm_attr);
- }
-
- fn addFnAttr(self: *LLVMIRModule, val: *const llvm.Value, attr_name: []const u8) void {
- // TODO: improve this API, `addAttr(-1, attr_name)`
- self.addAttr(val, std.math.maxInt(llvm.AttributeIndex), attr_name);
- }
-
- pub fn fail(self: *LLVMIRModule, src: usize, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } {
- @setCold(true);
- assert(self.err_msg == null);
- self.err_msg = try Module.ErrorMsg.create(self.gpa, .{
- .file_scope = self.src_loc.file_scope,
- .byte_offset = src,
- }, format, args);
- return error.CodegenFail;
+ return self.llvmModule().getIntrinsicDeclaration(id, null, 0);
}
};
diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig
@@ -14,6 +14,7 @@ const Type = @import("../type.zig").Type;
const Value = @import("../value.zig").Value;
const Compilation = @import("../Compilation.zig");
const AnyMCValue = @import("../codegen.zig").AnyMCValue;
+const LazySrcLoc = Module.LazySrcLoc;
/// Wasm Value, created when generating an instruction
const WValue = union(enum) {
@@ -70,11 +71,9 @@ pub const Context = struct {
}
/// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
- fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError {
- self.err_msg = try Module.ErrorMsg.create(self.gpa, .{
- .file_scope = self.decl.getFileScope(),
- .byte_offset = src,
- }, fmt, args);
+ fn fail(self: *Context, src: LazySrcLoc, comptime fmt: []const u8, args: anytype) InnerError {
+ const src_loc = src.toSrcLocWithDecl(self.decl);
+ self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, fmt, args);
return error.CodegenFail;
}
@@ -91,7 +90,7 @@ pub const Context = struct {
}
/// Using a given `Type`, returns the corresponding wasm value type
- fn genValtype(self: *Context, src: usize, ty: Type) InnerError!u8 {
+ fn genValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!u8 {
return switch (ty.tag()) {
.f32 => wasm.valtype(.f32),
.f64 => wasm.valtype(.f64),
@@ -104,7 +103,7 @@ pub const Context = struct {
/// Using a given `Type`, returns the corresponding wasm value type
/// Differently from `genValtype` this also allows `void` to create a block
/// with no return type
- fn genBlockType(self: *Context, src: usize, ty: Type) InnerError!u8 {
+ fn genBlockType(self: *Context, src: LazySrcLoc, ty: Type) InnerError!u8 {
return switch (ty.tag()) {
.void, .noreturn => wasm.block_empty,
else => self.genValtype(src, ty),
@@ -139,7 +138,7 @@ pub const Context = struct {
ty.fnParamTypes(params);
for (params) |param_type| {
// Can we maybe get the source index of each param?
- const val_type = try self.genValtype(self.decl.src(), param_type);
+ const val_type = try self.genValtype(.{ .node_offset = 0 }, param_type);
try writer.writeByte(val_type);
}
}
@@ -151,7 +150,7 @@ pub const Context = struct {
else => |ret_type| {
try leb.writeULEB128(writer, @as(u32, 1));
// Can we maybe get the source index of the return type?
- const val_type = try self.genValtype(self.decl.src(), return_type);
+ const val_type = try self.genValtype(.{ .node_offset = 0 }, return_type);
try writer.writeByte(val_type);
},
}
@@ -168,7 +167,7 @@ pub const Context = struct {
const mod_fn = blk: {
if (tv.val.castTag(.function)) |func| break :blk func.data;
if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions
- return self.fail(self.decl.src(), "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});
+ return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});
};
// Reserve space to write the size after generating the code as well as space for locals count
diff --git a/src/ir.zig b/src/ir.zig
@@ -25,8 +25,7 @@ pub const Inst = struct {
/// lifetimes of operands are encoded elsewhere.
deaths: DeathsInt = undefined,
ty: Type,
- /// Byte offset into the source.
- src: usize,
+ src: Module.LazySrcLoc,
pub const DeathsInt = u16;
pub const DeathsBitIndex = std.math.Log2Int(DeathsInt);
@@ -81,22 +80,28 @@ pub const Inst = struct {
condbr,
constant,
dbg_stmt,
- // ?T => bool
+ /// ?T => bool
is_null,
- // ?T => bool (inverted logic)
+ /// ?T => bool (inverted logic)
is_non_null,
- // *?T => bool
+ /// *?T => bool
is_null_ptr,
- // *?T => bool (inverted logic)
+ /// *?T => bool (inverted logic)
is_non_null_ptr,
- // E!T => bool
+ /// E!T => bool
is_err,
- // *E!T => bool
+ /// *E!T => bool
is_err_ptr,
+ /// E => u16
+ error_to_int,
+ /// u16 => E
+ int_to_error,
bool_and,
bool_or,
/// Read a value from a pointer.
load,
+ /// A labeled block of code that loops forever. At the end of the body it is implied
+ /// to repeat; no explicit "repeat" instruction terminates loop bodies.
loop,
ptrtoint,
ref,
@@ -113,9 +118,9 @@ pub const Inst = struct {
not,
floatcast,
intcast,
- // ?T => T
+ /// ?T => T
optional_payload,
- // *?T => *T
+ /// *?T => *T
optional_payload_ptr,
wrap_optional,
/// E!T -> T
@@ -132,6 +137,8 @@ pub const Inst = struct {
wrap_errunion_err,
xor,
switchbr,
+ /// Given a pointer to a struct and a field index, returns a pointer to the field.
+ struct_field_ptr,
pub fn Type(tag: Tag) type {
return switch (tag) {
@@ -139,7 +146,6 @@ pub const Inst = struct {
.retvoid,
.unreach,
.breakpoint,
- .dbg_stmt,
=> NoOp,
.ref,
@@ -152,6 +158,8 @@ pub const Inst = struct {
.is_null_ptr,
.is_err,
.is_err_ptr,
+ .int_to_error,
+ .error_to_int,
.ptrtoint,
.floatcast,
.intcast,
@@ -198,7 +206,9 @@ pub const Inst = struct {
.constant => Constant,
.loop => Loop,
.varptr => VarPtr,
+ .struct_field_ptr => StructFieldPtr,
.switchbr => SwitchBr,
+ .dbg_stmt => DbgStmt,
};
}
@@ -360,7 +370,8 @@ pub const Inst = struct {
base: Inst,
asm_source: []const u8,
is_volatile: bool,
- output: ?[]const u8,
+ output: ?*Inst,
+ output_name: ?[]const u8,
inputs: []const []const u8,
clobbers: []const []const u8,
args: []const *Inst,
@@ -544,6 +555,27 @@ pub const Inst = struct {
}
};
+ pub const StructFieldPtr = struct {
+ pub const base_tag = Tag.struct_field_ptr;
+
+ base: Inst,
+ struct_ptr: *Inst,
+ field_index: usize,
+
+ pub fn operandCount(self: *const StructFieldPtr) usize {
+ return 1;
+ }
+ pub fn getOperand(self: *const StructFieldPtr, index: usize) ?*Inst {
+ var i = index;
+
+ if (i < 1)
+ return self.struct_ptr;
+ i -= 1;
+
+ return null;
+ }
+ };
+
pub const SwitchBr = struct {
pub const base_tag = Tag.switchbr;
@@ -584,8 +616,528 @@ pub const Inst = struct {
return (self.deaths + self.else_index)[0..self.else_deaths];
}
};
+
+ pub const DbgStmt = struct {
+ pub const base_tag = Tag.dbg_stmt;
+
+ base: Inst,
+ byte_offset: u32,
+
+ pub fn operandCount(self: *const DbgStmt) usize {
+ return 0;
+ }
+ pub fn getOperand(self: *const DbgStmt, index: usize) ?*Inst {
+ return null;
+ }
+ };
};
pub const Body = struct {
instructions: []*Inst,
};
+
+/// For debugging purposes, prints a function representation to stderr.
+pub fn dumpFn(old_module: Module, module_fn: *Module.Fn) void {
+ const allocator = old_module.gpa;
+ var ctx: DumpTzir = .{
+ .allocator = allocator,
+ .arena = std.heap.ArenaAllocator.init(allocator),
+ .old_module = &old_module,
+ .module_fn = module_fn,
+ .indent = 2,
+ .inst_table = DumpTzir.InstTable.init(allocator),
+ .partial_inst_table = DumpTzir.InstTable.init(allocator),
+ .const_table = DumpTzir.InstTable.init(allocator),
+ };
+ defer ctx.inst_table.deinit();
+ defer ctx.partial_inst_table.deinit();
+ defer ctx.const_table.deinit();
+ defer ctx.arena.deinit();
+
+ switch (module_fn.state) {
+ .queued => std.debug.print("(queued)", .{}),
+ .inline_only => std.debug.print("(inline_only)", .{}),
+ .in_progress => std.debug.print("(in_progress)", .{}),
+ .sema_failure => std.debug.print("(sema_failure)", .{}),
+ .dependency_failure => std.debug.print("(dependency_failure)", .{}),
+ .success => {
+ const writer = std.io.getStdErr().writer();
+ ctx.dump(module_fn.body, writer) catch @panic("failed to dump TZIR");
+ },
+ }
+}
+
+const DumpTzir = struct {
+ allocator: *std.mem.Allocator,
+ arena: std.heap.ArenaAllocator,
+ old_module: *const Module,
+ module_fn: *Module.Fn,
+ indent: usize,
+ inst_table: InstTable,
+ partial_inst_table: InstTable,
+ const_table: InstTable,
+ next_index: usize = 0,
+ next_partial_index: usize = 0,
+ next_const_index: usize = 0,
+
+ const InstTable = std.AutoArrayHashMap(*Inst, usize);
+
+ /// TODO: Improve this code to include a stack of Body and store the instructions
+ /// in there. Now we are putting all the instructions in a function local table,
+ /// however instructions that are in a Body can be thown away when the Body ends.
+ fn dump(dtz: *DumpTzir, body: Body, writer: std.fs.File.Writer) !void {
+ // First pass to pre-populate the table so that we can show even invalid references.
+ // Must iterate the same order we iterate the second time.
+ // We also look for constants and put them in the const_table.
+ try dtz.fetchInstsAndResolveConsts(body);
+
+ std.debug.print("Module.Function(name={s}):\n", .{dtz.module_fn.owner_decl.name});
+
+ for (dtz.const_table.items()) |entry| {
+ const constant = entry.key.castTag(.constant).?;
+ try writer.print(" @{d}: {} = {};\n", .{
+ entry.value, constant.base.ty, constant.val,
+ });
+ }
+
+ return dtz.dumpBody(body, writer);
+ }
+
+ fn fetchInstsAndResolveConsts(dtz: *DumpTzir, body: Body) error{OutOfMemory}!void {
+ for (body.instructions) |inst| {
+ try dtz.inst_table.put(inst, dtz.next_index);
+ dtz.next_index += 1;
+ switch (inst.tag) {
+ .alloc,
+ .retvoid,
+ .unreach,
+ .breakpoint,
+ .dbg_stmt,
+ .arg,
+ => {},
+
+ .ref,
+ .ret,
+ .bitcast,
+ .not,
+ .is_non_null,
+ .is_non_null_ptr,
+ .is_null,
+ .is_null_ptr,
+ .is_err,
+ .is_err_ptr,
+ .error_to_int,
+ .int_to_error,
+ .ptrtoint,
+ .floatcast,
+ .intcast,
+ .load,
+ .optional_payload,
+ .optional_payload_ptr,
+ .wrap_optional,
+ .wrap_errunion_payload,
+ .wrap_errunion_err,
+ .unwrap_errunion_payload,
+ .unwrap_errunion_err,
+ .unwrap_errunion_payload_ptr,
+ .unwrap_errunion_err_ptr,
+ => {
+ const un_op = inst.cast(Inst.UnOp).?;
+ try dtz.findConst(un_op.operand);
+ },
+
+ .add,
+ .addwrap,
+ .sub,
+ .subwrap,
+ .mul,
+ .mulwrap,
+ .cmp_lt,
+ .cmp_lte,
+ .cmp_eq,
+ .cmp_gte,
+ .cmp_gt,
+ .cmp_neq,
+ .store,
+ .bool_and,
+ .bool_or,
+ .bit_and,
+ .bit_or,
+ .xor,
+ => {
+ const bin_op = inst.cast(Inst.BinOp).?;
+ try dtz.findConst(bin_op.lhs);
+ try dtz.findConst(bin_op.rhs);
+ },
+
+ .br => {
+ const br = inst.castTag(.br).?;
+ try dtz.findConst(&br.block.base);
+ try dtz.findConst(br.operand);
+ },
+
+ .br_block_flat => {
+ const br_block_flat = inst.castTag(.br_block_flat).?;
+ try dtz.findConst(&br_block_flat.block.base);
+ try dtz.fetchInstsAndResolveConsts(br_block_flat.body);
+ },
+
+ .br_void => {
+ const br_void = inst.castTag(.br_void).?;
+ try dtz.findConst(&br_void.block.base);
+ },
+
+ .block => {
+ const block = inst.castTag(.block).?;
+ try dtz.fetchInstsAndResolveConsts(block.body);
+ },
+
+ .condbr => {
+ const condbr = inst.castTag(.condbr).?;
+ try dtz.findConst(condbr.condition);
+ try dtz.fetchInstsAndResolveConsts(condbr.then_body);
+ try dtz.fetchInstsAndResolveConsts(condbr.else_body);
+ },
+ .switchbr => {
+ const switchbr = inst.castTag(.switchbr).?;
+ try dtz.findConst(switchbr.target);
+ try dtz.fetchInstsAndResolveConsts(switchbr.else_body);
+ for (switchbr.cases) |case| {
+ try dtz.fetchInstsAndResolveConsts(case.body);
+ }
+ },
+
+ .loop => {
+ const loop = inst.castTag(.loop).?;
+ try dtz.fetchInstsAndResolveConsts(loop.body);
+ },
+ .call => {
+ const call = inst.castTag(.call).?;
+ try dtz.findConst(call.func);
+ for (call.args) |arg| {
+ try dtz.findConst(arg);
+ }
+ },
+ .struct_field_ptr => {
+ const struct_field_ptr = inst.castTag(.struct_field_ptr).?;
+ try dtz.findConst(struct_field_ptr.struct_ptr);
+ },
+
+ // TODO fill out this debug printing
+ .assembly,
+ .constant,
+ .varptr,
+ => {},
+ }
+ }
+ }
+
+ fn dumpBody(dtz: *DumpTzir, body: Body, writer: std.fs.File.Writer) (std.fs.File.WriteError || error{OutOfMemory})!void {
+ for (body.instructions) |inst| {
+ const my_index = dtz.next_partial_index;
+ try dtz.partial_inst_table.put(inst, my_index);
+ dtz.next_partial_index += 1;
+
+ try writer.writeByteNTimes(' ', dtz.indent);
+ try writer.print("%{d}: {} = {s}(", .{
+ my_index, inst.ty, @tagName(inst.tag),
+ });
+ switch (inst.tag) {
+ .alloc,
+ .retvoid,
+ .unreach,
+ .breakpoint,
+ .dbg_stmt,
+ => try writer.writeAll(")\n"),
+
+ .ref,
+ .ret,
+ .bitcast,
+ .not,
+ .is_non_null,
+ .is_null,
+ .is_non_null_ptr,
+ .is_null_ptr,
+ .is_err,
+ .is_err_ptr,
+ .error_to_int,
+ .int_to_error,
+ .ptrtoint,
+ .floatcast,
+ .intcast,
+ .load,
+ .optional_payload,
+ .optional_payload_ptr,
+ .wrap_optional,
+ .wrap_errunion_err,
+ .wrap_errunion_payload,
+ .unwrap_errunion_err,
+ .unwrap_errunion_payload,
+ .unwrap_errunion_payload_ptr,
+ .unwrap_errunion_err_ptr,
+ => {
+ const un_op = inst.cast(Inst.UnOp).?;
+ const kinky = try dtz.writeInst(writer, un_op.operand);
+ if (kinky != null) {
+ try writer.writeAll(") // Instruction does not dominate all uses!\n");
+ } else {
+ try writer.writeAll(")\n");
+ }
+ },
+
+ .add,
+ .addwrap,
+ .sub,
+ .subwrap,
+ .mul,
+ .mulwrap,
+ .cmp_lt,
+ .cmp_lte,
+ .cmp_eq,
+ .cmp_gte,
+ .cmp_gt,
+ .cmp_neq,
+ .store,
+ .bool_and,
+ .bool_or,
+ .bit_and,
+ .bit_or,
+ .xor,
+ => {
+ const bin_op = inst.cast(Inst.BinOp).?;
+
+ const lhs_kinky = try dtz.writeInst(writer, bin_op.lhs);
+ try writer.writeAll(", ");
+ const rhs_kinky = try dtz.writeInst(writer, bin_op.rhs);
+
+ if (lhs_kinky != null or rhs_kinky != null) {
+ try writer.writeAll(") // Instruction does not dominate all uses!");
+ if (lhs_kinky) |lhs| {
+ try writer.print(" %{d}", .{lhs});
+ }
+ if (rhs_kinky) |rhs| {
+ try writer.print(" %{d}", .{rhs});
+ }
+ try writer.writeAll("\n");
+ } else {
+ try writer.writeAll(")\n");
+ }
+ },
+
+ .arg => {
+ const arg = inst.castTag(.arg).?;
+ try writer.print("{s})\n", .{arg.name});
+ },
+
+ .br => {
+ const br = inst.castTag(.br).?;
+
+ const lhs_kinky = try dtz.writeInst(writer, &br.block.base);
+ try writer.writeAll(", ");
+ const rhs_kinky = try dtz.writeInst(writer, br.operand);
+
+ if (lhs_kinky != null or rhs_kinky != null) {
+ try writer.writeAll(") // Instruction does not dominate all uses!");
+ if (lhs_kinky) |lhs| {
+ try writer.print(" %{d}", .{lhs});
+ }
+ if (rhs_kinky) |rhs| {
+ try writer.print(" %{d}", .{rhs});
+ }
+ try writer.writeAll("\n");
+ } else {
+ try writer.writeAll(")\n");
+ }
+ },
+
+ .br_block_flat => {
+ const br_block_flat = inst.castTag(.br_block_flat).?;
+ const block_kinky = try dtz.writeInst(writer, &br_block_flat.block.base);
+ if (block_kinky != null) {
+ try writer.writeAll(", { // Instruction does not dominate all uses!\n");
+ } else {
+ try writer.writeAll(", {\n");
+ }
+
+ const old_indent = dtz.indent;
+ dtz.indent += 2;
+ try dtz.dumpBody(br_block_flat.body, writer);
+ dtz.indent = old_indent;
+
+ try writer.writeByteNTimes(' ', dtz.indent);
+ try writer.writeAll("})\n");
+ },
+
+ .br_void => {
+ const br_void = inst.castTag(.br_void).?;
+ const kinky = try dtz.writeInst(writer, &br_void.block.base);
+ if (kinky) |_| {
+ try writer.writeAll(") // Instruction does not dominate all uses!\n");
+ } else {
+ try writer.writeAll(")\n");
+ }
+ },
+
+ .block => {
+ const block = inst.castTag(.block).?;
+
+ try writer.writeAll("{\n");
+
+ const old_indent = dtz.indent;
+ dtz.indent += 2;
+ try dtz.dumpBody(block.body, writer);
+ dtz.indent = old_indent;
+
+ try writer.writeByteNTimes(' ', dtz.indent);
+ try writer.writeAll("})\n");
+ },
+
+ .condbr => {
+ const condbr = inst.castTag(.condbr).?;
+
+ const condition_kinky = try dtz.writeInst(writer, condbr.condition);
+ if (condition_kinky != null) {
+ try writer.writeAll(", { // Instruction does not dominate all uses!\n");
+ } else {
+ try writer.writeAll(", {\n");
+ }
+
+ const old_indent = dtz.indent;
+ dtz.indent += 2;
+ try dtz.dumpBody(condbr.then_body, writer);
+
+ try writer.writeByteNTimes(' ', old_indent);
+ try writer.writeAll("}, {\n");
+
+ try dtz.dumpBody(condbr.else_body, writer);
+ dtz.indent = old_indent;
+
+ try writer.writeByteNTimes(' ', old_indent);
+ try writer.writeAll("})\n");
+ },
+
+ .switchbr => {
+ const switchbr = inst.castTag(.switchbr).?;
+
+ const condition_kinky = try dtz.writeInst(writer, switchbr.target);
+ if (condition_kinky != null) {
+ try writer.writeAll(", { // Instruction does not dominate all uses!\n");
+ } else {
+ try writer.writeAll(", {\n");
+ }
+ const old_indent = dtz.indent;
+
+ if (switchbr.else_body.instructions.len != 0) {
+ dtz.indent += 2;
+ try dtz.dumpBody(switchbr.else_body, writer);
+
+ try writer.writeByteNTimes(' ', old_indent);
+ try writer.writeAll("}, {\n");
+ dtz.indent = old_indent;
+ }
+ for (switchbr.cases) |case| {
+ dtz.indent += 2;
+ try dtz.dumpBody(case.body, writer);
+
+ try writer.writeByteNTimes(' ', old_indent);
+ try writer.writeAll("}, {\n");
+ dtz.indent = old_indent;
+ }
+
+ try writer.writeByteNTimes(' ', old_indent);
+ try writer.writeAll("})\n");
+ },
+
+ .loop => {
+ const loop = inst.castTag(.loop).?;
+
+ try writer.writeAll("{\n");
+
+ const old_indent = dtz.indent;
+ dtz.indent += 2;
+ try dtz.dumpBody(loop.body, writer);
+ dtz.indent = old_indent;
+
+ try writer.writeByteNTimes(' ', dtz.indent);
+ try writer.writeAll("})\n");
+ },
+
+ .call => {
+ const call = inst.castTag(.call).?;
+
+ const args_kinky = try dtz.allocator.alloc(?usize, call.args.len);
+ defer dtz.allocator.free(args_kinky);
+ std.mem.set(?usize, args_kinky, null);
+ var any_kinky_args = false;
+
+ const func_kinky = try dtz.writeInst(writer, call.func);
+
+ for (call.args) |arg, i| {
+ try writer.writeAll(", ");
+
+ args_kinky[i] = try dtz.writeInst(writer, arg);
+ any_kinky_args = any_kinky_args or args_kinky[i] != null;
+ }
+
+ if (func_kinky != null or any_kinky_args) {
+ try writer.writeAll(") // Instruction does not dominate all uses!");
+ if (func_kinky) |func_index| {
+ try writer.print(" %{d}", .{func_index});
+ }
+ for (args_kinky) |arg_kinky| {
+ if (arg_kinky) |arg_index| {
+ try writer.print(" %{d}", .{arg_index});
+ }
+ }
+ try writer.writeAll("\n");
+ } else {
+ try writer.writeAll(")\n");
+ }
+ },
+
+ .struct_field_ptr => {
+ const struct_field_ptr = inst.castTag(.struct_field_ptr).?;
+ const kinky = try dtz.writeInst(writer, struct_field_ptr.struct_ptr);
+ if (kinky != null) {
+ try writer.print("{d}) // Instruction does not dominate all uses!\n", .{
+ struct_field_ptr.field_index,
+ });
+ } else {
+ try writer.print("{d})\n", .{struct_field_ptr.field_index});
+ }
+ },
+
+ // TODO fill out this debug printing
+ .assembly,
+ .constant,
+ .varptr,
+ => {
+ try writer.writeAll("!TODO!)\n");
+ },
+ }
+ }
+ }
+
+ fn writeInst(dtz: *DumpTzir, writer: std.fs.File.Writer, inst: *Inst) !?usize {
+ if (dtz.partial_inst_table.get(inst)) |operand_index| {
+ try writer.print("%{d}", .{operand_index});
+ return null;
+ } else if (dtz.const_table.get(inst)) |operand_index| {
+ try writer.print("@{d}", .{operand_index});
+ return null;
+ } else if (dtz.inst_table.get(inst)) |operand_index| {
+ try writer.print("%{d}", .{operand_index});
+ return operand_index;
+ } else {
+ try writer.writeAll("!BADREF!");
+ return null;
+ }
+ }
+
+ fn findConst(dtz: *DumpTzir, operand: *Inst) !void {
+ if (operand.tag == .constant) {
+ try dtz.const_table.put(operand, dtz.next_const_index);
+ dtz.next_const_index += 1;
+ }
+ }
+};
diff --git a/src/link/C.zig b/src/link/C.zig
@@ -185,8 +185,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
if (module.global_error_set.size == 0) break :render_errors;
var it = module.global_error_set.iterator();
while (it.next()) |entry| {
- // + 1 because 0 represents no error
- try err_typedef_writer.print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value + 1 });
+ try err_typedef_writer.print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value });
}
try err_typedef_writer.writeByte('\n');
}
diff --git a/src/link/Coff.zig b/src/link/Coff.zig
@@ -34,7 +34,7 @@ pub const base_tag: link.File.Tag = .coff;
const msdos_stub = @embedFile("msdos-stub.bin");
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
-llvm_ir_module: ?*llvm_backend.LLVMIRModule = null,
+llvm_object: ?*llvm_backend.Object = null,
base: link.File,
ptr_width: PtrWidth,
@@ -129,7 +129,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
const self = try createEmpty(allocator, options);
errdefer self.base.destroy();
- self.llvm_ir_module = try llvm_backend.LLVMIRModule.create(allocator, sub_path, options);
+ self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options);
return self;
}
@@ -413,7 +413,7 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Coff {
}
pub fn allocateDeclIndexes(self: *Coff, decl: *Module.Decl) !void {
- if (self.llvm_ir_module) |_| return;
+ if (self.llvm_object) |_| return;
try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1);
@@ -660,7 +660,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void {
defer tracy.end();
if (build_options.have_llvm)
- if (self.llvm_ir_module) |llvm_ir_module| return try llvm_ir_module.updateDecl(module, decl);
+ if (self.llvm_object) |llvm_object| return try llvm_object.updateDecl(module, decl);
const typed_value = decl.typed_value.most_recent.typed_value;
if (typed_value.val.tag() == .extern_fn) {
@@ -720,15 +720,15 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void {
}
pub fn freeDecl(self: *Coff, decl: *Module.Decl) void {
- if (self.llvm_ir_module) |_| return;
+ if (self.llvm_object) |_| return;
// Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
self.freeTextBlock(&decl.link.coff);
self.offset_table_free_list.append(self.base.allocator, decl.link.coff.offset_table_index) catch {};
}
-pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, exports: []const *Module.Export) !void {
- if (self.llvm_ir_module) |_| return;
+pub fn updateDeclExports(self: *Coff, module: *Module, decl: *Module.Decl, exports: []const *Module.Export) !void {
+ if (self.llvm_object) |_| return;
for (exports) |exp| {
if (exp.options.section) |section_name| {
@@ -771,7 +771,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation) !void {
defer tracy.end();
if (build_options.have_llvm)
- if (self.llvm_ir_module) |llvm_ir_module| return try llvm_ir_module.flushModule(comp);
+ if (self.llvm_object) |llvm_object| return try llvm_object.flushModule(comp);
if (self.text_section_size_dirty) {
// Write the new raw size in the .text header
@@ -1308,7 +1308,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
}
pub fn getDeclVAddr(self: *Coff, decl: *const Module.Decl) u64 {
- assert(self.llvm_ir_module == null);
+ assert(self.llvm_object == null);
return self.text_section_virtual_address + decl.link.coff.text_offset;
}
@@ -1318,7 +1318,7 @@ pub fn updateDeclLineNumber(self: *Coff, module: *Module, decl: *Module.Decl) !v
pub fn deinit(self: *Coff) void {
if (build_options.have_llvm)
- if (self.llvm_ir_module) |ir_module| ir_module.deinit(self.base.allocator);
+ if (self.llvm_object) |ir_module| ir_module.deinit(self.base.allocator);
self.text_block_free_list.deinit(self.base.allocator);
self.offset_table.deinit(self.base.allocator);
diff --git a/src/link/Elf.zig b/src/link/Elf.zig
@@ -35,7 +35,7 @@ base: File,
ptr_width: PtrWidth,
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
-llvm_ir_module: ?*llvm_backend.LLVMIRModule = null,
+llvm_object: ?*llvm_backend.Object = null,
/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
/// Same order as in the file.
@@ -232,7 +232,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
const self = try createEmpty(allocator, options);
errdefer self.base.destroy();
- self.llvm_ir_module = try llvm_backend.LLVMIRModule.create(allocator, sub_path, options);
+ self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options);
return self;
}
@@ -299,7 +299,7 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Elf {
pub fn deinit(self: *Elf) void {
if (build_options.have_llvm)
- if (self.llvm_ir_module) |ir_module|
+ if (self.llvm_object) |ir_module|
ir_module.deinit(self.base.allocator);
self.sections.deinit(self.base.allocator);
@@ -318,7 +318,7 @@ pub fn deinit(self: *Elf) void {
}
pub fn getDeclVAddr(self: *Elf, decl: *const Module.Decl) u64 {
- assert(self.llvm_ir_module == null);
+ assert(self.llvm_object == null);
assert(decl.link.elf.local_sym_index != 0);
return self.local_symbols.items[decl.link.elf.local_sym_index].st_value;
}
@@ -438,7 +438,7 @@ fn updateString(self: *Elf, old_str_off: u32, new_name: []const u8) !u32 {
}
pub fn populateMissingMetadata(self: *Elf) !void {
- assert(self.llvm_ir_module == null);
+ assert(self.llvm_object == null);
const small_ptr = switch (self.ptr_width) {
.p32 => true,
@@ -745,7 +745,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void {
defer tracy.end();
if (build_options.have_llvm)
- if (self.llvm_ir_module) |llvm_ir_module| return try llvm_ir_module.flushModule(comp);
+ if (self.llvm_object) |llvm_object| return try llvm_object.flushModule(comp);
// TODO This linker code currently assumes there is only 1 compilation unit and it corresponds to the
// Zig source code.
@@ -2111,7 +2111,7 @@ fn allocateTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, al
}
pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void {
- if (self.llvm_ir_module) |_| return;
+ if (self.llvm_object) |_| return;
if (decl.link.elf.local_sym_index != 0) return;
@@ -2149,7 +2149,7 @@ pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void {
}
pub fn freeDecl(self: *Elf, decl: *Module.Decl) void {
- if (self.llvm_ir_module) |_| return;
+ if (self.llvm_object) |_| return;
// Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
self.freeTextBlock(&decl.link.elf);
@@ -2189,7 +2189,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
defer tracy.end();
if (build_options.have_llvm)
- if (self.llvm_ir_module) |llvm_ir_module| return try llvm_ir_module.updateDecl(module, decl);
+ if (self.llvm_object) |llvm_object| return try llvm_object.updateDecl(module, decl);
const typed_value = decl.typed_value.most_recent.typed_value;
if (typed_value.val.tag() == .extern_fn) {
@@ -2670,10 +2670,10 @@ fn writeDeclDebugInfo(self: *Elf, text_block: *TextBlock, dbg_info_buf: []const
pub fn updateDeclExports(
self: *Elf,
module: *Module,
- decl: *const Module.Decl,
+ decl: *Module.Decl,
exports: []const *Module.Export,
) !void {
- if (self.llvm_ir_module) |_| return;
+ if (self.llvm_object) |_| return;
const tracy = trace(@src());
defer tracy.end();
@@ -2748,7 +2748,7 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec
const tracy = trace(@src());
defer tracy.end();
- if (self.llvm_ir_module) |_| return;
+ if (self.llvm_object) |_| return;
const tree = decl.container.file_scope.tree;
const node_tags = tree.nodes.items(.tag);
@@ -2773,7 +2773,7 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec
}
pub fn deleteExport(self: *Elf, exp: Export) void {
- if (self.llvm_ir_module) |_| return;
+ if (self.llvm_object) |_| return;
const sym_index = exp.sym_index orelse return;
self.global_symbol_free_list.append(self.base.allocator, sym_index) catch {};
diff --git a/src/link/MachO.zig b/src/link/MachO.zig
@@ -1340,7 +1340,7 @@ pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.D
pub fn updateDeclExports(
self: *MachO,
module: *Module,
- decl: *const Module.Decl,
+ decl: *Module.Decl,
exports: []const *Module.Export,
) !void {
const tracy = trace(@src());
diff --git a/src/main.zig b/src/main.zig
@@ -1487,7 +1487,7 @@ fn buildOutputType(
for (diags.arch.?.allCpuModels()) |cpu| {
help_text.writer().print(" {s}\n", .{cpu.name}) catch break :help;
}
- std.log.info("Available CPUs for architecture '{s}': {s}", .{
+ std.log.info("Available CPUs for architecture '{s}':\n{s}", .{
@tagName(diags.arch.?), help_text.items,
});
}
@@ -1499,7 +1499,7 @@ fn buildOutputType(
for (diags.arch.?.allFeaturesList()) |feature| {
help_text.writer().print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help;
}
- std.log.info("Available CPU features for architecture '{s}': {s}", .{
+ std.log.info("Available CPU features for architecture '{s}':\n{s}", .{
@tagName(diags.arch.?), help_text.items,
});
}
@@ -1750,15 +1750,12 @@ fn buildOutputType(
}
const self_exe_path = try fs.selfExePathAlloc(arena);
- var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir|
- .{
- .path = lib_dir,
- .handle = try fs.cwd().openDir(lib_dir, .{}),
- }
- else
- introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
- fatal("unable to find zig installation directory: {s}", .{@errorName(err)});
- };
+ var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir| .{
+ .path = lib_dir,
+ .handle = try fs.cwd().openDir(lib_dir, .{}),
+ } else introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
+ fatal("unable to find zig installation directory: {s}", .{@errorName(err)});
+ };
defer zig_lib_directory.handle.close();
var thread_pool: ThreadPool = undefined;
@@ -2115,12 +2112,37 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi
} else switch (hook) {
.none => {},
.print => |bin_path| try io.getStdOut().writer().print("{s}\n", .{bin_path}),
- .update => |full_path| _ = try comp.bin_file.options.emit.?.directory.handle.updateFile(
- comp.bin_file.options.emit.?.sub_path,
- fs.cwd(),
- full_path,
- .{},
- ),
+ .update => |full_path| {
+ const bin_sub_path = comp.bin_file.options.emit.?.sub_path;
+ const cwd = fs.cwd();
+ const cache_dir = comp.bin_file.options.emit.?.directory.handle;
+ _ = try cache_dir.updateFile(bin_sub_path, cwd, full_path, .{});
+
+ // If a .pdb file is part of the expected output, we must also copy
+ // it into place here.
+ const coff_or_pe = switch (comp.bin_file.options.object_format) {
+ .coff, .pe => true,
+ else => false,
+ };
+ const have_pdb = coff_or_pe and !comp.bin_file.options.strip;
+ if (have_pdb) {
+ // Replace `.out` or `.exe` with `.pdb` on both the source and destination
+ const src_bin_ext = fs.path.extension(bin_sub_path);
+ const dst_bin_ext = fs.path.extension(full_path);
+
+ const src_pdb_path = try std.fmt.allocPrint(gpa, "{s}.pdb", .{
+ bin_sub_path[0 .. bin_sub_path.len - src_bin_ext.len],
+ });
+ defer gpa.free(src_pdb_path);
+
+ const dst_pdb_path = try std.fmt.allocPrint(gpa, "{s}.pdb", .{
+ full_path[0 .. full_path.len - dst_bin_ext.len],
+ });
+ defer gpa.free(dst_pdb_path);
+
+ _ = try cache_dir.updateFile(src_pdb_path, cwd, dst_pdb_path, .{});
+ }
+ },
}
}
@@ -2461,15 +2483,12 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
}
}
- var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir|
- .{
- .path = lib_dir,
- .handle = try fs.cwd().openDir(lib_dir, .{}),
- }
- else
- introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
- fatal("unable to find zig installation directory: {s}", .{@errorName(err)});
- };
+ var zig_lib_directory: Compilation.Directory = if (override_lib_dir) |lib_dir| .{
+ .path = lib_dir,
+ .handle = try fs.cwd().openDir(lib_dir, .{}),
+ } else introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| {
+ fatal("unable to find zig installation directory: {s}", .{@errorName(err)});
+ };
defer zig_lib_directory.handle.close();
const std_special = "std" ++ fs.path.sep_str ++ "special";
@@ -3281,8 +3300,7 @@ pub const ClangArgIterator = struct {
self.zig_equivalent = clang_arg.zig_equivalent;
break :find_clang_arg;
},
- }
- else {
+ } else {
fatal("Unknown Clang option: '{s}'", .{arg});
}
}
diff --git a/src/register_manager.zig b/src/register_manager.zig
@@ -0,0 +1,228 @@
+const std = @import("std");
+const math = std.math;
+const assert = std.debug.assert;
+const Allocator = std.mem.Allocator;
+const ir = @import("ir.zig");
+const Type = @import("type.zig").Type;
+const Module = @import("Module.zig");
+const LazySrcLoc = Module.LazySrcLoc;
+
+const log = std.log.scoped(.register_manager);
+
+pub fn RegisterManager(
+ comptime Function: type,
+ comptime Register: type,
+ comptime callee_preserved_regs: []const Register,
+) type {
+ return struct {
+ /// The key must be canonical register.
+ registers: std.AutoHashMapUnmanaged(Register, *ir.Inst) = .{},
+ free_registers: FreeRegInt = math.maxInt(FreeRegInt),
+ /// Tracks all registers allocated in the course of this function
+ allocated_registers: FreeRegInt = 0,
+
+ const Self = @This();
+
+ /// An integer whose bits represent all the registers and whether they are free.
+ const FreeRegInt = std.meta.Int(.unsigned, callee_preserved_regs.len);
+ const ShiftInt = math.Log2Int(FreeRegInt);
+
+ fn getFunction(self: *Self) *Function {
+ return @fieldParentPtr(Function, "register_manager", self);
+ }
+
+ pub fn deinit(self: *Self, allocator: *Allocator) void {
+ self.registers.deinit(allocator);
+ }
+
+ fn markRegUsed(self: *Self, reg: Register) void {
+ if (FreeRegInt == u0) return;
+ const index = reg.allocIndex() orelse return;
+ const shift = @intCast(ShiftInt, index);
+ const mask = @as(FreeRegInt, 1) << shift;
+ self.free_registers &= ~mask;
+ self.allocated_registers |= mask;
+ }
+
+ fn markRegFree(self: *Self, reg: Register) void {
+ if (FreeRegInt == u0) return;
+ const index = reg.allocIndex() orelse return;
+ const shift = @intCast(ShiftInt, index);
+ self.free_registers |= @as(FreeRegInt, 1) << shift;
+ }
+
+ /// Returns whether this register was allocated in the course
+ /// of this function
+ pub fn isRegAllocated(self: Self, reg: Register) bool {
+ if (FreeRegInt == u0) return false;
+ const index = reg.allocIndex() orelse return false;
+ const shift = @intCast(ShiftInt, index);
+ return self.allocated_registers & @as(FreeRegInt, 1) << shift != 0;
+ }
+
+ /// Before calling, must ensureCapacity + 1 on self.registers.
+ /// Returns `null` if all registers are allocated.
+ pub fn tryAllocReg(self: *Self, inst: *ir.Inst) ?Register {
+ const free_index = @ctz(FreeRegInt, self.free_registers);
+ if (free_index >= callee_preserved_regs.len) {
+ return null;
+ }
+
+ // This is necessary because the return type of @ctz is 1
+ // bit longer than ShiftInt if callee_preserved_regs.len
+ // is a power of two. This int cast is always safe because
+ // free_index < callee_preserved_regs.len
+ const shift = @intCast(ShiftInt, free_index);
+ const mask = @as(FreeRegInt, 1) << shift;
+ self.free_registers &= ~mask;
+ self.allocated_registers |= mask;
+
+ const reg = callee_preserved_regs[free_index];
+ self.registers.putAssumeCapacityNoClobber(reg, inst);
+ log.debug("alloc {} => {*}", .{ reg, inst });
+ return reg;
+ }
+
+ /// Before calling, must ensureCapacity + 1 on self.registers.
+ pub fn allocReg(self: *Self, inst: *ir.Inst) !Register {
+ return self.tryAllocReg(inst) orelse b: {
+ // We'll take over the first register. Move the instruction that was previously
+ // there to a stack allocation.
+ const reg = callee_preserved_regs[0];
+ const regs_entry = self.registers.getEntry(reg).?;
+ const spilled_inst = regs_entry.value;
+ regs_entry.value = inst;
+ try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+
+ break :b reg;
+ };
+ }
+
+ /// Does not track the register.
+ /// Returns `null` if all registers are allocated.
+ pub fn findUnusedReg(self: *Self) ?Register {
+ const free_index = @ctz(FreeRegInt, self.free_registers);
+ if (free_index >= callee_preserved_regs.len) {
+ return null;
+ }
+ return callee_preserved_regs[free_index];
+ }
+
+ /// Does not track the register.
+ pub fn allocRegWithoutTracking(self: *Self) !Register {
+ return self.findUnusedReg() orelse b: {
+ // We'll take over the first register. Move the instruction that was previously
+ // there to a stack allocation.
+ const reg = callee_preserved_regs[0];
+ const regs_entry = self.registers.remove(reg).?;
+ const spilled_inst = regs_entry.value;
+ try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+
+ break :b reg;
+ };
+ }
+
+ pub fn getRegAssumeFree(self: *Self, reg: Register, inst: *ir.Inst) !void {
+ try self.registers.putNoClobber(self.getFunction().gpa, reg, inst);
+ self.markRegUsed(reg);
+ }
+
+ pub fn freeReg(self: *Self, reg: Register) void {
+ _ = self.registers.remove(reg);
+ self.markRegFree(reg);
+ }
+ };
+}
+
+const MockRegister = enum(u2) {
+ r0, r1, r2, r3,
+
+ pub fn allocIndex(self: MockRegister) ?u2 {
+ inline for (mock_callee_preserved_regs) |cpreg, i| {
+ if (self == cpreg) return i;
+ }
+ return null;
+ }
+};
+
+const mock_callee_preserved_regs = [_]MockRegister{ .r2, .r3 };
+
+const MockFunction = struct {
+ allocator: *Allocator,
+ register_manager: RegisterManager(Self, MockRegister, &mock_callee_preserved_regs) = .{},
+ spilled: std.ArrayListUnmanaged(MockRegister) = .{},
+
+ const Self = @This();
+
+ pub fn deinit(self: *Self) void {
+ self.register_manager.deinit(self.allocator);
+ self.spilled.deinit(self.allocator);
+ }
+
+ pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: MockRegister, inst: *ir.Inst) !void {
+ try self.spilled.append(self.allocator, reg);
+ }
+};
+
+test "tryAllocReg: no spilling" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ var mock_instruction = ir.Inst{
+ .tag = .breakpoint,
+ .ty = Type.initTag(.void),
+ .src = .unneeded,
+ };
+
+ std.testing.expect(!function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(!function.register_manager.isRegAllocated(.r3));
+
+ try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2);
+ std.testing.expectEqual(@as(?MockRegister, .r2), function.register_manager.tryAllocReg(&mock_instruction));
+ std.testing.expectEqual(@as(?MockRegister, .r3), function.register_manager.tryAllocReg(&mock_instruction));
+ std.testing.expectEqual(@as(?MockRegister, null), function.register_manager.tryAllocReg(&mock_instruction));
+
+ std.testing.expect(function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(function.register_manager.isRegAllocated(.r3));
+
+ function.register_manager.freeReg(.r2);
+ function.register_manager.freeReg(.r3);
+
+ std.testing.expect(function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(function.register_manager.isRegAllocated(.r3));
+}
+
+test "allocReg: spilling" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ var mock_instruction = ir.Inst{
+ .tag = .breakpoint,
+ .ty = Type.initTag(.void),
+ .src = .unneeded,
+ };
+
+ std.testing.expect(!function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(!function.register_manager.isRegAllocated(.r3));
+
+ try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2);
+ std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction));
+ std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction));
+
+ // Spill a register
+ std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction));
+ std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);
+
+ // No spilling necessary
+ function.register_manager.freeReg(.r3);
+ std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction));
+ std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);
+}
diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp
@@ -8723,7 +8723,9 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS
assert(async_frame_type->id == ZigTypeIdFnFrame);
assert(field_type->id == ZigTypeIdFn);
resolve_llvm_types_fn(g, async_frame_type->data.frame.fn);
- llvm_type = LLVMPointerType(async_frame_type->data.frame.fn->raw_type_ref, 0);
+
+ const unsigned addrspace = ZigLLVMDataLayoutGetProgramAddressSpace(g->target_data_ref);
+ llvm_type = LLVMPointerType(async_frame_type->data.frame.fn->raw_type_ref, addrspace);
} else {
llvm_type = get_llvm_type(g, field_type);
}
diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp
@@ -7641,12 +7641,7 @@ static IrInstSrc *ir_gen_fn_call(IrBuilderSrc *irb, Scope *scope, AstNode *node,
bool is_nosuspend = get_scope_nosuspend(scope) != nullptr;
CallModifier modifier = node->data.fn_call_expr.modifier;
- if (is_nosuspend) {
- if (modifier == CallModifierAsync) {
- add_node_error(irb->codegen, node,
- buf_sprintf("async call in nosuspend scope"));
- return irb->codegen->invalid_inst_src;
- }
+ if (is_nosuspend && modifier != CallModifierAsync) {
modifier = CallModifierNoSuspend;
}
@@ -10129,10 +10124,6 @@ static IrInstSrc *ir_gen_fn_proto(IrBuilderSrc *irb, Scope *parent_scope, AstNod
static IrInstSrc *ir_gen_resume(IrBuilderSrc *irb, Scope *scope, AstNode *node) {
assert(node->type == NodeTypeResume);
- if (get_scope_nosuspend(scope) != nullptr) {
- add_node_error(irb->codegen, node, buf_sprintf("resume in nosuspend scope"));
- return irb->codegen->invalid_inst_src;
- }
IrInstSrc *target_inst = ir_gen_node_extra(irb, node->data.resume_expr.expr, scope, LValPtr, nullptr);
if (target_inst == irb->codegen->invalid_inst_src)
diff --git a/src/translate_c.zig b/src/translate_c.zig
@@ -4343,7 +4343,7 @@ fn isZigPrimitiveType(name: []const u8) bool {
}
return true;
}
- return @import("astgen.zig").simple_types.has(name);
+ return @import("AstGen.zig").simple_types.has(name);
}
const MacroCtx = struct {
diff --git a/src/type.zig b/src/type.zig
@@ -4,6 +4,7 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Target = std.Target;
const Module = @import("Module.zig");
+const log = std.log.scoped(.Type);
/// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication.
/// It's important for this type to be small.
@@ -92,11 +93,9 @@ pub const Type = extern union {
.anyerror_void_error_union, .error_union => return .ErrorUnion,
- .anyframe_T, .@"anyframe" => return .AnyFrame,
-
- .@"struct", .empty_struct => return .Struct,
- .@"enum" => return .Enum,
- .@"union" => return .Union,
+ .empty_struct => return .Struct,
+ .empty_struct_literal => return .Struct,
+ .@"struct" => return .Struct,
.var_args_param => unreachable, // can be any type
}
@@ -173,6 +172,125 @@ pub const Type = extern union {
};
}
+ pub fn ptrInfo(self: Type) Payload.Pointer {
+ switch (self.tag()) {
+ .single_const_pointer_to_comptime_int => return .{ .data = .{
+ .pointee_type = Type.initTag(.comptime_int),
+ .sentinel = null,
+ .@"align" = 0,
+ .bit_offset = 0,
+ .host_size = 0,
+ .@"allowzero" = false,
+ .mutable = false,
+ .@"volatile" = false,
+ .size = .One,
+ } },
+ .const_slice_u8 => return .{ .data = .{
+ .pointee_type = Type.initTag(.u8),
+ .sentinel = null,
+ .@"align" = 0,
+ .bit_offset = 0,
+ .host_size = 0,
+ .@"allowzero" = false,
+ .mutable = false,
+ .@"volatile" = false,
+ .size = .Slice,
+ } },
+ .single_const_pointer => return .{ .data = .{
+ .pointee_type = self.castPointer().?.data,
+ .sentinel = null,
+ .@"align" = 0,
+ .bit_offset = 0,
+ .host_size = 0,
+ .@"allowzero" = false,
+ .mutable = false,
+ .@"volatile" = false,
+ .size = .One,
+ } },
+ .single_mut_pointer => return .{ .data = .{
+ .pointee_type = self.castPointer().?.data,
+ .sentinel = null,
+ .@"align" = 0,
+ .bit_offset = 0,
+ .host_size = 0,
+ .@"allowzero" = false,
+ .mutable = true,
+ .@"volatile" = false,
+ .size = .One,
+ } },
+ .many_const_pointer => return .{ .data = .{
+ .pointee_type = self.castPointer().?.data,
+ .sentinel = null,
+ .@"align" = 0,
+ .bit_offset = 0,
+ .host_size = 0,
+ .@"allowzero" = false,
+ .mutable = false,
+ .@"volatile" = false,
+ .size = .Many,
+ } },
+ .many_mut_pointer => return .{ .data = .{
+ .pointee_type = self.castPointer().?.data,
+ .sentinel = null,
+ .@"align" = 0,
+ .bit_offset = 0,
+ .host_size = 0,
+ .@"allowzero" = false,
+ .mutable = true,
+ .@"volatile" = false,
+ .size = .Many,
+ } },
+ .c_const_pointer => return .{ .data = .{
+ .pointee_type = self.castPointer().?.data,
+ .sentinel = null,
+ .@"align" = 0,
+ .bit_offset = 0,
+ .host_size = 0,
+ .@"allowzero" = false,
+ .mutable = false,
+ .@"volatile" = false,
+ .size = .C,
+ } },
+ .c_mut_pointer => return .{ .data = .{
+ .pointee_type = self.castPointer().?.data,
+ .sentinel = null,
+ .@"align" = 0,
+ .bit_offset = 0,
+ .host_size = 0,
+ .@"allowzero" = false,
+ .mutable = true,
+ .@"volatile" = false,
+ .size = .C,
+ } },
+ .const_slice => return .{ .data = .{
+ .pointee_type = self.castPointer().?.data,
+ .sentinel = null,
+ .@"align" = 0,
+ .bit_offset = 0,
+ .host_size = 0,
+ .@"allowzero" = false,
+ .mutable = false,
+ .@"volatile" = false,
+ .size = .Slice,
+ } },
+ .mut_slice => return .{ .data = .{
+ .pointee_type = self.castPointer().?.data,
+ .sentinel = null,
+ .@"align" = 0,
+ .bit_offset = 0,
+ .host_size = 0,
+ .@"allowzero" = false,
+ .mutable = true,
+ .@"volatile" = false,
+ .size = .Slice,
+ } },
+
+ .pointer => return self.castTag(.pointer).?.*,
+
+ else => unreachable,
+ }
+ }
+
pub fn eql(a: Type, b: Type) bool {
// As a shortcut, if the small tags / addresses match, we're done.
if (a.tag_if_small_enough == b.tag_if_small_enough)
@@ -195,25 +313,38 @@ pub const Type = extern union {
return a.elemType().eql(b.elemType());
},
.Pointer => {
- // Hot path for common case:
- if (a.castPointer()) |a_payload| {
- if (b.castPointer()) |b_payload| {
- return a.tag() == b.tag() and eql(a_payload.data, b_payload.data);
- }
- }
- const is_slice_a = isSlice(a);
- const is_slice_b = isSlice(b);
- if (is_slice_a != is_slice_b)
+ const info_a = a.ptrInfo().data;
+ const info_b = b.ptrInfo().data;
+ if (!info_a.pointee_type.eql(info_b.pointee_type))
return false;
-
- const ptr_size_a = ptrSize(a);
- const ptr_size_b = ptrSize(b);
- if (ptr_size_a != ptr_size_b)
+ if (info_a.size != info_b.size)
+ return false;
+ if (info_a.mutable != info_b.mutable)
+ return false;
+ if (info_a.@"volatile" != info_b.@"volatile")
+ return false;
+ if (info_a.@"allowzero" != info_b.@"allowzero")
+ return false;
+ if (info_a.bit_offset != info_b.bit_offset)
+ return false;
+ if (info_a.host_size != info_b.host_size)
return false;
- std.debug.panic("TODO implement more pointer Type equality comparison: {} and {}", .{
- a, b,
- });
+ const sentinel_a = info_a.sentinel;
+ const sentinel_b = info_b.sentinel;
+ if (sentinel_a) |sa| {
+ if (sentinel_b) |sb| {
+ if (!sa.eql(sb))
+ return false;
+ } else {
+ return false;
+ }
+ } else {
+ if (sentinel_b != null)
+ return false;
+ }
+
+ return true;
},
.Int => {
// Detect that e.g. u64 != usize, even if the bits match on a particular target.
@@ -399,10 +530,10 @@ pub const Type = extern union {
.const_slice_u8,
.enum_literal,
.anyerror_void_error_union,
- .@"anyframe",
.inferred_alloc_const,
.inferred_alloc_mut,
.var_args_param,
+ .empty_struct_literal,
=> unreachable,
.array_u8,
@@ -420,7 +551,6 @@ pub const Type = extern union {
.optional,
.optional_single_mut_pointer,
.optional_single_const_pointer,
- .anyframe_T,
=> return self.copyPayloadShallow(allocator, Payload.ElemType),
.int_signed,
@@ -480,13 +610,10 @@ pub const Type = extern union {
.payload = try payload.payload.copy(allocator),
});
},
- .error_set => return self.copyPayloadShallow(allocator, Payload.Decl),
+ .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet),
.error_set_single => return self.copyPayloadShallow(allocator, Payload.Name),
.empty_struct => return self.copyPayloadShallow(allocator, Payload.ContainerScope),
-
- .@"enum" => return self.copyPayloadShallow(allocator, Payload.Enum),
.@"struct" => return self.copyPayloadShallow(allocator, Payload.Struct),
- .@"union" => return self.copyPayloadShallow(allocator, Payload.Union),
.@"opaque" => return self.copyPayloadShallow(allocator, Payload.Opaque),
}
}
@@ -549,9 +676,8 @@ pub const Type = extern union {
.@"null" => return out_stream.writeAll("@Type(.Null)"),
.@"undefined" => return out_stream.writeAll("@Type(.Undefined)"),
- // TODO this should print the structs name
- .empty_struct => return out_stream.writeAll("struct {}"),
- .@"anyframe" => return out_stream.writeAll("anyframe"),
+ .empty_struct, .empty_struct_literal => return out_stream.writeAll("struct {}"),
+ .@"struct" => return out_stream.writeAll("(struct)"),
.anyerror_void_error_union => return out_stream.writeAll("anyerror!void"),
.const_slice_u8 => return out_stream.writeAll("[]const u8"),
.fn_noreturn_no_args => return out_stream.writeAll("fn() noreturn"),
@@ -579,12 +705,6 @@ pub const Type = extern union {
continue;
},
- .anyframe_T => {
- const return_type = ty.castTag(.anyframe_T).?.data;
- try out_stream.print("anyframe->", .{});
- ty = return_type;
- continue;
- },
.array_u8 => {
const len = ty.castTag(.array_u8).?.data;
return out_stream.print("[{d}]u8", .{len});
@@ -715,8 +835,8 @@ pub const Type = extern union {
continue;
},
.error_set => {
- const decl = ty.castTag(.error_set).?.data;
- return out_stream.writeAll(std.mem.spanZ(decl.name));
+ const error_set = ty.castTag(.error_set).?.data;
+ return out_stream.writeAll(std.mem.spanZ(error_set.owner_decl.name));
},
.error_set_single => {
const name = ty.castTag(.error_set_single).?.data;
@@ -725,9 +845,6 @@ pub const Type = extern union {
.inferred_alloc_const => return out_stream.writeAll("(inferred_alloc_const)"),
.inferred_alloc_mut => return out_stream.writeAll("(inferred_alloc_mut)"),
// TODO use declaration name
- .@"enum" => return out_stream.writeAll("enum {}"),
- .@"struct" => return out_stream.writeAll("struct {}"),
- .@"union" => return out_stream.writeAll("union {}"),
.@"opaque" => return out_stream.writeAll("opaque {}"),
}
unreachable;
@@ -822,12 +939,22 @@ pub const Type = extern union {
.optional,
.optional_single_mut_pointer,
.optional_single_const_pointer,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
=> true,
+
+ .@"struct" => {
+ // TODO introduce lazy value mechanism
+ const struct_obj = self.castTag(.@"struct").?.data;
+ for (struct_obj.fields.entries.items) |entry| {
+ if (entry.value.ty.hasCodeGenBits())
+ return true;
+ } else {
+ return false;
+ }
+ },
+
// TODO lazy types
.array => self.elemType().hasCodeGenBits() and self.arrayLen() != 0,
.array_u8 => self.arrayLen() != 0,
@@ -839,10 +966,6 @@ pub const Type = extern union {
return payload.error_set.hasCodeGenBits() or payload.payload.hasCodeGenBits();
},
- .@"enum" => @panic("TODO"),
- .@"struct" => @panic("TODO"),
- .@"union" => @panic("TODO"),
-
.c_void,
.void,
.type,
@@ -853,6 +976,7 @@ pub const Type = extern union {
.@"undefined",
.enum_literal,
.empty_struct,
+ .empty_struct_literal,
.@"opaque",
=> false,
@@ -863,7 +987,39 @@ pub const Type = extern union {
}
pub fn isNoReturn(self: Type) bool {
- return self.zigTypeTag() == .NoReturn;
+ const definitely_correct_result = self.zigTypeTag() == .NoReturn;
+ const fast_result = self.tag_if_small_enough == @enumToInt(Tag.noreturn);
+ assert(fast_result == definitely_correct_result);
+ return fast_result;
+ }
+
+ pub fn ptrAlignment(self: Type, target: Target) u32 {
+ switch (self.tag()) {
+ .single_const_pointer,
+ .single_mut_pointer,
+ .many_const_pointer,
+ .many_mut_pointer,
+ .c_const_pointer,
+ .c_mut_pointer,
+ .const_slice,
+ .mut_slice,
+ .optional_single_const_pointer,
+ .optional_single_mut_pointer,
+ => return self.cast(Payload.ElemType).?.data.abiAlignment(target),
+
+ .const_slice_u8 => return 1,
+
+ .pointer => {
+ const ptr_info = self.castTag(.pointer).?.data;
+ if (ptr_info.@"align" != 0) {
+ return ptr_info.@"align";
+ } else {
+ return ptr_info.pointee_type.abiAlignment();
+ }
+ },
+
+ else => unreachable,
+ }
}
/// Asserts that hasCodeGenBits() is true.
@@ -907,17 +1063,9 @@ pub const Type = extern union {
.mut_slice,
.optional_single_const_pointer,
.optional_single_mut_pointer,
- .@"anyframe",
- .anyframe_T,
+ .pointer,
=> return @divExact(target.cpu.arch.ptrBitWidth(), 8),
- .pointer => {
- const payload = self.castTag(.pointer).?.data;
-
- if (payload.@"align" != 0) return payload.@"align";
- return @divExact(target.cpu.arch.ptrBitWidth(), 8);
- },
-
.c_short => return @divExact(CType.short.sizeInBits(target), 8),
.c_ushort => return @divExact(CType.ushort.sizeInBits(target), 8),
.c_int => return @divExact(CType.int.sizeInBits(target), 8),
@@ -967,9 +1115,9 @@ pub const Type = extern union {
@panic("TODO abiAlignment error union");
},
- .@"enum" => self.cast(Payload.Enum).?.abiAlignment(target),
- .@"struct" => @panic("TODO"),
- .@"union" => @panic("TODO"),
+ .@"struct" => {
+ @panic("TODO abiAlignment struct");
+ },
.c_void,
.void,
@@ -981,6 +1129,7 @@ pub const Type = extern union {
.@"undefined",
.enum_literal,
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
.@"opaque",
@@ -1008,11 +1157,16 @@ pub const Type = extern union {
.enum_literal => unreachable,
.single_const_pointer_to_comptime_int => unreachable,
.empty_struct => unreachable,
+ .empty_struct_literal => unreachable,
.inferred_alloc_const => unreachable,
.inferred_alloc_mut => unreachable,
.@"opaque" => unreachable,
.var_args_param => unreachable,
+ .@"struct" => {
+ @panic("TODO abiSize struct");
+ },
+
.u8,
.i8,
.bool,
@@ -1038,7 +1192,7 @@ pub const Type = extern union {
.i64, .u64 => return 8,
.u128, .i128 => return 16,
- .@"anyframe", .anyframe_T, .isize, .usize => return @divExact(target.cpu.arch.ptrBitWidth(), 8),
+ .isize, .usize => return @divExact(target.cpu.arch.ptrBitWidth(), 8),
.const_slice,
.mut_slice,
@@ -1119,10 +1273,6 @@ pub const Type = extern union {
}
@panic("TODO abiSize error union");
},
-
- .@"enum" => @panic("TODO"),
- .@"struct" => @panic("TODO"),
- .@"union" => @panic("TODO"),
};
}
@@ -1186,15 +1336,12 @@ pub const Type = extern union {
.const_slice,
.mut_slice,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
- .empty_struct,
- .@"enum",
.@"struct",
- .@"union",
+ .empty_struct,
+ .empty_struct_literal,
.@"opaque",
.var_args_param,
=> false,
@@ -1264,16 +1411,13 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
.empty_struct,
- .@"enum",
- .@"struct",
- .@"union",
+ .empty_struct_literal,
.@"opaque",
+ .@"struct",
.var_args_param,
=> unreachable,
@@ -1361,17 +1505,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
.@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> false,
@@ -1442,17 +1583,14 @@ pub const Type = extern union {
.enum_literal,
.mut_slice,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
.@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> false,
@@ -1532,17 +1670,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
.@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> false,
@@ -1617,17 +1752,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
.@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> false,
@@ -1689,7 +1821,11 @@ pub const Type = extern union {
.ErrorUnion => ty = ty.errorUnionChild(),
.Fn => @panic("TODO fn isValidVarType"),
- .Struct => @panic("TODO struct isValidVarType"),
+ .Struct => {
+ // TODO this is not always correct; introduce lazy value mechanism
+ // and here we need to force a resolve of "type requires comptime".
+ return true;
+ },
.Union => @panic("TODO union isValidVarType"),
};
}
@@ -1744,17 +1880,14 @@ pub const Type = extern union {
.optional_single_mut_pointer => unreachable,
.enum_literal => unreachable,
.error_union => unreachable,
- .@"anyframe" => unreachable,
- .anyframe_T => unreachable,
.anyerror_void_error_union => unreachable,
.error_set => unreachable,
.error_set_single => unreachable,
+ .@"struct" => unreachable,
.empty_struct => unreachable,
+ .empty_struct_literal => unreachable,
.inferred_alloc_const => unreachable,
.inferred_alloc_mut => unreachable,
- .@"enum" => unreachable,
- .@"struct" => unreachable,
- .@"union" => unreachable,
.@"opaque" => unreachable,
.var_args_param => unreachable,
@@ -1897,17 +2030,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> unreachable,
@@ -1972,17 +2102,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> unreachable,
@@ -2062,17 +2189,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> false,
@@ -2148,17 +2272,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> false,
@@ -2220,17 +2341,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> unreachable,
@@ -2320,17 +2438,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> false,
@@ -2441,17 +2556,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> unreachable,
@@ -2528,17 +2640,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> unreachable,
@@ -2614,17 +2723,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> unreachable,
@@ -2700,17 +2806,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> unreachable,
@@ -2783,17 +2886,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> unreachable,
@@ -2866,17 +2966,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> unreachable,
@@ -2949,17 +3046,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> false,
@@ -3016,8 +3110,6 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.anyerror_void_error_union,
- .anyframe_T,
- .@"anyframe",
.error_union,
.error_set,
.error_set_single,
@@ -3025,11 +3117,12 @@ pub const Type = extern union {
.var_args_param,
=> return null,
- .@"enum" => @panic("TODO onePossibleValue enum"),
- .@"struct" => @panic("TODO onePossibleValue struct"),
- .@"union" => @panic("TODO onePossibleValue union"),
+ .@"struct" => {
+ log.warn("TODO implement Type.onePossibleValue for structs", .{});
+ return null;
+ },
- .empty_struct => return Value.initTag(.empty_struct_value),
+ .empty_struct, .empty_struct_literal => return Value.initTag(.empty_struct_value),
.void => return Value.initTag(.void_value),
.noreturn => return Value.initTag(.unreachable_value),
.@"null" => return Value.initTag(.null_value),
@@ -3128,17 +3221,14 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
+ .@"struct",
.empty_struct,
+ .empty_struct_literal,
.inferred_alloc_const,
.inferred_alloc_mut,
- .@"enum",
- .@"struct",
- .@"union",
.@"opaque",
.var_args_param,
=> return false,
@@ -3220,8 +3310,6 @@ pub const Type = extern union {
.optional_single_const_pointer,
.enum_literal,
.error_union,
- .@"anyframe",
- .anyframe_T,
.anyerror_void_error_union,
.error_set,
.error_set_single,
@@ -3231,13 +3319,12 @@ pub const Type = extern union {
.inferred_alloc_const,
.inferred_alloc_mut,
.var_args_param,
+ .empty_struct_literal,
=> unreachable,
+ .@"struct" => &self.castTag(.@"struct").?.data.container,
.empty_struct => self.castTag(.empty_struct).?.data,
- .@"enum" => &self.castTag(.@"enum").?.scope,
- .@"struct" => &self.castTag(.@"struct").?.scope,
- .@"union" => &self.castTag(.@"union").?.scope,
- .@"opaque" => &self.castTag(.@"opaque").?.scope,
+ .@"opaque" => &self.castTag(.@"opaque").?.data,
};
}
@@ -3296,6 +3383,10 @@ pub const Type = extern union {
}
}
+ pub fn isExhaustiveEnum(ty: Type) bool {
+ return false; // TODO
+ }
+
/// This enum does not directly correspond to `std.builtin.TypeId` because
/// it has extra enum tags in it, as a way of using less memory. For example,
/// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types
@@ -3346,11 +3437,12 @@ pub const Type = extern union {
fn_ccc_void_no_args,
single_const_pointer_to_comptime_int,
anyerror_void_error_union,
- @"anyframe",
const_slice_u8,
/// This is a special type for variadic parameters of a function call.
/// Casts to it will validate that the type can be passed to a c calling convetion function.
var_args_param,
+ /// Same as `empty_struct` except it has an empty namespace.
+ empty_struct_literal,
/// This is a special value that tracks a set of types that have been stored
/// to an inferred allocation. It does not support most of the normal type queries.
/// However it does respond to `isConstPtr`, `ptrSize`, `zigTypeTag`, etc.
@@ -3379,14 +3471,11 @@ pub const Type = extern union {
optional_single_mut_pointer,
optional_single_const_pointer,
error_union,
- anyframe_T,
error_set,
error_set_single,
empty_struct,
- @"enum",
- @"struct",
- @"union",
@"opaque",
+ @"struct",
pub const last_no_payload_tag = Tag.inferred_alloc_const;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
@@ -3435,11 +3524,11 @@ pub const Type = extern union {
.fn_ccc_void_no_args,
.single_const_pointer_to_comptime_int,
.anyerror_void_error_union,
- .@"anyframe",
.const_slice_u8,
.inferred_alloc_const,
.inferred_alloc_mut,
.var_args_param,
+ .empty_struct_literal,
=> @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
.array_u8,
@@ -3457,25 +3546,23 @@ pub const Type = extern union {
.optional,
.optional_single_mut_pointer,
.optional_single_const_pointer,
- .anyframe_T,
=> Payload.ElemType,
.int_signed,
.int_unsigned,
=> Payload.Bits,
+ .error_set => Payload.ErrorSet,
+
.array => Payload.Array,
.array_sentinel => Payload.ArraySentinel,
.pointer => Payload.Pointer,
.function => Payload.Function,
.error_union => Payload.ErrorUnion,
- .error_set => Payload.Decl,
.error_set_single => Payload.Name,
- .empty_struct => Payload.ContainerScope,
- .@"enum" => Payload.Enum,
- .@"struct" => Payload.Struct,
- .@"union" => Payload.Union,
.@"opaque" => Payload.Opaque,
+ .@"struct" => Payload.Struct,
+ .empty_struct => Payload.ContainerScope,
};
}
@@ -3550,6 +3637,13 @@ pub const Type = extern union {
},
};
+ pub const ErrorSet = struct {
+ pub const base_tag = Tag.error_set;
+
+ base: Payload = Payload{ .tag = base_tag },
+ data: *Module.ErrorSet,
+ };
+
pub const Pointer = struct {
pub const base_tag = Tag.pointer;
@@ -3598,13 +3692,13 @@ pub const Type = extern union {
pub const Opaque = struct {
base: Payload = .{ .tag = .@"opaque" },
-
- scope: Module.Scope.Container,
+ data: Module.Scope.Container,
};
- pub const Enum = @import("type/Enum.zig");
- pub const Struct = @import("type/Struct.zig");
- pub const Union = @import("type/Union.zig");
+ pub const Struct = struct {
+ base: Payload = .{ .tag = .@"struct" },
+ data: *Module.Struct,
+ };
};
};
diff --git a/src/type/Enum.zig b/src/type/Enum.zig
@@ -1,55 +0,0 @@
-const std = @import("std");
-const zir = @import("../zir.zig");
-const Value = @import("../value.zig").Value;
-const Type = @import("../type.zig").Type;
-const Module = @import("../Module.zig");
-const Scope = Module.Scope;
-const Enum = @This();
-
-base: Type.Payload = .{ .tag = .@"enum" },
-
-analysis: union(enum) {
- queued: Zir,
- in_progress,
- resolved: Size,
- failed,
-},
-scope: Scope.Container,
-
-pub const Field = struct {
- value: Value,
-};
-
-pub const Zir = struct {
- body: zir.Body,
- inst: *zir.Inst,
-};
-
-pub const Size = struct {
- tag_type: Type,
- fields: std.StringArrayHashMapUnmanaged(Field),
-};
-
-pub fn resolve(self: *Enum, mod: *Module, scope: *Scope) !void {
- const zir = switch (self.analysis) {
- .failed => return error.AnalysisFail,
- .resolved => return,
- .in_progress => {
- return mod.fail(scope, src, "enum '{}' depends on itself", .{enum_name});
- },
- .queued => |zir| zir,
- };
- self.analysis = .in_progress;
-
- // TODO
-}
-
-// TODO should this resolve the type or assert that it has already been resolved?
-pub fn abiAlignment(self: *Enum, target: std.Target) u32 {
- switch (self.analysis) {
- .queued => unreachable, // alignment has not been resolved
- .in_progress => unreachable, // alignment has not been resolved
- .failed => unreachable, // type resolution failed
- .resolved => |r| return r.tag_type.abiAlignment(target),
- }
-}
diff --git a/src/type/Struct.zig b/src/type/Struct.zig
@@ -1,56 +0,0 @@
-const std = @import("std");
-const zir = @import("../zir.zig");
-const Value = @import("../value.zig").Value;
-const Type = @import("../type.zig").Type;
-const Module = @import("../Module.zig");
-const Scope = Module.Scope;
-const Struct = @This();
-
-base: Type.Payload = .{ .tag = .@"struct" },
-
-analysis: union(enum) {
- queued: Zir,
- zero_bits_in_progress,
- zero_bits: Zero,
- in_progress,
- // alignment: Align,
- resolved: Size,
- failed,
-},
-scope: Scope.Container,
-
-pub const Field = struct {
- value: Value,
-};
-
-pub const Zir = struct {
- body: zir.Body,
- inst: *zir.Inst,
-};
-
-pub const Zero = struct {
- is_zero_bits: bool,
- fields: std.StringArrayHashMapUnmanaged(Field),
-};
-
-pub const Size = struct {
- is_zero_bits: bool,
- alignment: u32,
- size: u32,
- fields: std.StringArrayHashMapUnmanaged(Field),
-};
-
-pub fn resolveZeroBits(self: *Struct, mod: *Module, scope: *Scope) !void {
- const zir = switch (self.analysis) {
- .failed => return error.AnalysisFail,
- .zero_bits_in_progress => {
- return mod.fail(scope, src, "struct '{}' depends on itself", .{});
- },
- .queued => |zir| zir,
- else => return,
- };
-
- self.analysis = .zero_bits_in_progress;
-
- // TODO
-}
diff --git a/src/type/Union.zig b/src/type/Union.zig
@@ -1,56 +0,0 @@
-const std = @import("std");
-const zir = @import("../zir.zig");
-const Value = @import("../value.zig").Value;
-const Type = @import("../type.zig").Type;
-const Module = @import("../Module.zig");
-const Scope = Module.Scope;
-const Union = @This();
-
-base: Type.Payload = .{ .tag = .@"struct" },
-
-analysis: union(enum) {
- queued: Zir,
- zero_bits_in_progress,
- zero_bits: Zero,
- in_progress,
- // alignment: Align,
- resolved: Size,
- failed,
-},
-scope: Scope.Container,
-
-pub const Field = struct {
- value: Value,
-};
-
-pub const Zir = struct {
- body: zir.Body,
- inst: *zir.Inst,
-};
-
-pub const Zero = struct {
- is_zero_bits: bool,
- fields: std.StringArrayHashMapUnmanaged(Field),
-};
-
-pub const Size = struct {
- is_zero_bits: bool,
- alignment: u32,
- size: u32,
- fields: std.StringArrayHashMapUnmanaged(Field),
-};
-
-pub fn resolveZeroBits(self: *Union, mod: *Module, scope: *Scope) !void {
- const zir = switch (self.analysis) {
- .failed => return error.AnalysisFail,
- .zero_bits_in_progress => {
- return mod.fail(scope, src, "union '{}' depends on itself", .{});
- },
- .queued => |zir| zir,
- else => return,
- };
-
- self.analysis = .zero_bits_in_progress;
-
- // TODO
-}
diff --git a/src/value.zig b/src/value.zig
@@ -30,6 +30,8 @@ pub const Value = extern union {
i32_type,
u64_type,
i64_type,
+ u128_type,
+ i128_type,
usize_type,
isize_type,
c_short_type,
@@ -62,18 +64,19 @@ pub const Value = extern union {
single_const_pointer_to_comptime_int_type,
const_slice_u8_type,
enum_literal_type,
- anyframe_type,
undef,
zero,
one,
void_value,
unreachable_value,
- empty_struct_value,
- empty_array,
null_value,
bool_true,
- bool_false, // See last_no_payload_tag below.
+ bool_false,
+
+ abi_align_default,
+ empty_struct_value,
+ empty_array, // See last_no_payload_tag below.
// After this, the tag requires a payload.
ty,
@@ -100,14 +103,13 @@ pub const Value = extern union {
float_64,
float_128,
enum_literal,
- error_set,
@"error",
error_union,
/// This is a special value that tracks a set of types that have been stored
/// to an inferred allocation. It does not support any of the normal value queries.
inferred_alloc,
- pub const last_no_payload_tag = Tag.bool_false;
+ pub const last_no_payload_tag = Tag.empty_array;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
pub fn Type(comptime t: Tag) type {
@@ -120,6 +122,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -152,7 +156,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.undef,
.zero,
.one,
@@ -163,6 +166,7 @@ pub const Value = extern union {
.null_value,
.bool_true,
.bool_false,
+ .abi_align_default,
=> @compileError("Value Tag " ++ @tagName(t) ++ " has no payload"),
.int_big_positive,
@@ -193,7 +197,6 @@ pub const Value = extern union {
.float_32 => Payload.Float_32,
.float_64 => Payload.Float_64,
.float_128 => Payload.Float_128,
- .error_set => Payload.ErrorSet,
.@"error" => Payload.Error,
.inferred_alloc => Payload.InferredAlloc,
};
@@ -275,6 +278,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -307,7 +312,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.undef,
.zero,
.one,
@@ -318,6 +322,7 @@ pub const Value = extern union {
.bool_true,
.bool_false,
.empty_struct_value,
+ .abi_align_default,
=> unreachable,
.ty => {
@@ -400,7 +405,6 @@ pub const Value = extern union {
return Value{ .ptr_otherwise = &new_payload.base };
},
- .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet),
.inferred_alloc => unreachable,
}
}
@@ -429,6 +433,8 @@ pub const Value = extern union {
.i32_type => return out_stream.writeAll("i32"),
.u64_type => return out_stream.writeAll("u64"),
.i64_type => return out_stream.writeAll("i64"),
+ .u128_type => return out_stream.writeAll("u128"),
+ .i128_type => return out_stream.writeAll("i128"),
.isize_type => return out_stream.writeAll("isize"),
.usize_type => return out_stream.writeAll("usize"),
.c_short_type => return out_stream.writeAll("c_short"),
@@ -461,9 +467,8 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
.const_slice_u8_type => return out_stream.writeAll("[]const u8"),
.enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"),
- .anyframe_type => return out_stream.writeAll("anyframe"),
+ .abi_align_default => return out_stream.writeAll("(default ABI alignment)"),
- // TODO this should print `NAME{}`
.empty_struct_value => return out_stream.writeAll("struct {}{}"),
.null_value => return out_stream.writeAll("null"),
.undef => return out_stream.writeAll("undefined"),
@@ -510,15 +515,6 @@ pub const Value = extern union {
.float_32 => return out_stream.print("{}", .{val.castTag(.float_32).?.data}),
.float_64 => return out_stream.print("{}", .{val.castTag(.float_64).?.data}),
.float_128 => return out_stream.print("{}", .{val.castTag(.float_128).?.data}),
- .error_set => {
- const error_set = val.castTag(.error_set).?.data;
- try out_stream.writeAll("error{");
- var it = error_set.fields.iterator();
- while (it.next()) |entry| {
- try out_stream.print("{},", .{entry.value});
- }
- return out_stream.writeAll("}");
- },
.@"error" => return out_stream.print("error.{s}", .{val.castTag(.@"error").?.data.name}),
// TODO to print this it should be error{ Set, Items }!T(val), but we need the type for that
.error_union => return out_stream.print("error_union_val({})", .{val.castTag(.error_union).?.data}),
@@ -557,6 +553,8 @@ pub const Value = extern union {
.i32_type => Type.initTag(.i32),
.u64_type => Type.initTag(.u64),
.i64_type => Type.initTag(.i64),
+ .u128_type => Type.initTag(.u128),
+ .i128_type => Type.initTag(.i128),
.usize_type => Type.initTag(.usize),
.isize_type => Type.initTag(.isize),
.c_short_type => Type.initTag(.c_short),
@@ -589,7 +587,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int),
.const_slice_u8_type => Type.initTag(.const_slice_u8),
.enum_literal_type => Type.initTag(.enum_literal),
- .anyframe_type => Type.initTag(.@"anyframe"),
.int_type => {
const payload = self.castTag(.int_type).?.data;
@@ -602,10 +599,6 @@ pub const Value = extern union {
};
return Type.initPayload(&new.base);
},
- .error_set => {
- const payload = self.castTag(.error_set).?.data;
- return Type.Tag.error_set.create(allocator, payload.decl);
- },
.undef,
.zero,
@@ -637,6 +630,7 @@ pub const Value = extern union {
.error_union,
.empty_struct_value,
.inferred_alloc,
+ .abi_align_default,
=> unreachable,
};
}
@@ -654,6 +648,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -686,7 +682,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.null_value,
.function,
.extern_fn,
@@ -704,11 +699,11 @@ pub const Value = extern union {
.unreachable_value,
.empty_array,
.enum_literal,
- .error_set,
.error_union,
.@"error",
.empty_struct_value,
.inferred_alloc,
+ .abi_align_default,
=> unreachable,
.undef => unreachable,
@@ -741,6 +736,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -773,7 +770,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.null_value,
.function,
.extern_fn,
@@ -791,11 +787,11 @@ pub const Value = extern union {
.unreachable_value,
.empty_array,
.enum_literal,
- .error_set,
.@"error",
.error_union,
.empty_struct_value,
.inferred_alloc,
+ .abi_align_default,
=> unreachable,
.undef => unreachable,
@@ -828,6 +824,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -860,7 +858,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.null_value,
.function,
.extern_fn,
@@ -878,11 +875,11 @@ pub const Value = extern union {
.unreachable_value,
.empty_array,
.enum_literal,
- .error_set,
.@"error",
.error_union,
.empty_struct_value,
.inferred_alloc,
+ .abi_align_default,
=> unreachable,
.undef => unreachable,
@@ -942,6 +939,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -974,7 +973,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.null_value,
.function,
.extern_fn,
@@ -993,11 +991,11 @@ pub const Value = extern union {
.unreachable_value,
.empty_array,
.enum_literal,
- .error_set,
.@"error",
.error_union,
.empty_struct_value,
.inferred_alloc,
+ .abi_align_default,
=> unreachable,
.zero,
@@ -1034,6 +1032,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -1066,7 +1066,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.null_value,
.function,
.extern_fn,
@@ -1084,11 +1083,11 @@ pub const Value = extern union {
.unreachable_value,
.empty_array,
.enum_literal,
- .error_set,
.@"error",
.error_union,
.empty_struct_value,
.inferred_alloc,
+ .abi_align_default,
=> unreachable,
.zero,
@@ -1191,6 +1190,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -1223,7 +1224,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.bool_true,
.bool_false,
.null_value,
@@ -1244,11 +1244,11 @@ pub const Value = extern union {
.void_value,
.unreachable_value,
.enum_literal,
- .error_set,
.@"error",
.error_union,
.empty_struct_value,
.inferred_alloc,
+ .abi_align_default,
=> unreachable,
.zero,
@@ -1275,6 +1275,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -1307,7 +1309,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.null_value,
.function,
.extern_fn,
@@ -1322,11 +1323,11 @@ pub const Value = extern union {
.unreachable_value,
.empty_array,
.enum_literal,
- .error_set,
.@"error",
.error_union,
.empty_struct_value,
.inferred_alloc,
+ .abi_align_default,
=> unreachable,
.zero,
@@ -1427,6 +1428,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -1459,18 +1462,13 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.ty,
+ .abi_align_default,
=> {
- // Directly return Type.hash, toType can only fail for .int_type and .error_set.
+ // Directly return Type.hash, toType can only fail for .int_type.
var allocator = std.heap.FixedBufferAllocator.init(&[_]u8{});
return (self.toType(&allocator.allocator) catch unreachable).hash();
},
- .error_set => {
- // Payload.decl should be same for all instances of the type.
- const payload = self.castTag(.error_set).?.data;
- std.hash.autoHash(&hasher, payload.decl);
- },
.int_type => {
const payload = self.castTag(.int_type).?.data;
var int_payload = Type.Payload.Bits{
@@ -1585,6 +1583,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -1617,7 +1617,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.zero,
.one,
.bool_true,
@@ -1641,11 +1640,11 @@ pub const Value = extern union {
.unreachable_value,
.empty_array,
.enum_literal,
- .error_set,
.@"error",
.error_union,
.empty_struct_value,
.inferred_alloc,
+ .abi_align_default,
=> unreachable,
.ref_val => self.castTag(.ref_val).?.data,
@@ -1672,6 +1671,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -1704,7 +1705,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.zero,
.one,
.bool_true,
@@ -1728,11 +1728,11 @@ pub const Value = extern union {
.void_value,
.unreachable_value,
.enum_literal,
- .error_set,
.@"error",
.error_union,
.empty_struct_value,
.inferred_alloc,
+ .abi_align_default,
=> unreachable,
.empty_array => unreachable, // out of bounds array index
@@ -1776,6 +1776,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -1808,7 +1810,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.zero,
.one,
.empty_array,
@@ -1832,10 +1833,10 @@ pub const Value = extern union {
.float_128,
.void_value,
.enum_literal,
- .error_set,
.@"error",
.error_union,
.empty_struct_value,
+ .abi_align_default,
=> false,
.undef => unreachable,
@@ -1858,6 +1859,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -1890,7 +1893,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
.zero,
.one,
.null_value,
@@ -1915,8 +1917,8 @@ pub const Value = extern union {
.float_128,
.void_value,
.enum_literal,
- .error_set,
.empty_struct_value,
+ .abi_align_default,
=> null,
.error_union => {
@@ -1960,6 +1962,8 @@ pub const Value = extern union {
.i32_type,
.u64_type,
.i64_type,
+ .u128_type,
+ .i128_type,
.usize_type,
.isize_type,
.c_short_type,
@@ -1992,8 +1996,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.enum_literal_type,
- .anyframe_type,
- .error_set,
=> true,
.zero,
@@ -2023,6 +2025,7 @@ pub const Value = extern union {
.error_union,
.empty_struct_value,
.null_value,
+ .abi_align_default,
=> false,
.undef => unreachable,
@@ -2137,18 +2140,6 @@ pub const Value = extern union {
data: f128,
};
- /// TODO move to type.zig
- pub const ErrorSet = struct {
- pub const base_tag = Tag.error_set;
-
- base: Payload = .{ .tag = base_tag },
- data: struct {
- /// TODO revisit this when we have the concept of the error tag type
- fields: std.StringHashMapUnmanaged(void),
- decl: *Module.Decl,
- },
- };
-
pub const Error = struct {
base: Payload = .{ .tag = .@"error" },
data: struct {
diff --git a/src/zir.zig b/src/zir.zig
@@ -1,4 +1,5 @@
-//! This file has to do with parsing and rendering the ZIR text format.
+//! Zig Intermediate Representation. Astgen.zig converts AST nodes to these
+//! untyped IR instructions. Next, Sema.zig processes these into TZIR.
const std = @import("std");
const mem = std.mem;
@@ -6,524 +7,663 @@ const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
+const ast = std.zig.ast;
+
const Type = @import("type.zig").Type;
const Value = @import("value.zig").Value;
const TypedValue = @import("TypedValue.zig");
const ir = @import("ir.zig");
-const IrModule = @import("Module.zig");
+const Module = @import("Module.zig");
+const LazySrcLoc = Module.LazySrcLoc;
+
+/// The minimum amount of information needed to represent a list of ZIR instructions.
+/// Once this structure is completed, it can be used to generate TZIR, followed by
+/// machine code, without any memory access into the AST tree token list, node list,
+/// or source bytes. Exceptions include:
+/// * Compile errors, which may need to reach into these data structures to
+/// create a useful report.
+/// * In the future, possibly inline assembly, which needs to get parsed and
+/// handled by the codegen backend, and errors reported there. However for now,
+/// inline assembly is not an exception.
+pub const Code = struct {
+ /// There is always implicitly a `block` instruction at index 0.
+ /// This is so that `break_inline` can break from the root block.
+ instructions: std.MultiArrayList(Inst).Slice,
+ /// In order to store references to strings in fewer bytes, we copy all
+ /// string bytes into here. String bytes can be null. It is up to whomever
+ /// is referencing the data here whether they want to store both index and length,
+ /// thus allowing null bytes, or store only index, and use null-termination. The
+ /// `string_bytes` array is agnostic to either usage.
+ string_bytes: []u8,
+ /// The meaning of this data is determined by `Inst.Tag` value.
+ extra: []u32,
+ /// Used for decl_val and decl_ref instructions.
+ decls: []*Module.Decl,
+
+ /// Returns the requested data, as well as the new index which is at the start of the
+ /// trailers for the object.
+ pub fn extraData(code: Code, comptime T: type, index: usize) struct { data: T, end: usize } {
+ const fields = std.meta.fields(T);
+ var i: usize = index;
+ var result: T = undefined;
+ inline for (fields) |field| {
+ @field(result, field.name) = switch (field.field_type) {
+ u32 => code.extra[i],
+ Inst.Ref => @intToEnum(Inst.Ref, code.extra[i]),
+ else => unreachable,
+ };
+ i += 1;
+ }
+ return .{
+ .data = result,
+ .end = i,
+ };
+ }
+
+ /// Given an index into `string_bytes` returns the null-terminated string found there.
+ pub fn nullTerminatedString(code: Code, index: usize) [:0]const u8 {
+ var end: usize = index;
+ while (code.string_bytes[end] != 0) {
+ end += 1;
+ }
+ return code.string_bytes[index..end :0];
+ }
+
+ pub fn refSlice(code: Code, start: usize, len: usize) []Inst.Ref {
+ const raw_slice = code.extra[start..][0..len];
+ return @bitCast([]Inst.Ref, raw_slice);
+ }
+
+ pub fn deinit(code: *Code, gpa: *Allocator) void {
+ code.instructions.deinit(gpa);
+ gpa.free(code.string_bytes);
+ gpa.free(code.extra);
+ gpa.free(code.decls);
+ code.* = undefined;
+ }
+
+ /// For debugging purposes, like dumpFn but for unanalyzed zir blocks
+ pub fn dump(
+ code: Code,
+ gpa: *Allocator,
+ kind: []const u8,
+ scope: *Module.Scope,
+ param_count: usize,
+ ) !void {
+ var arena = std.heap.ArenaAllocator.init(gpa);
+ defer arena.deinit();
+
+ var writer: Writer = .{
+ .gpa = gpa,
+ .arena = &arena.allocator,
+ .scope = scope,
+ .code = code,
+ .indent = 0,
+ .param_count = param_count,
+ };
+
+ const decl_name = scope.srcDecl().?.name;
+ const stderr = std.io.getStdErr().writer();
+ try stderr.print("ZIR {s} {s} %0 ", .{ kind, decl_name });
+ try writer.writeInstToStream(stderr, 0);
+ try stderr.print(" // end ZIR {s} {s}\n\n", .{ kind, decl_name });
+ }
+};
-/// These are instructions that correspond to the ZIR text format. See `ir.Inst` for
-/// in-memory, analyzed instructions with types and values.
-/// We use a table to map these instruction to their respective semantically analyzed
-/// instructions because it is possible to have multiple analyses on the same ZIR
-/// happening at the same time.
+/// These are untyped instructions generated from an Abstract Syntax Tree.
+/// The data here is immutable because it is possible to have multiple
+/// analyses on the same ZIR happening at the same time.
pub const Inst = struct {
tag: Tag,
- /// Byte offset into the source.
- src: usize,
+ data: Data,
/// These names are used directly as the instruction names in the text format.
- pub const Tag = enum {
+ pub const Tag = enum(u8) {
/// Arithmetic addition, asserts no integer overflow.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
add,
/// Twos complement wrapping integer addition.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
addwrap,
- /// Allocates stack local memory. Its lifetime ends when the block ends that contains
- /// this instruction. The operand is the type of the allocated object.
+ /// Allocates stack local memory.
+ /// Uses the `un_node` union field. The operand is the type of the allocated object.
+ /// The node source location points to a var decl node.
+ /// Indicates the beginning of a new statement in debug info.
alloc,
/// Same as `alloc` except mutable.
alloc_mut,
/// Same as `alloc` except the type is inferred.
+ /// The operand is unused.
alloc_inferred,
/// Same as `alloc_inferred` except mutable.
alloc_inferred_mut,
- /// Create an `anyframe->T`.
- anyframe_type,
/// Array concatenation. `a ++ b`
+ /// Uses the `pl_node` union field. Payload is `Bin`.
array_cat,
/// Array multiplication `a ** b`
+ /// Uses the `pl_node` union field. Payload is `Bin`.
array_mul,
- /// Create an array type
+ /// `[N]T` syntax. No source location provided.
+ /// Uses the `bin` union field. lhs is length, rhs is element type.
array_type,
- /// Create an array type with sentinel
+ /// `[N:S]T` syntax. No source location provided.
+ /// Uses the `array_type_sentinel` field.
array_type_sentinel,
/// Given a pointer to an indexable object, returns the len property. This is
- /// used by for loops. This instruction also emits a for-loop specific instruction
- /// if the indexable object is not indexable.
+ /// used by for loops. This instruction also emits a for-loop specific compile
+ /// error if the indexable object is not indexable.
+ /// Uses the `un_node` field. The AST node is the for loop node.
indexable_ptr_len,
- /// Function parameter value. These must be first in a function's main block,
- /// in respective order with the parameters.
- /// TODO make this instruction implicit; after we transition to having ZIR
- /// instructions be same sized and referenced by index, the first N indexes
- /// will implicitly be references to the parameters of the function.
- arg,
- /// Type coercion.
+ /// Type coercion. No source location attached.
+ /// Uses the `bin` field.
as,
- /// Inline assembly.
+ /// Type coercion to the function's return type.
+ /// Uses the `pl_node` field. Payload is `As`. AST node could be many things.
+ as_node,
+ /// Inline assembly. Non-volatile.
+ /// Uses the `pl_node` union field. Payload is `Asm`. AST node is the assembly node.
@"asm",
- /// Await an async function.
- @"await",
+ /// Inline assembly with the volatile attribute.
+ /// Uses the `pl_node` union field. Payload is `Asm`. AST node is the assembly node.
+ asm_volatile,
/// Bitwise AND. `&`
bit_and,
- /// TODO delete this instruction, it has no purpose.
+ /// Bitcast a value to a different type.
+ /// Uses the pl_node field with payload `Bin`.
bitcast,
- /// An arbitrary typed pointer is pointer-casted to a new Pointer.
- /// The destination type is given by LHS. The cast is to be evaluated
- /// as if it were a bit-cast operation from the operand pointer element type to the
- /// provided destination type.
- bitcast_ref,
/// A typed result location pointer is bitcasted to a new result location pointer.
/// The new result location pointer has an inferred type.
+ /// Uses the un_node field.
bitcast_result_ptr,
/// Bitwise NOT. `~`
+ /// Uses `un_node`.
bit_not,
/// Bitwise OR. `|`
bit_or,
/// A labeled block of code, which can return a value.
+ /// Uses the `pl_node` union field. Payload is `Block`.
block,
- /// A block of code, which can return a value. There are no instructions that break out of
- /// this block; it is implied that the final instruction is the result.
- block_flat,
- /// Same as `block` but additionally makes the inner instructions execute at comptime.
- block_comptime,
- /// Same as `block_flat` but additionally makes the inner instructions execute at comptime.
- block_comptime_flat,
+ /// A list of instructions which are analyzed in the parent context, without
+ /// generating a runtime block. Must terminate with an "inline" variant of
+ /// a noreturn instruction.
+ /// Uses the `pl_node` union field. Payload is `Block`.
+ block_inline,
/// Boolean AND. See also `bit_and`.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
bool_and,
/// Boolean NOT. See also `bit_not`.
+ /// Uses the `un_node` field.
bool_not,
/// Boolean OR. See also `bit_or`.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
bool_or,
- /// Return a value from a `Block`.
+ /// Short-circuiting boolean `and`. `lhs` is a boolean `Ref` and the other operand
+ /// is a block, which is evaluated if `lhs` is `true`.
+ /// Uses the `bool_br` union field.
+ bool_br_and,
+ /// Short-circuiting boolean `or`. `lhs` is a boolean `Ref` and the other operand
+ /// is a block, which is evaluated if `lhs` is `false`.
+ /// Uses the `bool_br` union field.
+ bool_br_or,
+ /// Return a value from a block.
+ /// Uses the `break` union field.
+ /// Uses the source information from previous instruction.
@"break",
+ /// Return a value from a block. This instruction is used as the terminator
+ /// of a `block_inline`. It allows using the return value from `Sema.analyzeBody`.
+ /// This instruction may also be used when it is known that there is only one
+ /// break instruction in a block, and the target block is the parent.
+ /// Uses the `break` union field.
+ break_inline,
+ /// Uses the `node` union field.
breakpoint,
- /// Same as `break` but without an operand; the operand is assumed to be the void value.
- break_void,
- /// Function call.
+ /// Function call with modifier `.auto`.
+ /// Uses `pl_node`. AST node is the function call. Payload is `Call`.
call,
+ /// Same as `call` but it also does `ensure_result_used` on the return value.
+ call_chkused,
+ /// Same as `call` but with modifier `.compile_time`.
+ call_compile_time,
+ /// Function call with modifier `.auto`, empty parameter list.
+ /// Uses the `un_node` field. Operand is callee. AST node is the function call.
+ call_none,
+ /// Same as `call_none` but it also does `ensure_result_used` on the return value.
+ call_none_chkused,
/// `<`
+ /// Uses the `pl_node` union field. Payload is `Bin`.
cmp_lt,
/// `<=`
+ /// Uses the `pl_node` union field. Payload is `Bin`.
cmp_lte,
/// `==`
+ /// Uses the `pl_node` union field. Payload is `Bin`.
cmp_eq,
/// `>=`
+ /// Uses the `pl_node` union field. Payload is `Bin`.
cmp_gte,
/// `>`
+ /// Uses the `pl_node` union field. Payload is `Bin`.
cmp_gt,
/// `!=`
+ /// Uses the `pl_node` union field. Payload is `Bin`.
cmp_neq,
/// Coerces a result location pointer to a new element type. It is evaluated "backwards"-
/// as type coercion from the new element type to the old element type.
+ /// Uses the `bin` union field.
/// LHS is destination element type, RHS is result pointer.
coerce_result_ptr,
/// Emit an error message and fail compilation.
+ /// Uses the `un_node` field.
compile_error,
/// Log compile time variables and emit an error message.
+ /// Uses the `pl_node` union field. The AST node is the compile log builtin call.
+ /// The payload is `MultiOp`.
compile_log,
/// Conditional branch. Splits control flow based on a boolean condition value.
+ /// Uses the `pl_node` union field. AST node is an if, while, for, etc.
+ /// Payload is `CondBr`.
condbr,
- /// Special case, has no textual representation.
+ /// Same as `condbr`, except the condition is coerced to a comptime value, and
+ /// only the taken branch is analyzed. The then block and else block must
+ /// terminate with an "inline" variant of a noreturn instruction.
+ condbr_inline,
+ /// A comptime known value.
+ /// Uses the `const` union field.
@"const",
- /// Container field with just the name.
- container_field_named,
- /// Container field with a type and a name,
- container_field_typed,
- /// Container field with all the bells and whistles.
- container_field,
+ /// A struct type definition. Contains references to ZIR instructions for
+ /// the field types, defaults, and alignments.
+ /// Uses the `pl_node` union field. Payload is `StructDecl`.
+ struct_decl,
+ /// Same as `struct_decl`, except has the `packed` layout.
+ struct_decl_packed,
+ /// Same as `struct_decl`, except has the `extern` layout.
+ struct_decl_extern,
+ /// A union type definition. Contains references to ZIR instructions for
+ /// the field types and optional type tag expression.
+ /// Uses the `pl_node` union field. Payload is `UnionDecl`.
+ union_decl,
+ /// An enum type definition. Contains references to ZIR instructions for
+ /// the field value expressions and optional type tag expression.
+ /// Uses the `pl_node` union field. Payload is `EnumDecl`.
+ enum_decl,
+ /// An opaque type definition. Provides an AST node only.
+ /// Uses the `node` union field.
+ opaque_decl,
/// Declares the beginning of a statement. Used for debug info.
- dbg_stmt,
+ /// Uses the `node` union field.
+ dbg_stmt_node,
/// Represents a pointer to a global decl.
+ /// Uses the `pl_node` union field. `payload_index` is into `decls`.
decl_ref,
- /// Represents a pointer to a global decl by string name.
- decl_ref_str,
- /// Equivalent to a decl_ref followed by deref.
+ /// Equivalent to a decl_ref followed by load.
+ /// Uses the `pl_node` union field. `payload_index` is into `decls`.
decl_val,
- /// Load the value from a pointer.
- deref,
+ /// Load the value from a pointer. Assumes `x.*` syntax.
+ /// Uses `un_node` field. AST node is the `x.*` syntax.
+ load,
/// Arithmetic division. Asserts no integer overflow.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
div,
/// Given a pointer to an array, slice, or pointer, returns a pointer to the element at
- /// the provided index.
+ /// the provided index. Uses the `bin` union field. Source location is implied
+ /// to be the same as the previous instruction.
elem_ptr,
+ /// Same as `elem_ptr` except also stores a source location node.
+ /// Uses the `pl_node` union field. AST node is a[b] syntax. Payload is `Bin`.
+ elem_ptr_node,
/// Given an array, slice, or pointer, returns the element at the provided index.
+ /// Uses the `bin` union field. Source location is implied to be the same
+ /// as the previous instruction.
elem_val,
+ /// Same as `elem_val` except also stores a source location node.
+ /// Uses the `pl_node` union field. AST node is a[b] syntax. Payload is `Bin`.
+ elem_val_node,
+ /// This instruction has been deleted late in the astgen phase. It must
+ /// be ignored, and the corresponding `Data` is undefined.
+ elided,
/// Emits a compile error if the operand is not `void`.
+ /// Uses the `un_node` field.
ensure_result_used,
/// Emits a compile error if an error is ignored.
+ /// Uses the `un_node` field.
ensure_result_non_error,
/// Create a `E!T` type.
+ /// Uses the `pl_node` field with `Bin` payload.
error_union_type,
- /// Create an error set.
- error_set,
- /// `error.Foo` syntax.
+ /// `error.Foo` syntax. Uses the `str_tok` field of the Data union.
error_value,
- /// Export the provided Decl as the provided name in the compilation's output object file.
- @"export",
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
- /// to the named field. The field name is a []const u8. Used by a.b syntax.
+ /// to the named field. The field name is stored in string_bytes. Used by a.b syntax.
+ /// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
field_ptr,
/// Given a struct or object that contains virtual fields, returns the named field.
- /// The field name is a []const u8. Used by a.b syntax.
+ /// The field name is stored in string_bytes. Used by a.b syntax.
+ /// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
field_val,
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
/// to the named field. The field name is a comptime instruction. Used by @field.
+ /// Uses `pl_node` field. The AST node is the builtin call. Payload is FieldNamed.
field_ptr_named,
/// Given a struct or object that contains virtual fields, returns the named field.
/// The field name is a comptime instruction. Used by @field.
+ /// Uses `pl_node` field. The AST node is the builtin call. Payload is FieldNamed.
field_val_named,
- /// Convert a larger float type to any other float type, possibly causing a loss of precision.
+ /// Convert a larger float type to any other float type, possibly causing
+ /// a loss of precision.
+ /// Uses the `pl_node` field. AST is the `@floatCast` syntax.
+ /// Payload is `Bin` with lhs as the dest type, rhs the operand.
floatcast,
- /// Declare a function body.
- @"fn",
/// Returns a function type, assuming unspecified calling convention.
+ /// Uses the `pl_node` union field. `payload_index` points to a `FnType`.
fn_type,
/// Same as `fn_type` but the function is variadic.
fn_type_var_args,
/// Returns a function type, with a calling convention instruction operand.
+ /// Uses the `pl_node` union field. `payload_index` points to a `FnTypeCc`.
fn_type_cc,
/// Same as `fn_type_cc` but the function is variadic.
fn_type_cc_var_args,
- /// @import(operand)
+ /// `@import(operand)`.
+ /// Uses the `un_node` field.
import,
- /// Integer literal.
+ /// Integer literal that fits in a u64. Uses the int union value.
int,
/// Convert an integer value to another integer type, asserting that the destination type
/// can hold the same mathematical value.
+ /// Uses the `pl_node` field. AST is the `@intCast` syntax.
+ /// Payload is `Bin` with lhs as the dest type, rhs the operand.
intcast,
/// Make an integer type out of signedness and bit count.
+ /// Payload is `int_type`
int_type,
+ /// Convert an error type to `u16`
+ error_to_int,
+ /// Convert a `u16` to `anyerror`
+ int_to_error,
/// Return a boolean false if an optional is null. `x != null`
+ /// Uses the `un_node` field.
is_non_null,
/// Return a boolean true if an optional is null. `x == null`
+ /// Uses the `un_node` field.
is_null,
/// Return a boolean false if an optional is null. `x.* != null`
+ /// Uses the `un_node` field.
is_non_null_ptr,
/// Return a boolean true if an optional is null. `x.* == null`
+ /// Uses the `un_node` field.
is_null_ptr,
/// Return a boolean true if value is an error
+ /// Uses the `un_node` field.
is_err,
/// Return a boolean true if dereferenced pointer is an error
+ /// Uses the `un_node` field.
is_err_ptr,
- /// A labeled block of code that loops forever. At the end of the body it is implied
- /// to repeat; no explicit "repeat" instruction terminates loop bodies.
+ /// A labeled block of code that loops forever. At the end of the body will have either
+ /// a `repeat` instruction or a `repeat_inline` instruction.
+ /// Uses the `pl_node` field. The AST node is either a for loop or while loop.
+ /// This ZIR instruction is needed because TZIR does not (yet?) match ZIR, and Sema
+ /// needs to emit more than 1 TZIR block for this instruction.
+ /// The payload is `Block`.
loop,
+ /// Sends runtime control flow back to the beginning of the current block.
+ /// Uses the `node` field.
+ repeat,
+ /// Sends comptime control flow back to the beginning of the current block.
+ /// Uses the `node` field.
+ repeat_inline,
/// Merge two error sets into one, `E1 || E2`.
+ /// Uses the `pl_node` field with payload `Bin`.
merge_error_sets,
/// Ambiguously remainder division or modulus. If the computation would possibly have
/// a different value depending on whether the operation is remainder division or modulus,
/// a compile error is emitted. Otherwise the computation is performed.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
mod_rem,
/// Arithmetic multiplication. Asserts no integer overflow.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
mul,
/// Twos complement wrapping integer multiplication.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
mulwrap,
- /// An await inside a nosuspend scope.
- nosuspend_await,
/// Given a reference to a function and a parameter index, returns the
- /// type of the parameter. TODO what happens when the parameter is `anytype`?
+ /// type of the parameter. The only usage of this instruction is for the
+ /// result location of parameters of function calls. In the case of a function's
+ /// parameter type being `anytype`, it is the type coercion's job to detect this
+ /// scenario and skip the coercion, so that semantic analysis of this instruction
+ /// is not in a position where it must create an invalid type.
+ /// Uses the `param_type` union field.
param_type,
- /// An alternative to using `const` for simple primitive values such as `true` or `u8`.
- /// TODO flatten so that each primitive has its own ZIR Inst Tag.
- primitive,
/// Convert a pointer to a `usize` integer.
+ /// Uses the `un_node` field. The AST node is the builtin fn call node.
ptrtoint,
/// Turns an R-Value into a const L-Value. In other words, it takes a value,
/// stores it in a memory location, and returns a const pointer to it. If the value
/// is `comptime`, the memory location is global static constant data. Otherwise,
/// the memory location is in the stack frame, local to the scope containing the
/// instruction.
+ /// Uses the `un_tok` union field.
ref,
- /// Resume an async function.
- @"resume",
/// Obtains a pointer to the return value.
+ /// Uses the `node` union field.
ret_ptr,
/// Obtains the return type of the in-scope function.
+ /// Uses the `node` union field.
ret_type,
- /// Sends control flow back to the function's callee. Takes an operand as the return value.
- @"return",
- /// Same as `return` but there is no operand; the operand is implicitly the void value.
- return_void,
+ /// Sends control flow back to the function's callee.
+ /// Includes an operand as the return value.
+ /// Includes an AST node source location.
+ /// Uses the `un_node` union field.
+ ret_node,
+ /// Sends control flow back to the function's callee.
+ /// Includes an operand as the return value.
+ /// Includes a token source location.
+ /// Uses the `un_tok` union field.
+ ret_tok,
+ /// Same as `ret_tok` except the operand needs to get coerced to the function's
+ /// return type.
+ ret_coerce,
/// Changes the maximum number of backwards branches that compile-time
/// code execution can use before giving up and making a compile error.
+ /// Uses the `un_node` union field.
set_eval_branch_quota,
/// Integer shift-left. Zeroes are shifted in from the right hand side.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
shl,
/// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
shr,
- /// Create a const pointer type with element type T. `*const T`
- single_const_ptr_type,
- /// Create a mutable pointer type with element type T. `*T`
- single_mut_ptr_type,
- /// Create a const pointer type with element type T. `[*]const T`
- many_const_ptr_type,
- /// Create a mutable pointer type with element type T. `[*]T`
- many_mut_ptr_type,
- /// Create a const pointer type with element type T. `[*c]const T`
- c_const_ptr_type,
- /// Create a mutable pointer type with element type T. `[*c]T`
- c_mut_ptr_type,
- /// Create a mutable slice type with element type T. `[]T`
- mut_slice_type,
- /// Create a const slice type with element type T. `[]T`
- const_slice_type,
- /// Create a pointer type with attributes
+ /// Create a pointer type that does not have a sentinel, alignment, or bit range specified.
+ /// Uses the `ptr_type_simple` union field.
+ ptr_type_simple,
+ /// Create a pointer type which can have a sentinel, alignment, and/or bit range.
+ /// Uses the `ptr_type` union field.
ptr_type,
/// Each `store_to_inferred_ptr` puts the type of the stored value into a set,
/// and then `resolve_inferred_alloc` triggers peer type resolution on the set.
/// The operand is a `alloc_inferred` or `alloc_inferred_mut` instruction, which
/// is the allocation that needs to have its type inferred.
+ /// Uses the `un_node` field. The AST node is the var decl.
resolve_inferred_alloc,
- /// Slice operation `array_ptr[start..end:sentinel]`
- slice,
- /// Slice operation with just start `lhs[rhs..]`
+ /// Slice operation `lhs[rhs..]`. No sentinel and no end offset.
+ /// Uses the `pl_node` field. AST node is the slice syntax. Payload is `SliceStart`.
slice_start,
- /// Write a value to a pointer. For loading, see `deref`.
+ /// Slice operation `array_ptr[start..end]`. No sentinel.
+ /// Uses the `pl_node` field. AST node is the slice syntax. Payload is `SliceEnd`.
+ slice_end,
+ /// Slice operation `array_ptr[start..end:sentinel]`.
+ /// Uses the `pl_node` field. AST node is the slice syntax. Payload is `SliceSentinel`.
+ slice_sentinel,
+ /// Write a value to a pointer. For loading, see `load`.
+ /// Source location is assumed to be same as previous instruction.
+ /// Uses the `bin` union field.
store,
+ /// Same as `store` except provides a source location.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ store_node,
/// Same as `store` but the type of the value being stored will be used to infer
/// the block type. The LHS is the pointer to store to.
+ /// Uses the `bin` union field.
store_to_block_ptr,
/// Same as `store` but the type of the value being stored will be used to infer
/// the pointer type.
+ /// Uses the `bin` union field - Astgen.zig depends on the ability to change
+ /// the tag of an instruction from `store_to_block_ptr` to `store_to_inferred_ptr`
+ /// without changing the data.
store_to_inferred_ptr,
/// String Literal. Makes an anonymous Decl and then takes a pointer to it.
+ /// Uses the `str` union field.
str,
- /// Create a struct type.
- struct_type,
/// Arithmetic subtraction. Asserts no integer overflow.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
sub,
/// Twos complement wrapping integer subtraction.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
subwrap,
+ /// Arithmetic negation. Asserts no integer overflow.
+ /// Same as sub with a lhs of 0, split into a separate instruction to save memory.
+ /// Uses `un_node`.
+ negate,
+ /// Twos complement wrapping integer negation.
+ /// Same as subwrap with a lhs of 0, split into a separate instruction to save memory.
+ /// Uses `un_node`.
+ negate_wrap,
/// Returns the type of a value.
+ /// Uses the `un_tok` field.
typeof,
- /// Is the builtin @TypeOf which returns the type after peertype resolution of one or more params
+ /// Given a value which is a pointer, returns the element type.
+ /// Uses the `un_node` field.
+ typeof_elem,
+ /// The builtin `@TypeOf` which returns the type after Peer Type Resolution
+ /// of one or more params.
+ /// Uses the `pl_node` field. AST node is the `@TypeOf` call. Payload is `MultiOp`.
typeof_peer,
- /// Asserts control-flow will not reach this instruction. Not safety checked - the compiler
- /// will assume the correctness of this instruction.
- unreachable_unsafe,
- /// Asserts control-flow will not reach this instruction. In safety-checked modes,
- /// this will generate a call to the panic function unless it can be proven unreachable
- /// by the compiler.
- unreachable_safe,
+ /// Asserts control-flow will not reach this instruction (`unreachable`).
+ /// Uses the `unreachable` union field.
+ @"unreachable",
/// Bitwise XOR. `^`
+ /// Uses the `pl_node` union field. Payload is `Bin`.
xor,
/// Create an optional type '?T'
+ /// Uses the `un_node` field.
optional_type,
/// Create an optional type '?T'. The operand is a pointer value. The optional type will
/// be the type of the pointer element, wrapped in an optional.
+ /// Uses the `un_node` field.
optional_type_from_ptr_elem,
- /// Create a union type.
- union_type,
/// ?T => T with safety.
/// Given an optional value, returns the payload value, with a safety check that
/// the value is non-null. Used for `orelse`, `if` and `while`.
+ /// Uses the `un_node` field.
optional_payload_safe,
/// ?T => T without safety.
/// Given an optional value, returns the payload value. No safety checks.
+ /// Uses the `un_node` field.
optional_payload_unsafe,
/// *?T => *T with safety.
/// Given a pointer to an optional value, returns a pointer to the payload value,
/// with a safety check that the value is non-null. Used for `orelse`, `if` and `while`.
+ /// Uses the `un_node` field.
optional_payload_safe_ptr,
/// *?T => *T without safety.
/// Given a pointer to an optional value, returns a pointer to the payload value.
/// No safety checks.
+ /// Uses the `un_node` field.
optional_payload_unsafe_ptr,
/// E!T => T with safety.
/// Given an error union value, returns the payload value, with a safety check
/// that the value is not an error. Used for catch, if, and while.
+ /// Uses the `un_node` field.
err_union_payload_safe,
/// E!T => T without safety.
/// Given an error union value, returns the payload value. No safety checks.
+ /// Uses the `un_node` field.
err_union_payload_unsafe,
/// *E!T => *T with safety.
/// Given a pointer to an error union value, returns a pointer to the payload value,
/// with a safety check that the value is not an error. Used for catch, if, and while.
+ /// Uses the `un_node` field.
err_union_payload_safe_ptr,
/// *E!T => *T without safety.
/// Given a pointer to a error union value, returns a pointer to the payload value.
/// No safety checks.
+ /// Uses the `un_node` field.
err_union_payload_unsafe_ptr,
/// E!T => E without safety.
/// Given an error union value, returns the error code. No safety checks.
+ /// Uses the `un_node` field.
err_union_code,
/// *E!T => E without safety.
/// Given a pointer to an error union value, returns the error code. No safety checks.
+ /// Uses the `un_node` field.
err_union_code_ptr,
/// Takes a *E!T and raises a compiler error if T != void
+ /// Uses the `un_tok` field.
ensure_err_payload_void,
- /// Create a enum literal,
+ /// An enum literal. Uses the `str_tok` union field.
enum_literal,
- /// Create an enum type.
- enum_type,
- /// Does nothing; returns a void value.
- void_value,
- /// Suspend an async function.
- @"suspend",
- /// Suspend an async function.
- /// Same as .suspend but with a block.
- suspend_block,
- /// A switch expression.
- switchbr,
- /// Same as `switchbr` but the target is a pointer to the value being switched on.
- switchbr_ref,
- /// A range in a switch case, `lhs...rhs`.
- /// Only checks that `lhs >= rhs` if they are ints, everything else is
- /// validated by the .switch instruction.
- switch_range,
-
- pub fn Type(tag: Tag) type {
- return switch (tag) {
- .alloc_inferred,
- .alloc_inferred_mut,
- .breakpoint,
- .dbg_stmt,
- .return_void,
- .ret_ptr,
- .ret_type,
- .unreachable_unsafe,
- .unreachable_safe,
- .void_value,
- .@"suspend",
- => NoOp,
-
- .alloc,
- .alloc_mut,
- .bool_not,
- .compile_error,
- .deref,
- .@"return",
- .is_null,
- .is_non_null,
- .is_null_ptr,
- .is_non_null_ptr,
- .is_err,
- .is_err_ptr,
- .ptrtoint,
- .ensure_result_used,
- .ensure_result_non_error,
- .bitcast_result_ptr,
- .ref,
- .bitcast_ref,
- .typeof,
- .resolve_inferred_alloc,
- .single_const_ptr_type,
- .single_mut_ptr_type,
- .many_const_ptr_type,
- .many_mut_ptr_type,
- .c_const_ptr_type,
- .c_mut_ptr_type,
- .mut_slice_type,
- .const_slice_type,
- .optional_type,
- .optional_type_from_ptr_elem,
- .optional_payload_safe,
- .optional_payload_unsafe,
- .optional_payload_safe_ptr,
- .optional_payload_unsafe_ptr,
- .err_union_payload_safe,
- .err_union_payload_unsafe,
- .err_union_payload_safe_ptr,
- .err_union_payload_unsafe_ptr,
- .err_union_code,
- .err_union_code_ptr,
- .ensure_err_payload_void,
- .anyframe_type,
- .bit_not,
- .import,
- .set_eval_branch_quota,
- .indexable_ptr_len,
- .@"resume",
- .@"await",
- .nosuspend_await,
- => UnOp,
-
- .add,
- .addwrap,
- .array_cat,
- .array_mul,
- .array_type,
- .bit_and,
- .bit_or,
- .bool_and,
- .bool_or,
- .div,
- .mod_rem,
- .mul,
- .mulwrap,
- .shl,
- .shr,
- .store,
- .store_to_block_ptr,
- .store_to_inferred_ptr,
- .sub,
- .subwrap,
- .cmp_lt,
- .cmp_lte,
- .cmp_eq,
- .cmp_gte,
- .cmp_gt,
- .cmp_neq,
- .as,
- .floatcast,
- .intcast,
- .bitcast,
- .coerce_result_ptr,
- .xor,
- .error_union_type,
- .merge_error_sets,
- .slice_start,
- .switch_range,
- => BinOp,
-
- .block,
- .block_flat,
- .block_comptime,
- .block_comptime_flat,
- .suspend_block,
- => Block,
-
- .switchbr, .switchbr_ref => SwitchBr,
-
- .arg => Arg,
- .array_type_sentinel => ArrayTypeSentinel,
- .@"break" => Break,
- .break_void => BreakVoid,
- .call => Call,
- .decl_ref => DeclRef,
- .decl_ref_str => DeclRefStr,
- .decl_val => DeclVal,
- .compile_log => CompileLog,
- .loop => Loop,
- .@"const" => Const,
- .str => Str,
- .int => Int,
- .int_type => IntType,
- .field_ptr, .field_val => Field,
- .field_ptr_named, .field_val_named => FieldNamed,
- .@"asm" => Asm,
- .@"fn" => Fn,
- .@"export" => Export,
- .param_type => ParamType,
- .primitive => Primitive,
- .fn_type, .fn_type_var_args => FnType,
- .fn_type_cc, .fn_type_cc_var_args => FnTypeCc,
- .elem_ptr, .elem_val => Elem,
- .condbr => CondBr,
- .ptr_type => PtrType,
- .enum_literal => EnumLiteral,
- .error_set => ErrorSet,
- .error_value => ErrorValue,
- .slice => Slice,
- .typeof_peer => TypeOfPeer,
- .container_field_named => ContainerFieldNamed,
- .container_field_typed => ContainerFieldTyped,
- .container_field => ContainerField,
- .enum_type => EnumType,
- .union_type => UnionType,
- .struct_type => StructType,
- };
- }
+ /// An enum literal 8 or fewer bytes. No source location.
+ /// Uses the `small_str` field.
+ enum_literal_small,
+ /// A switch expression. Uses the `pl_node` union field.
+ /// AST node is the switch, payload is `SwitchBlock`.
+ /// All prongs of target handled.
+ switch_block,
+ /// Same as switch_block, except one or more prongs have multiple items.
+ switch_block_multi,
+ /// Same as switch_block, except has an else prong.
+ switch_block_else,
+ /// Same as switch_block_else, except one or more prongs have multiple items.
+ switch_block_else_multi,
+ /// Same as switch_block, except has an underscore prong.
+ switch_block_under,
+ /// Same as switch_block, except one or more prongs have multiple items.
+ switch_block_under_multi,
+ /// Same as `switch_block` but the target is a pointer to the value being switched on.
+ switch_block_ref,
+ /// Same as `switch_block_multi` but the target is a pointer to the value being switched on.
+ switch_block_ref_multi,
+ /// Same as `switch_block_else` but the target is a pointer to the value being switched on.
+ switch_block_ref_else,
+ /// Same as `switch_block_else_multi` but the target is a pointer to the
+ /// value being switched on.
+ switch_block_ref_else_multi,
+ /// Same as `switch_block_under` but the target is a pointer to the value
+ /// being switched on.
+ switch_block_ref_under,
+ /// Same as `switch_block_under_multi` but the target is a pointer to
+ /// the value being switched on.
+ switch_block_ref_under_multi,
+ /// Produces the capture value for a switch prong.
+ /// Uses the `switch_capture` field.
+ switch_capture,
+ /// Produces the capture value for a switch prong.
+ /// Result is a pointer to the value.
+ /// Uses the `switch_capture` field.
+ switch_capture_ref,
+ /// Produces the capture value for a switch prong.
+ /// The prong is one of the multi cases.
+ /// Uses the `switch_capture` field.
+ switch_capture_multi,
+ /// Produces the capture value for a switch prong.
+ /// The prong is one of the multi cases.
+ /// Result is a pointer to the value.
+ /// Uses the `switch_capture` field.
+ switch_capture_multi_ref,
+ /// Produces the capture value for the else/'_' switch prong.
+ /// Uses the `switch_capture` field.
+ switch_capture_else,
+ /// Produces the capture value for the else/'_' switch prong.
+ /// Result is a pointer to the value.
+ /// Uses the `switch_capture` field.
+ switch_capture_else_ref,
+ /// Given a set of `field_ptr` instructions, assumes they are all part of a struct
+ /// initialization expression, and emits compile errors for duplicate fields
+ /// as well as missing fields, if applicable.
+ /// Uses the `pl_node` field. Payload is `Block`.
+ validate_struct_init_ptr,
+ /// A struct literal with a specified type, with no fields.
+ /// Uses the `un_node` field.
+ struct_init_empty,
/// Returns whether the instruction is one of the control flow "noreturn" types.
/// Function calls do not count.
@@ -540,23 +680,28 @@ pub const Inst = struct {
.array_type,
.array_type_sentinel,
.indexable_ptr_len,
- .arg,
.as,
+ .as_node,
.@"asm",
+ .asm_volatile,
.bit_and,
.bitcast,
- .bitcast_ref,
.bitcast_result_ptr,
.bit_or,
.block,
- .block_flat,
- .block_comptime,
- .block_comptime_flat,
+ .block_inline,
+ .loop,
+ .bool_br_and,
+ .bool_br_or,
.bool_not,
.bool_and,
.bool_or,
.breakpoint,
.call,
+ .call_chkused,
+ .call_compile_time,
+ .call_none,
+ .call_none_chkused,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@@ -565,23 +710,28 @@ pub const Inst = struct {
.cmp_neq,
.coerce_result_ptr,
.@"const",
- .dbg_stmt,
+ .struct_decl,
+ .struct_decl_packed,
+ .struct_decl_extern,
+ .union_decl,
+ .enum_decl,
+ .opaque_decl,
+ .dbg_stmt_node,
.decl_ref,
- .decl_ref_str,
.decl_val,
- .deref,
+ .load,
.div,
.elem_ptr,
.elem_val,
+ .elem_ptr_node,
+ .elem_val_node,
.ensure_result_used,
.ensure_result_non_error,
- .@"export",
.floatcast,
.field_ptr,
.field_val,
.field_ptr_named,
.field_val_named,
- .@"fn",
.fn_type,
.fn_type_var_args,
.fn_type_cc,
@@ -599,28 +749,23 @@ pub const Inst = struct {
.mul,
.mulwrap,
.param_type,
- .primitive,
.ptrtoint,
.ref,
.ret_ptr,
.ret_type,
.shl,
.shr,
- .single_const_ptr_type,
- .single_mut_ptr_type,
- .many_const_ptr_type,
- .many_mut_ptr_type,
- .c_const_ptr_type,
- .c_mut_ptr_type,
- .mut_slice_type,
- .const_slice_type,
.store,
+ .store_node,
.store_to_block_ptr,
.store_to_inferred_ptr,
.str,
.sub,
.subwrap,
+ .negate,
+ .negate_wrap,
.typeof,
+ .typeof_elem,
.xor,
.optional_type,
.optional_type_from_ptr_elem,
@@ -634,1436 +779,1566 @@ pub const Inst = struct {
.err_union_payload_unsafe_ptr,
.err_union_code,
.err_union_code_ptr,
+ .error_to_int,
+ .int_to_error,
.ptr_type,
+ .ptr_type_simple,
.ensure_err_payload_void,
.enum_literal,
+ .enum_literal_small,
.merge_error_sets,
- .anyframe_type,
.error_union_type,
.bit_not,
- .error_set,
.error_value,
- .slice,
.slice_start,
+ .slice_end,
+ .slice_sentinel,
.import,
.typeof_peer,
.resolve_inferred_alloc,
.set_eval_branch_quota,
.compile_log,
- .enum_type,
- .union_type,
- .struct_type,
- .void_value,
- .switch_range,
- .@"resume",
- .@"await",
- .nosuspend_await,
+ .elided,
+ .switch_capture,
+ .switch_capture_ref,
+ .switch_capture_multi,
+ .switch_capture_multi_ref,
+ .switch_capture_else,
+ .switch_capture_else_ref,
+ .switch_block,
+ .switch_block_multi,
+ .switch_block_else,
+ .switch_block_else_multi,
+ .switch_block_under,
+ .switch_block_under_multi,
+ .switch_block_ref,
+ .switch_block_ref_multi,
+ .switch_block_ref_else,
+ .switch_block_ref_else_multi,
+ .switch_block_ref_under,
+ .switch_block_ref_under_multi,
+ .validate_struct_init_ptr,
+ .struct_init_empty,
=> false,
.@"break",
- .break_void,
+ .break_inline,
.condbr,
+ .condbr_inline,
.compile_error,
- .@"return",
- .return_void,
- .unreachable_unsafe,
- .unreachable_safe,
- .loop,
- .container_field_named,
- .container_field_typed,
- .container_field,
- .switchbr,
- .switchbr_ref,
- .@"suspend",
- .suspend_block,
+ .ret_node,
+ .ret_tok,
+ .ret_coerce,
+ .@"unreachable",
+ .repeat,
+ .repeat_inline,
=> true,
};
}
};
- /// Prefer `castTag` to this.
- pub fn cast(base: *Inst, comptime T: type) ?*T {
- if (@hasField(T, "base_tag")) {
- return base.castTag(T.base_tag);
- }
- inline for (@typeInfo(Tag).Enum.fields) |field| {
- const tag = @intToEnum(Tag, field.value);
- if (base.tag == tag) {
- if (T == tag.Type()) {
- return @fieldParentPtr(T, "base", base);
- }
- return null;
- }
- }
- unreachable;
- }
-
- pub fn castTag(base: *Inst, comptime tag: Tag) ?*tag.Type() {
- if (base.tag == tag) {
- return @fieldParentPtr(tag.Type(), "base", base);
- }
- return null;
- }
-
- pub const NoOp = struct {
- base: Inst,
+ /// The position of a ZIR instruction within the `Code` instructions array.
+ pub const Index = u32;
+
+ /// A reference to a TypedValue, parameter of the current function,
+ /// or ZIR instruction.
+ ///
+ /// If the Ref has a tag in this enum, it refers to a TypedValue which may be
+ /// retrieved with Ref.toTypedValue().
+ ///
+ /// If the value of a Ref does not have a tag, it referes to either a parameter
+ /// of the current function or a ZIR instruction.
+ ///
+ /// The first values after the the last tag refer to parameters which may be
+ /// derived by subtracting typed_value_map.len.
+ ///
+ /// All further values refer to ZIR instructions which may be derived by
+ /// subtracting typed_value_map.len and the number of parameters.
+ ///
+ /// When adding a tag to this enum, consider adding a corresponding entry to
+ /// `simple_types` in astgen.
+ ///
+ /// The tag type is specified so that it is safe to bitcast between `[]u32`
+ /// and `[]Ref`.
+ pub const Ref = enum(u32) {
+ /// This Ref does not correspond to any ZIR instruction or constant
+ /// value and may instead be used as a sentinel to indicate null.
+ none,
+
+ u8_type,
+ i8_type,
+ u16_type,
+ i16_type,
+ u32_type,
+ i32_type,
+ u64_type,
+ i64_type,
+ usize_type,
+ isize_type,
+ c_short_type,
+ c_ushort_type,
+ c_int_type,
+ c_uint_type,
+ c_long_type,
+ c_ulong_type,
+ c_longlong_type,
+ c_ulonglong_type,
+ c_longdouble_type,
+ f16_type,
+ f32_type,
+ f64_type,
+ f128_type,
+ c_void_type,
+ bool_type,
+ void_type,
+ type_type,
+ anyerror_type,
+ comptime_int_type,
+ comptime_float_type,
+ noreturn_type,
+ null_type,
+ undefined_type,
+ fn_noreturn_no_args_type,
+ fn_void_no_args_type,
+ fn_naked_noreturn_no_args_type,
+ fn_ccc_void_no_args_type,
+ single_const_pointer_to_comptime_int_type,
+ const_slice_u8_type,
+ enum_literal_type,
+
+ /// `undefined` (untyped)
+ undef,
+ /// `0` (comptime_int)
+ zero,
+ /// `1` (comptime_int)
+ one,
+ /// `{}`
+ void_value,
+ /// `unreachable` (noreturn type)
+ unreachable_value,
+ /// `null` (untyped)
+ null_value,
+ /// `true`
+ bool_true,
+ /// `false`
+ bool_false,
+ /// `.{}` (untyped)
+ empty_struct,
+ /// `0` (usize)
+ zero_usize,
+ /// `1` (usize)
+ one_usize,
+
+ _,
+
+ pub const typed_value_map = std.enums.directEnumArray(Ref, TypedValue, 0, .{
+ .none = undefined,
+
+ .u8_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.u8_type),
+ },
+ .i8_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.i8_type),
+ },
+ .u16_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.u16_type),
+ },
+ .i16_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.i16_type),
+ },
+ .u32_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.u32_type),
+ },
+ .i32_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.i32_type),
+ },
+ .u64_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.u64_type),
+ },
+ .i64_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.i64_type),
+ },
+ .usize_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.usize_type),
+ },
+ .isize_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.isize_type),
+ },
+ .c_short_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.c_short_type),
+ },
+ .c_ushort_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.c_ushort_type),
+ },
+ .c_int_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.c_int_type),
+ },
+ .c_uint_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.c_uint_type),
+ },
+ .c_long_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.c_long_type),
+ },
+ .c_ulong_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.c_ulong_type),
+ },
+ .c_longlong_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.c_longlong_type),
+ },
+ .c_ulonglong_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.c_ulonglong_type),
+ },
+ .c_longdouble_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.c_longdouble_type),
+ },
+ .f16_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.f16_type),
+ },
+ .f32_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.f32_type),
+ },
+ .f64_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.f64_type),
+ },
+ .f128_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.f128_type),
+ },
+ .c_void_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.c_void_type),
+ },
+ .bool_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.bool_type),
+ },
+ .void_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.void_type),
+ },
+ .type_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.type_type),
+ },
+ .anyerror_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.anyerror_type),
+ },
+ .comptime_int_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.comptime_int_type),
+ },
+ .comptime_float_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.comptime_float_type),
+ },
+ .noreturn_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.noreturn_type),
+ },
+ .null_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.null_type),
+ },
+ .undefined_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.undefined_type),
+ },
+ .fn_noreturn_no_args_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.fn_noreturn_no_args_type),
+ },
+ .fn_void_no_args_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.fn_void_no_args_type),
+ },
+ .fn_naked_noreturn_no_args_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.fn_naked_noreturn_no_args_type),
+ },
+ .fn_ccc_void_no_args_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.fn_ccc_void_no_args_type),
+ },
+ .single_const_pointer_to_comptime_int_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.single_const_pointer_to_comptime_int_type),
+ },
+ .const_slice_u8_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.const_slice_u8_type),
+ },
+ .enum_literal_type = .{
+ .ty = Type.initTag(.type),
+ .val = Value.initTag(.enum_literal_type),
+ },
- positionals: struct {},
- kw_args: struct {},
+ .undef = .{
+ .ty = Type.initTag(.@"undefined"),
+ .val = Value.initTag(.undef),
+ },
+ .zero = .{
+ .ty = Type.initTag(.comptime_int),
+ .val = Value.initTag(.zero),
+ },
+ .zero_usize = .{
+ .ty = Type.initTag(.usize),
+ .val = Value.initTag(.zero),
+ },
+ .one = .{
+ .ty = Type.initTag(.comptime_int),
+ .val = Value.initTag(.one),
+ },
+ .one_usize = .{
+ .ty = Type.initTag(.usize),
+ .val = Value.initTag(.one),
+ },
+ .void_value = .{
+ .ty = Type.initTag(.void),
+ .val = Value.initTag(.void_value),
+ },
+ .unreachable_value = .{
+ .ty = Type.initTag(.noreturn),
+ .val = Value.initTag(.unreachable_value),
+ },
+ .null_value = .{
+ .ty = Type.initTag(.@"null"),
+ .val = Value.initTag(.null_value),
+ },
+ .bool_true = .{
+ .ty = Type.initTag(.bool),
+ .val = Value.initTag(.bool_true),
+ },
+ .bool_false = .{
+ .ty = Type.initTag(.bool),
+ .val = Value.initTag(.bool_false),
+ },
+ .empty_struct = .{
+ .ty = Type.initTag(.empty_struct_literal),
+ .val = Value.initTag(.empty_struct_value),
+ },
+ });
};
- pub const UnOp = struct {
- base: Inst,
-
- positionals: struct {
- operand: *Inst,
+ /// All instructions have an 8-byte payload, which is contained within
+ /// this union. `Tag` determines which union field is active, as well as
+ /// how to interpret the data within.
+ pub const Data = union {
+ /// Used for unary operators, with an AST node source location.
+ un_node: struct {
+ /// Offset from Decl AST node index.
+ src_node: i32,
+ /// The meaning of this operand depends on the corresponding `Tag`.
+ operand: Ref,
+
+ pub fn src(self: @This()) LazySrcLoc {
+ return .{ .node_offset = self.src_node };
+ }
},
- kw_args: struct {},
- };
-
- pub const BinOp = struct {
- base: Inst,
-
- positionals: struct {
- lhs: *Inst,
- rhs: *Inst,
+ /// Used for unary operators, with a token source location.
+ un_tok: struct {
+ /// Offset from Decl AST token index.
+ src_tok: ast.TokenIndex,
+ /// The meaning of this operand depends on the corresponding `Tag`.
+ operand: Ref,
+
+ pub fn src(self: @This()) LazySrcLoc {
+ return .{ .token_offset = self.src_tok };
+ }
},
- kw_args: struct {},
- };
-
- pub const Arg = struct {
- pub const base_tag = Tag.arg;
- base: Inst,
+ pl_node: struct {
+ /// Offset from Decl AST node index.
+ /// `Tag` determines which kind of AST node this points to.
+ src_node: i32,
+ /// index into extra.
+ /// `Tag` determines what lives there.
+ payload_index: u32,
+
+ pub fn src(self: @This()) LazySrcLoc {
+ return .{ .node_offset = self.src_node };
+ }
+ },
+ bin: Bin,
+ @"const": *TypedValue,
+ /// For strings which may contain null bytes.
+ str: struct {
+ /// Offset into `string_bytes`.
+ start: u32,
+ /// Number of bytes in the string.
+ len: u32,
+
+ pub fn get(self: @This(), code: Code) []const u8 {
+ return code.string_bytes[self.start..][0..self.len];
+ }
+ },
+ /// Strings 8 or fewer bytes which may not contain null bytes.
+ small_str: struct {
+ bytes: [8]u8,
+
+ pub fn get(self: @This()) []const u8 {
+ const end = for (self.bytes) |byte, i| {
+ if (byte == 0) break i;
+ } else self.bytes.len;
+ return self.bytes[0..end];
+ }
+ },
+ str_tok: struct {
+ /// Offset into `string_bytes`. Null-terminated.
+ start: u32,
+ /// Offset from Decl AST token index.
+ src_tok: u32,
+
+ pub fn get(self: @This(), code: Code) [:0]const u8 {
+ return code.nullTerminatedString(self.start);
+ }
- positionals: struct {
- /// This exists to be passed to the arg TZIR instruction, which
- /// needs it for debug info.
- name: []const u8,
+ pub fn src(self: @This()) LazySrcLoc {
+ return .{ .token_offset = self.src_tok };
+ }
+ },
+ /// Offset from Decl AST token index.
+ tok: ast.TokenIndex,
+ /// Offset from Decl AST node index.
+ node: i32,
+ int: u64,
+ array_type_sentinel: struct {
+ len: Ref,
+ /// index into extra, points to an `ArrayTypeSentinel`
+ payload_index: u32,
+ },
+ ptr_type_simple: struct {
+ is_allowzero: bool,
+ is_mutable: bool,
+ is_volatile: bool,
+ size: std.builtin.TypeInfo.Pointer.Size,
+ elem_type: Ref,
+ },
+ ptr_type: struct {
+ flags: packed struct {
+ is_allowzero: bool,
+ is_mutable: bool,
+ is_volatile: bool,
+ has_sentinel: bool,
+ has_align: bool,
+ has_bit_range: bool,
+ _: u2 = undefined,
+ },
+ size: std.builtin.TypeInfo.Pointer.Size,
+ /// Index into extra. See `PtrType`.
+ payload_index: u32,
+ },
+ int_type: struct {
+ /// Offset from Decl AST node index.
+ /// `Tag` determines which kind of AST node this points to.
+ src_node: i32,
+ signedness: std.builtin.Signedness,
+ bit_count: u16,
+
+ pub fn src(self: @This()) LazySrcLoc {
+ return .{ .node_offset = self.src_node };
+ }
+ },
+ bool_br: struct {
+ lhs: Ref,
+ /// Points to a `Block`.
+ payload_index: u32,
+ },
+ param_type: struct {
+ callee: Ref,
+ param_index: u32,
+ },
+ @"unreachable": struct {
+ /// Offset from Decl AST node index.
+ /// `Tag` determines which kind of AST node this points to.
+ src_node: i32,
+ /// `false`: Not safety checked - the compiler will assume the
+ /// correctness of this instruction.
+ /// `true`: In safety-checked modes, this will generate a call
+ /// to the panic function unless it can be proven unreachable by the compiler.
+ safety: bool,
+
+ pub fn src(self: @This()) LazySrcLoc {
+ return .{ .node_offset = self.src_node };
+ }
+ },
+ @"break": struct {
+ block_inst: Index,
+ operand: Ref,
+ },
+ switch_capture: struct {
+ switch_inst: Index,
+ prong_index: u32,
},
- kw_args: struct {},
- };
- pub const Block = struct {
- pub const base_tag = Tag.block;
- base: Inst,
+ // Make sure we don't accidentally add a field to make this union
+ // bigger than expected. Note that in Debug builds, Zig is allowed
+ // to insert a secret field for safety checks.
+ comptime {
+ if (std.builtin.mode != .Debug) {
+ assert(@sizeOf(Data) == 8);
+ }
+ }
+ };
- positionals: struct {
- body: Body,
- },
- kw_args: struct {},
+ /// Stored in extra. Trailing is:
+ /// * output_name: u32 // index into string_bytes (null terminated) if output is present
+ /// * arg: Ref // for every args_len.
+ /// * constraint: u32 // index into string_bytes (null terminated) for every args_len.
+ /// * clobber: u32 // index into string_bytes (null terminated) for every clobbers_len.
+ pub const Asm = struct {
+ asm_source: Ref,
+ return_type: Ref,
+ /// May be omitted.
+ output: Ref,
+ args_len: u32,
+ clobbers_len: u32,
};
- pub const Break = struct {
- pub const base_tag = Tag.@"break";
- base: Inst,
+ /// This data is stored inside extra, with trailing parameter type indexes
+ /// according to `param_types_len`.
+ /// Each param type is a `Ref`.
+ pub const FnTypeCc = struct {
+ return_type: Ref,
+ cc: Ref,
+ param_types_len: u32,
+ };
- positionals: struct {
- block: *Block,
- operand: *Inst,
- },
- kw_args: struct {},
+ /// This data is stored inside extra, with trailing parameter type indexes
+ /// according to `param_types_len`.
+ /// Each param type is a `Ref`.
+ pub const FnType = struct {
+ return_type: Ref,
+ param_types_len: u32,
};
- pub const BreakVoid = struct {
- pub const base_tag = Tag.break_void;
- base: Inst,
+ /// This data is stored inside extra, with trailing operands according to `operands_len`.
+ /// Each operand is a `Ref`.
+ pub const MultiOp = struct {
+ operands_len: u32,
+ };
- positionals: struct {
- block: *Block,
- },
- kw_args: struct {},
+ /// This data is stored inside extra, with trailing operands according to `body_len`.
+ /// Each operand is an `Index`.
+ pub const Block = struct {
+ body_len: u32,
};
- // TODO break this into multiple call instructions to avoid paying the cost
- // of the calling convention field most of the time.
+ /// Stored inside extra, with trailing arguments according to `args_len`.
+ /// Each argument is a `Ref`.
pub const Call = struct {
- pub const base_tag = Tag.call;
- base: Inst,
-
- positionals: struct {
- func: *Inst,
- args: []*Inst,
- modifier: std.builtin.CallOptions.Modifier = .auto,
- },
- kw_args: struct {},
+ callee: Ref,
+ args_len: u32,
};
- pub const DeclRef = struct {
- pub const base_tag = Tag.decl_ref;
- base: Inst,
-
- positionals: struct {
- decl: *IrModule.Decl,
- },
- kw_args: struct {},
+ /// This data is stored inside extra, with two sets of trailing `Ref`:
+ /// * 0. the then body, according to `then_body_len`.
+ /// * 1. the else body, according to `else_body_len`.
+ pub const CondBr = struct {
+ condition: Ref,
+ then_body_len: u32,
+ else_body_len: u32,
};
- pub const DeclRefStr = struct {
- pub const base_tag = Tag.decl_ref_str;
- base: Inst,
-
- positionals: struct {
- name: *Inst,
- },
- kw_args: struct {},
+ /// Stored in extra. Depending on the flags in Data, there will be up to 4
+ /// trailing Ref fields:
+ /// 0. sentinel: Ref // if `has_sentinel` flag is set
+ /// 1. align: Ref // if `has_align` flag is set
+ /// 2. bit_start: Ref // if `has_bit_range` flag is set
+ /// 3. bit_end: Ref // if `has_bit_range` flag is set
+ pub const PtrType = struct {
+ elem_type: Ref,
};
- pub const DeclVal = struct {
- pub const base_tag = Tag.decl_val;
- base: Inst,
-
- positionals: struct {
- decl: *IrModule.Decl,
- },
- kw_args: struct {},
+ pub const ArrayTypeSentinel = struct {
+ sentinel: Ref,
+ elem_type: Ref,
};
- pub const CompileLog = struct {
- pub const base_tag = Tag.compile_log;
- base: Inst,
-
- positionals: struct {
- to_log: []*Inst,
- },
- kw_args: struct {},
+ pub const SliceStart = struct {
+ lhs: Ref,
+ start: Ref,
};
- pub const Const = struct {
- pub const base_tag = Tag.@"const";
- base: Inst,
-
- positionals: struct {
- typed_value: TypedValue,
- },
- kw_args: struct {},
+ pub const SliceEnd = struct {
+ lhs: Ref,
+ start: Ref,
+ end: Ref,
};
- pub const Str = struct {
- pub const base_tag = Tag.str;
- base: Inst,
-
- positionals: struct {
- bytes: []const u8,
- },
- kw_args: struct {},
+ pub const SliceSentinel = struct {
+ lhs: Ref,
+ start: Ref,
+ end: Ref,
+ sentinel: Ref,
};
- pub const Int = struct {
- pub const base_tag = Tag.int;
- base: Inst,
-
- positionals: struct {
- int: BigIntConst,
- },
- kw_args: struct {},
+ /// The meaning of these operands depends on the corresponding `Tag`.
+ pub const Bin = struct {
+ lhs: Ref,
+ rhs: Ref,
};
- pub const Loop = struct {
- pub const base_tag = Tag.loop;
- base: Inst,
+ /// This form is supported when there are no ranges, and exactly 1 item per block.
+ /// Depending on zir tag and len fields, extra fields trail
+ /// this one in the extra array.
+ /// 0. else_body { // If the tag has "_else" or "_under" in it.
+ /// body_len: u32,
+ /// body member Index for every body_len
+ /// }
+ /// 1. cases: {
+ /// item: Ref,
+ /// body_len: u32,
+ /// body member Index for every body_len
+ /// } for every cases_len
+ pub const SwitchBlock = struct {
+ operand: Ref,
+ cases_len: u32,
+ };
- positionals: struct {
- body: Body,
- },
- kw_args: struct {},
+ /// This form is required when there exists a block which has more than one item,
+ /// or a range.
+ /// Depending on zir tag and len fields, extra fields trail
+ /// this one in the extra array.
+ /// 0. else_body { // If the tag has "_else" or "_under" in it.
+ /// body_len: u32,
+ /// body member Index for every body_len
+ /// }
+ /// 1. scalar_cases: { // for every scalar_cases_len
+ /// item: Ref,
+ /// body_len: u32,
+ /// body member Index for every body_len
+ /// }
+ /// 2. multi_cases: { // for every multi_cases_len
+ /// items_len: u32,
+ /// ranges_len: u32,
+ /// body_len: u32,
+ /// item: Ref // for every items_len
+ /// ranges: { // for every ranges_len
+ /// item_first: Ref,
+ /// item_last: Ref,
+ /// }
+ /// body member Index for every body_len
+ /// }
+ pub const SwitchBlockMulti = struct {
+ operand: Ref,
+ scalar_cases_len: u32,
+ multi_cases_len: u32,
};
pub const Field = struct {
- base: Inst,
-
- positionals: struct {
- object: *Inst,
- field_name: []const u8,
- },
- kw_args: struct {},
+ lhs: Ref,
+ /// Offset into `string_bytes`.
+ field_name_start: u32,
};
pub const FieldNamed = struct {
- base: Inst,
-
- positionals: struct {
- object: *Inst,
- field_name: *Inst,
- },
- kw_args: struct {},
+ lhs: Ref,
+ field_name: Ref,
};
- pub const Asm = struct {
- pub const base_tag = Tag.@"asm";
- base: Inst,
-
- positionals: struct {
- asm_source: *Inst,
- return_type: *Inst,
- },
- kw_args: struct {
- @"volatile": bool = false,
- output: ?*Inst = null,
- inputs: []const []const u8 = &.{},
- clobbers: []const []const u8 = &.{},
- args: []*Inst = &[0]*Inst{},
- },
+ pub const As = struct {
+ dest_type: Ref,
+ operand: Ref,
};
- pub const Fn = struct {
- pub const base_tag = Tag.@"fn";
- base: Inst,
-
- positionals: struct {
- fn_type: *Inst,
- body: Body,
- },
- kw_args: struct {},
+ /// Trailing:
+ /// 0. has_bits: u32 // for every 16 fields
+ /// - sets of 2 bits:
+ /// 0b0X: whether corresponding field has an align expression
+ /// 0bX0: whether corresponding field has a default expression
+ /// 1. fields: { // for every fields_len
+ /// field_name: u32,
+ /// field_type: Ref,
+ /// align: Ref, // if corresponding bit is set
+ /// default_value: Ref, // if corresponding bit is set
+ /// }
+ pub const StructDecl = struct {
+ fields_len: u32,
};
- pub const FnType = struct {
- pub const base_tag = Tag.fn_type;
- base: Inst,
-
- positionals: struct {
- param_types: []*Inst,
- return_type: *Inst,
- },
- kw_args: struct {},
+ /// Trailing:
+ /// 0. has_bits: u32 // for every 32 fields
+ /// - the bit is whether corresponding field has an value expression
+ /// 1. field_name: u32 // for every field: null terminated string index
+ /// 2. value: Ref // for every field for which corresponding bit is set
+ pub const EnumDecl = struct {
+ /// Can be `Ref.none`.
+ tag_type: Ref,
+ fields_len: u32,
};
- pub const FnTypeCc = struct {
- pub const base_tag = Tag.fn_type_cc;
- base: Inst,
-
- positionals: struct {
- param_types: []*Inst,
- return_type: *Inst,
- cc: *Inst,
- },
- kw_args: struct {},
+ /// Trailing:
+ /// 0. has_bits: u32 // for every 10 fields (+1)
+ /// - first bit is special: set if and only if auto enum tag is enabled.
+ /// - sets of 3 bits:
+ /// 0b00X: whether corresponding field has a type expression
+ /// 0b0X0: whether corresponding field has a align expression
+ /// 0bX00: whether corresponding field has a tag value expression
+ /// 1. field_name: u32 // for every field: null terminated string index
+ /// 2. opt_exprs // Ref for every field for which corresponding bit is set
+ /// - interleaved. type if present, align if present, tag value if present.
+ pub const UnionDecl = struct {
+ /// Can be `Ref.none`.
+ tag_type: Ref,
+ fields_len: u32,
};
+};
- pub const IntType = struct {
- pub const base_tag = Tag.int_type;
- base: Inst,
-
- positionals: struct {
- signed: *Inst,
- bits: *Inst,
- },
- kw_args: struct {},
- };
+pub const SpecialProng = enum { none, @"else", under };
- pub const Export = struct {
- pub const base_tag = Tag.@"export";
- base: Inst,
+const Writer = struct {
+ gpa: *Allocator,
+ arena: *Allocator,
+ scope: *Module.Scope,
+ code: Code,
+ indent: usize,
+ param_count: usize,
- positionals: struct {
- symbol_name: *Inst,
- decl_name: []const u8,
- },
- kw_args: struct {},
- };
+ fn writeInstToStream(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const tags = self.code.instructions.items(.tag);
+ const tag = tags[inst];
+ try stream.print("= {s}(", .{@tagName(tags[inst])});
+ switch (tag) {
+ .array_type,
+ .as,
+ .coerce_result_ptr,
+ .elem_ptr,
+ .elem_val,
+ .intcast,
+ .store,
+ .store_to_block_ptr,
+ => try self.writeBin(stream, inst),
+
+ .alloc,
+ .alloc_mut,
+ .alloc_inferred,
+ .alloc_inferred_mut,
+ .indexable_ptr_len,
+ .bit_not,
+ .bool_not,
+ .negate,
+ .negate_wrap,
+ .call_none,
+ .call_none_chkused,
+ .compile_error,
+ .load,
+ .ensure_result_used,
+ .ensure_result_non_error,
+ .import,
+ .ptrtoint,
+ .ret_node,
+ .set_eval_branch_quota,
+ .resolve_inferred_alloc,
+ .optional_type,
+ .optional_type_from_ptr_elem,
+ .optional_payload_safe,
+ .optional_payload_unsafe,
+ .optional_payload_safe_ptr,
+ .optional_payload_unsafe_ptr,
+ .err_union_payload_safe,
+ .err_union_payload_unsafe,
+ .err_union_payload_safe_ptr,
+ .err_union_payload_unsafe_ptr,
+ .err_union_code,
+ .err_union_code_ptr,
+ .int_to_error,
+ .error_to_int,
+ .is_non_null,
+ .is_null,
+ .is_non_null_ptr,
+ .is_null_ptr,
+ .is_err,
+ .is_err_ptr,
+ .typeof,
+ .typeof_elem,
+ .struct_init_empty,
+ => try self.writeUnNode(stream, inst),
+
+ .ref,
+ .ret_tok,
+ .ret_coerce,
+ .ensure_err_payload_void,
+ => try self.writeUnTok(stream, inst),
+
+ .bool_br_and,
+ .bool_br_or,
+ => try self.writeBoolBr(stream, inst),
+
+ .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst),
+ .@"const" => try self.writeConst(stream, inst),
+ .param_type => try self.writeParamType(stream, inst),
+ .ptr_type_simple => try self.writePtrTypeSimple(stream, inst),
+ .ptr_type => try self.writePtrType(stream, inst),
+ .int => try self.writeInt(stream, inst),
+ .str => try self.writeStr(stream, inst),
+ .elided => try stream.writeAll(")"),
+ .int_type => try self.writeIntType(stream, inst),
+
+ .@"break",
+ .break_inline,
+ => try self.writeBreak(stream, inst),
+
+ .@"asm",
+ .asm_volatile,
+ .elem_ptr_node,
+ .elem_val_node,
+ .field_ptr_named,
+ .field_val_named,
+ .floatcast,
+ .slice_start,
+ .slice_end,
+ .slice_sentinel,
+ .union_decl,
+ .enum_decl,
+ => try self.writePlNode(stream, inst),
+
+ .add,
+ .addwrap,
+ .array_cat,
+ .array_mul,
+ .mul,
+ .mulwrap,
+ .sub,
+ .subwrap,
+ .bool_and,
+ .bool_or,
+ .cmp_lt,
+ .cmp_lte,
+ .cmp_eq,
+ .cmp_gte,
+ .cmp_gt,
+ .cmp_neq,
+ .div,
+ .mod_rem,
+ .shl,
+ .shr,
+ .xor,
+ .store_node,
+ .error_union_type,
+ .merge_error_sets,
+ .bit_and,
+ .bit_or,
+ => try self.writePlNodeBin(stream, inst),
+
+ .call,
+ .call_chkused,
+ .call_compile_time,
+ => try self.writePlNodeCall(stream, inst),
+
+ .block,
+ .block_inline,
+ .loop,
+ .validate_struct_init_ptr,
+ => try self.writePlNodeBlock(stream, inst),
+
+ .condbr,
+ .condbr_inline,
+ => try self.writePlNodeCondBr(stream, inst),
+
+ .struct_decl,
+ .struct_decl_packed,
+ .struct_decl_extern,
+ => try self.writeStructDecl(stream, inst),
+
+ .switch_block => try self.writePlNodeSwitchBr(stream, inst, .none),
+ .switch_block_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"),
+ .switch_block_under => try self.writePlNodeSwitchBr(stream, inst, .under),
+ .switch_block_ref => try self.writePlNodeSwitchBr(stream, inst, .none),
+ .switch_block_ref_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"),
+ .switch_block_ref_under => try self.writePlNodeSwitchBr(stream, inst, .under),
+
+ .switch_block_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none),
+ .switch_block_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"),
+ .switch_block_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under),
+ .switch_block_ref_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none),
+ .switch_block_ref_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"),
+ .switch_block_ref_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under),
+
+ .compile_log,
+ .typeof_peer,
+ => try self.writePlNodeMultiOp(stream, inst),
+
+ .decl_ref,
+ .decl_val,
+ => try self.writePlNodeDecl(stream, inst),
+
+ .field_ptr,
+ .field_val,
+ => try self.writePlNodeField(stream, inst),
+
+ .as_node => try self.writeAs(stream, inst),
+
+ .breakpoint,
+ .opaque_decl,
+ .dbg_stmt_node,
+ .ret_ptr,
+ .ret_type,
+ .repeat,
+ .repeat_inline,
+ => try self.writeNode(stream, inst),
+
+ .error_value,
+ .enum_literal,
+ => try self.writeStrTok(stream, inst),
+
+ .fn_type => try self.writeFnType(stream, inst, false),
+ .fn_type_cc => try self.writeFnTypeCc(stream, inst, false),
+ .fn_type_var_args => try self.writeFnType(stream, inst, true),
+ .fn_type_cc_var_args => try self.writeFnTypeCc(stream, inst, true),
+
+ .@"unreachable" => try self.writeUnreachable(stream, inst),
+
+ .enum_literal_small => try self.writeSmallStr(stream, inst),
+
+ .switch_capture,
+ .switch_capture_ref,
+ .switch_capture_multi,
+ .switch_capture_multi_ref,
+ .switch_capture_else,
+ .switch_capture_else_ref,
+ => try self.writeSwitchCapture(stream, inst),
+
+ .bitcast,
+ .bitcast_result_ptr,
+ .store_to_inferred_ptr,
+ => try stream.writeAll("TODO)"),
+ }
+ }
- pub const ParamType = struct {
- pub const base_tag = Tag.param_type;
- base: Inst,
-
- positionals: struct {
- func: *Inst,
- arg_index: usize,
- },
- kw_args: struct {},
- };
-
- pub const Primitive = struct {
- pub const base_tag = Tag.primitive;
- base: Inst,
-
- positionals: struct {
- tag: Builtin,
- },
- kw_args: struct {},
-
- pub const Builtin = enum {
- i8,
- u8,
- i16,
- u16,
- i32,
- u32,
- i64,
- u64,
- isize,
- usize,
- c_short,
- c_ushort,
- c_int,
- c_uint,
- c_long,
- c_ulong,
- c_longlong,
- c_ulonglong,
- c_longdouble,
- c_void,
- f16,
- f32,
- f64,
- f128,
- bool,
- void,
- noreturn,
- type,
- anyerror,
- comptime_int,
- comptime_float,
- @"true",
- @"false",
- @"null",
- @"undefined",
- void_value,
-
- pub fn toTypedValue(self: Builtin) TypedValue {
- return switch (self) {
- .i8 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i8_type) },
- .u8 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u8_type) },
- .i16 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i16_type) },
- .u16 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u16_type) },
- .i32 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i32_type) },
- .u32 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u32_type) },
- .i64 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i64_type) },
- .u64 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u64_type) },
- .isize => .{ .ty = Type.initTag(.type), .val = Value.initTag(.isize_type) },
- .usize => .{ .ty = Type.initTag(.type), .val = Value.initTag(.usize_type) },
- .c_short => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_short_type) },
- .c_ushort => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_ushort_type) },
- .c_int => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_int_type) },
- .c_uint => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_uint_type) },
- .c_long => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_long_type) },
- .c_ulong => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_ulong_type) },
- .c_longlong => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_longlong_type) },
- .c_ulonglong => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_ulonglong_type) },
- .c_longdouble => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_longdouble_type) },
- .c_void => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_void_type) },
- .f16 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.f16_type) },
- .f32 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.f32_type) },
- .f64 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.f64_type) },
- .f128 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.f128_type) },
- .bool => .{ .ty = Type.initTag(.type), .val = Value.initTag(.bool_type) },
- .void => .{ .ty = Type.initTag(.type), .val = Value.initTag(.void_type) },
- .noreturn => .{ .ty = Type.initTag(.type), .val = Value.initTag(.noreturn_type) },
- .type => .{ .ty = Type.initTag(.type), .val = Value.initTag(.type_type) },
- .anyerror => .{ .ty = Type.initTag(.type), .val = Value.initTag(.anyerror_type) },
- .comptime_int => .{ .ty = Type.initTag(.type), .val = Value.initTag(.comptime_int_type) },
- .comptime_float => .{ .ty = Type.initTag(.type), .val = Value.initTag(.comptime_float_type) },
- .@"true" => .{ .ty = Type.initTag(.bool), .val = Value.initTag(.bool_true) },
- .@"false" => .{ .ty = Type.initTag(.bool), .val = Value.initTag(.bool_false) },
- .@"null" => .{ .ty = Type.initTag(.@"null"), .val = Value.initTag(.null_value) },
- .@"undefined" => .{ .ty = Type.initTag(.@"undefined"), .val = Value.initTag(.undef) },
- .void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.void_value) },
- };
- }
- };
- };
-
- pub const Elem = struct {
- base: Inst,
-
- positionals: struct {
- array: *Inst,
- index: *Inst,
- },
- kw_args: struct {},
- };
-
- pub const CondBr = struct {
- pub const base_tag = Tag.condbr;
- base: Inst,
-
- positionals: struct {
- condition: *Inst,
- then_body: Body,
- else_body: Body,
- },
- kw_args: struct {},
- };
-
- pub const PtrType = struct {
- pub const base_tag = Tag.ptr_type;
- base: Inst,
-
- positionals: struct {
- child_type: *Inst,
- },
- kw_args: struct {
- @"allowzero": bool = false,
- @"align": ?*Inst = null,
- align_bit_start: ?*Inst = null,
- align_bit_end: ?*Inst = null,
- mutable: bool = true,
- @"volatile": bool = false,
- sentinel: ?*Inst = null,
- size: std.builtin.TypeInfo.Pointer.Size = .One,
- },
- };
-
- pub const ArrayTypeSentinel = struct {
- pub const base_tag = Tag.array_type_sentinel;
- base: Inst,
-
- positionals: struct {
- len: *Inst,
- sentinel: *Inst,
- elem_type: *Inst,
- },
- kw_args: struct {},
- };
-
- pub const EnumLiteral = struct {
- pub const base_tag = Tag.enum_literal;
- base: Inst,
-
- positionals: struct {
- name: []const u8,
- },
- kw_args: struct {},
- };
-
- pub const ErrorSet = struct {
- pub const base_tag = Tag.error_set;
- base: Inst,
-
- positionals: struct {
- fields: [][]const u8,
- },
- kw_args: struct {},
- };
-
- pub const ErrorValue = struct {
- pub const base_tag = Tag.error_value;
- base: Inst,
+ fn writeBin(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].bin;
+ try self.writeInstRef(stream, inst_data.lhs);
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, inst_data.rhs);
+ try stream.writeByte(')');
+ }
- positionals: struct {
- name: []const u8,
- },
- kw_args: struct {},
- };
+ fn writeUnNode(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].un_node;
+ try self.writeInstRef(stream, inst_data.operand);
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- pub const Slice = struct {
- pub const base_tag = Tag.slice;
- base: Inst,
+ fn writeUnTok(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].un_tok;
+ try self.writeInstRef(stream, inst_data.operand);
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- positionals: struct {
- array_ptr: *Inst,
- start: *Inst,
- },
- kw_args: struct {
- end: ?*Inst = null,
- sentinel: ?*Inst = null,
- },
- };
+ fn writeArrayTypeSentinel(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].array_type_sentinel;
+ try stream.writeAll("TODO)");
+ }
- pub const TypeOfPeer = struct {
- pub const base_tag = .typeof_peer;
- base: Inst,
- positionals: struct {
- items: []*Inst,
- },
- kw_args: struct {},
- };
+ fn writeConst(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].@"const";
+ try stream.writeAll("TODO)");
+ }
- pub const ContainerFieldNamed = struct {
- pub const base_tag = Tag.container_field_named;
- base: Inst,
+ fn writeParamType(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].param_type;
+ try self.writeInstRef(stream, inst_data.callee);
+ try stream.print(", {d})", .{inst_data.param_index});
+ }
- positionals: struct {
- bytes: []const u8,
- },
- kw_args: struct {},
- };
+ fn writePtrTypeSimple(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].ptr_type_simple;
+ try stream.writeAll("TODO)");
+ }
- pub const ContainerFieldTyped = struct {
- pub const base_tag = Tag.container_field_typed;
- base: Inst,
+ fn writePtrType(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].ptr_type;
+ try stream.writeAll("TODO)");
+ }
- positionals: struct {
- bytes: []const u8,
- ty: *Inst,
- },
- kw_args: struct {},
- };
+ fn writeInt(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].int;
+ try stream.print("{d})", .{inst_data});
+ }
- pub const ContainerField = struct {
- pub const base_tag = Tag.container_field;
- base: Inst,
+ fn writeStr(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].str;
+ const str = inst_data.get(self.code);
+ try stream.print("\"{}\")", .{std.zig.fmtEscapes(str)});
+ }
- positionals: struct {
- bytes: []const u8,
- },
- kw_args: struct {
- ty: ?*Inst = null,
- init: ?*Inst = null,
- alignment: ?*Inst = null,
- is_comptime: bool = false,
- },
- };
+ fn writePlNode(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ try stream.writeAll("TODO) ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- pub const EnumType = struct {
- pub const base_tag = Tag.enum_type;
- base: Inst,
+ fn writePlNodeBin(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.Bin, inst_data.payload_index).data;
+ try self.writeInstRef(stream, extra.lhs);
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, extra.rhs);
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- positionals: struct {
- fields: []*Inst,
- },
- kw_args: struct {
- tag_type: ?*Inst = null,
- layout: std.builtin.TypeInfo.ContainerLayout = .Auto,
- },
- };
+ fn writePlNodeCall(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.Call, inst_data.payload_index);
+ const args = self.code.refSlice(extra.end, extra.data.args_len);
- pub const StructType = struct {
- pub const base_tag = Tag.struct_type;
- base: Inst,
+ try self.writeInstRef(stream, extra.data.callee);
+ try stream.writeAll(", [");
+ for (args) |arg, i| {
+ if (i != 0) try stream.writeAll(", ");
+ try self.writeInstRef(stream, arg);
+ }
+ try stream.writeAll("]) ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- positionals: struct {
- fields: []*Inst,
- },
- kw_args: struct {
- layout: std.builtin.TypeInfo.ContainerLayout = .Auto,
- },
- };
+ fn writePlNodeBlock(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.Block, inst_data.payload_index);
+ const body = self.code.extra[extra.end..][0..extra.data.body_len];
+ try stream.writeAll("{\n");
+ self.indent += 2;
+ try self.writeBody(stream, body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}) ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- pub const UnionType = struct {
- pub const base_tag = Tag.union_type;
- base: Inst,
+ fn writePlNodeCondBr(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.CondBr, inst_data.payload_index);
+ const then_body = self.code.extra[extra.end..][0..extra.data.then_body_len];
+ const else_body = self.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
+ try self.writeInstRef(stream, extra.data.condition);
+ try stream.writeAll(", {\n");
+ self.indent += 2;
+ try self.writeBody(stream, then_body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}, {\n");
+ self.indent += 2;
+ try self.writeBody(stream, else_body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}) ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- positionals: struct {
- fields: []*Inst,
- },
- kw_args: struct {
- init_inst: ?*Inst = null,
- has_enum_token: bool,
- layout: std.builtin.TypeInfo.ContainerLayout = .Auto,
- },
- };
+ fn writeStructDecl(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.StructDecl, inst_data.payload_index);
+ const fields_len = extra.data.fields_len;
+ const bit_bags_count = std.math.divCeil(usize, fields_len, 16) catch unreachable;
+
+ try stream.writeAll("{\n");
+ self.indent += 2;
+
+ var field_index: usize = extra.end + bit_bags_count;
+ var bit_bag_index: usize = extra.end;
+ var cur_bit_bag: u32 = undefined;
+ var field_i: u32 = 0;
+ while (field_i < fields_len) : (field_i += 1) {
+ if (field_i % 16 == 0) {
+ cur_bit_bag = self.code.extra[bit_bag_index];
+ bit_bag_index += 1;
+ }
+ const has_align = @truncate(u1, cur_bit_bag) != 0;
+ cur_bit_bag >>= 1;
+ const has_default = @truncate(u1, cur_bit_bag) != 0;
+ cur_bit_bag >>= 1;
+
+ const field_name = self.code.nullTerminatedString(self.code.extra[field_index]);
+ field_index += 1;
+ const field_type = @intToEnum(Inst.Ref, self.code.extra[field_index]);
+ field_index += 1;
+
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.print("{}: ", .{std.zig.fmtId(field_name)});
+ try self.writeInstRef(stream, field_type);
+
+ if (has_align) {
+ const align_ref = @intToEnum(Inst.Ref, self.code.extra[field_index]);
+ field_index += 1;
+
+ try stream.writeAll(" align(");
+ try self.writeInstRef(stream, align_ref);
+ try stream.writeAll(")");
+ }
+ if (has_default) {
+ const default_ref = @intToEnum(Inst.Ref, self.code.extra[field_index]);
+ field_index += 1;
- pub const SwitchBr = struct {
- base: Inst,
-
- positionals: struct {
- target: *Inst,
- /// List of all individual items and ranges
- items: []*Inst,
- cases: []Case,
- else_body: Body,
- /// Pointer to first range if such exists.
- range: ?*Inst = null,
- special_prong: SpecialProng = .none,
- },
- kw_args: struct {},
+ try stream.writeAll(" = ");
+ try self.writeInstRef(stream, default_ref);
+ }
+ try stream.writeAll(",\n");
+ }
- pub const SpecialProng = enum {
- none,
- @"else",
- underscore,
- };
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}) ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- pub const Case = struct {
- item: *Inst,
- body: Body,
+ fn writePlNodeSwitchBr(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ special_prong: SpecialProng,
+ ) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.SwitchBlock, inst_data.payload_index);
+ const special: struct {
+ body: []const Inst.Index,
+ end: usize,
+ } = switch (special_prong) {
+ .none => .{ .body = &.{}, .end = extra.end },
+ .under, .@"else" => blk: {
+ const body_len = self.code.extra[extra.end];
+ const extra_body_start = extra.end + 1;
+ break :blk .{
+ .body = self.code.extra[extra_body_start..][0..body_len],
+ .end = extra_body_start + body_len,
+ };
+ },
};
- };
-};
-pub const ErrorMsg = struct {
- byte_offset: usize,
- msg: []const u8,
-};
-
-pub const Body = struct {
- instructions: []*Inst,
-};
+ try self.writeInstRef(stream, extra.data.operand);
-pub const Module = struct {
- decls: []*Decl,
- arena: std.heap.ArenaAllocator,
- error_msg: ?ErrorMsg = null,
- metadata: std.AutoHashMap(*Inst, MetaData),
- body_metadata: std.AutoHashMap(*Body, BodyMetaData),
-
- pub const Decl = struct {
- name: []const u8,
-
- /// Hash of slice into the source of the part after the = and before the next instruction.
- contents_hash: std.zig.SrcHash,
+ if (special.body.len != 0) {
+ const prong_name = switch (special_prong) {
+ .@"else" => "else",
+ .under => "_",
+ else => unreachable,
+ };
+ try stream.print(", {s} => {{\n", .{prong_name});
+ self.indent += 2;
+ try self.writeBody(stream, special.body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}");
+ }
- inst: *Inst,
- };
+ var extra_index: usize = special.end;
+ {
+ var scalar_i: usize = 0;
+ while (scalar_i < extra.data.cases_len) : (scalar_i += 1) {
+ const item_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]);
+ extra_index += 1;
+ const body_len = self.code.extra[extra_index];
+ extra_index += 1;
+ const body = self.code.extra[extra_index..][0..body_len];
+ extra_index += body_len;
- pub const MetaData = struct {
- deaths: ir.Inst.DeathsInt,
- addr: usize,
- };
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, item_ref);
+ try stream.writeAll(" => {\n");
+ self.indent += 2;
+ try self.writeBody(stream, body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}");
+ }
+ }
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- pub const BodyMetaData = struct {
- deaths: []*Inst,
- };
+ fn writePlNodeSwitchBlockMulti(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ special_prong: SpecialProng,
+ ) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.SwitchBlockMulti, inst_data.payload_index);
+ const special: struct {
+ body: []const Inst.Index,
+ end: usize,
+ } = switch (special_prong) {
+ .none => .{ .body = &.{}, .end = extra.end },
+ .under, .@"else" => blk: {
+ const body_len = self.code.extra[extra.end];
+ const extra_body_start = extra.end + 1;
+ break :blk .{
+ .body = self.code.extra[extra_body_start..][0..body_len],
+ .end = extra_body_start + body_len,
+ };
+ },
+ };
- pub fn deinit(self: *Module, allocator: *Allocator) void {
- self.metadata.deinit();
- self.body_metadata.deinit();
- allocator.free(self.decls);
- self.arena.deinit();
- self.* = undefined;
- }
+ try self.writeInstRef(stream, extra.data.operand);
- /// This is a debugging utility for rendering the tree to stderr.
- pub fn dump(self: Module) void {
- self.writeToStream(std.heap.page_allocator, std.io.getStdErr().writer()) catch {};
- }
+ if (special.body.len != 0) {
+ const prong_name = switch (special_prong) {
+ .@"else" => "else",
+ .under => "_",
+ else => unreachable,
+ };
+ try stream.print(", {s} => {{\n", .{prong_name});
+ self.indent += 2;
+ try self.writeBody(stream, special.body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}");
+ }
- const DeclAndIndex = struct {
- decl: *Decl,
- index: usize,
- };
+ var extra_index: usize = special.end;
+ {
+ var scalar_i: usize = 0;
+ while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) {
+ const item_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]);
+ extra_index += 1;
+ const body_len = self.code.extra[extra_index];
+ extra_index += 1;
+ const body = self.code.extra[extra_index..][0..body_len];
+ extra_index += body_len;
- /// TODO Look into making a table to speed this up.
- pub fn findDecl(self: Module, name: []const u8) ?DeclAndIndex {
- for (self.decls) |decl, i| {
- if (mem.eql(u8, decl.name, name)) {
- return DeclAndIndex{
- .decl = decl,
- .index = i,
- };
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, item_ref);
+ try stream.writeAll(" => {\n");
+ self.indent += 2;
+ try self.writeBody(stream, body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}");
}
}
- return null;
- }
+ {
+ var multi_i: usize = 0;
+ while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) {
+ const items_len = self.code.extra[extra_index];
+ extra_index += 1;
+ const ranges_len = self.code.extra[extra_index];
+ extra_index += 1;
+ const body_len = self.code.extra[extra_index];
+ extra_index += 1;
+ const items = self.code.refSlice(extra_index, items_len);
+ extra_index += items_len;
+
+ for (items) |item_ref| {
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, item_ref);
+ }
- pub fn findInstDecl(self: Module, inst: *Inst) ?DeclAndIndex {
- for (self.decls) |decl, i| {
- if (decl.inst == inst) {
- return DeclAndIndex{
- .decl = decl,
- .index = i,
- };
+ var range_i: usize = 0;
+ while (range_i < ranges_len) : (range_i += 1) {
+ const item_first = @intToEnum(Inst.Ref, self.code.extra[extra_index]);
+ extra_index += 1;
+ const item_last = @intToEnum(Inst.Ref, self.code.extra[extra_index]);
+ extra_index += 1;
+
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, item_first);
+ try stream.writeAll("...");
+ try self.writeInstRef(stream, item_last);
+ }
+
+ const body = self.code.extra[extra_index..][0..body_len];
+ extra_index += body_len;
+ try stream.writeAll(" => {\n");
+ self.indent += 2;
+ try self.writeBody(stream, body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}");
}
}
- return null;
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
}
- /// The allocator is used for temporary storage, but this function always returns
- /// with no resources allocated.
- pub fn writeToStream(self: Module, allocator: *Allocator, stream: anytype) !void {
- var write = Writer{
- .module = &self,
- .inst_table = InstPtrTable.init(allocator),
- .block_table = std.AutoHashMap(*Inst.Block, []const u8).init(allocator),
- .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator),
- .arena = std.heap.ArenaAllocator.init(allocator),
- .indent = 2,
- .next_instr_index = undefined,
- };
- defer write.arena.deinit();
- defer write.inst_table.deinit();
- defer write.block_table.deinit();
- defer write.loop_table.deinit();
-
- // First, build a map of *Inst to @ or % indexes
- try write.inst_table.ensureCapacity(@intCast(u32, self.decls.len));
+ fn writePlNodeMultiOp(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.MultiOp, inst_data.payload_index);
+ const operands = self.code.refSlice(extra.end, extra.data.operands_len);
- for (self.decls) |decl, decl_i| {
- try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name });
+ for (operands) |operand, i| {
+ if (i != 0) try stream.writeAll(", ");
+ try self.writeInstRef(stream, operand);
}
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- for (self.decls) |decl, i| {
- write.next_instr_index = 0;
- try stream.print("@{s} ", .{decl.name});
- try write.writeInstToStream(stream, decl.inst);
- try stream.writeByte('\n');
- }
+ fn writePlNodeDecl(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const decl = self.code.decls[inst_data.payload_index];
+ try stream.print("{s}) ", .{decl.name});
+ try self.writeSrc(stream, inst_data.src());
}
-};
-const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize, name: []const u8 });
+ fn writePlNodeField(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.Field, inst_data.payload_index).data;
+ const name = self.code.nullTerminatedString(extra.field_name_start);
+ try self.writeInstRef(stream, extra.lhs);
+ try stream.print(", \"{}\") ", .{std.zig.fmtEscapes(name)});
+ try self.writeSrc(stream, inst_data.src());
+ }
-const Writer = struct {
- module: *const Module,
- inst_table: InstPtrTable,
- block_table: std.AutoHashMap(*Inst.Block, []const u8),
- loop_table: std.AutoHashMap(*Inst.Loop, []const u8),
- arena: std.heap.ArenaAllocator,
- indent: usize,
- next_instr_index: usize,
+ fn writeAs(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.As, inst_data.payload_index).data;
+ try self.writeInstRef(stream, extra.dest_type);
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, extra.operand);
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
- fn writeInstToStream(
+ fn writeNode(
self: *Writer,
stream: anytype,
- inst: *Inst,
+ inst: Inst.Index,
) (@TypeOf(stream).Error || error{OutOfMemory})!void {
- inline for (@typeInfo(Inst.Tag).Enum.fields) |enum_field| {
- const expected_tag = @field(Inst.Tag, enum_field.name);
- if (inst.tag == expected_tag) {
- return self.writeInstToStreamGeneric(stream, expected_tag, inst);
- }
- }
- unreachable; // all tags handled
+ const src_node = self.code.instructions.items(.data)[inst].node;
+ const src: LazySrcLoc = .{ .node_offset = src_node };
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, src);
}
- fn writeInstToStreamGeneric(
+ fn writeStrTok(
self: *Writer,
stream: anytype,
- comptime inst_tag: Inst.Tag,
- base: *Inst,
+ inst: Inst.Index,
) (@TypeOf(stream).Error || error{OutOfMemory})!void {
- const SpecificInst = inst_tag.Type();
- const inst = @fieldParentPtr(SpecificInst, "base", base);
- const Positionals = @TypeOf(inst.positionals);
- try stream.writeAll("= " ++ @tagName(inst_tag) ++ "(");
- const pos_fields = @typeInfo(Positionals).Struct.fields;
- inline for (pos_fields) |arg_field, i| {
- if (i != 0) {
- try stream.writeAll(", ");
- }
- try self.writeParamToStream(stream, &@field(inst.positionals, arg_field.name));
- }
+ const inst_data = self.code.instructions.items(.data)[inst].str_tok;
+ const str = inst_data.get(self.code);
+ try stream.print("\"{}\") ", .{std.zig.fmtEscapes(str)});
+ try self.writeSrc(stream, inst_data.src());
+ }
- comptime var need_comma = pos_fields.len != 0;
- const KW_Args = @TypeOf(inst.kw_args);
- inline for (@typeInfo(KW_Args).Struct.fields) |arg_field, i| {
- if (@typeInfo(arg_field.field_type) == .Optional) {
- if (@field(inst.kw_args, arg_field.name)) |non_optional| {
- if (need_comma) try stream.writeAll(", ");
- try stream.print("{s}=", .{arg_field.name});
- try self.writeParamToStream(stream, &non_optional);
- need_comma = true;
- }
- } else {
- if (need_comma) try stream.writeAll(", ");
- try stream.print("{s}=", .{arg_field.name});
- try self.writeParamToStream(stream, &@field(inst.kw_args, arg_field.name));
- need_comma = true;
- }
- }
+ fn writeFnType(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ var_args: bool,
+ ) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = self.code.extraData(Inst.FnType, inst_data.payload_index);
+ const param_types = self.code.refSlice(extra.end, extra.data.param_types_len);
+ return self.writeFnTypeCommon(stream, param_types, extra.data.return_type, var_args, .none, src);
+ }
- try stream.writeByte(')');
+ fn writeFnTypeCc(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ var_args: bool,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const extra = self.code.extraData(Inst.FnTypeCc, inst_data.payload_index);
+ const param_types = self.code.refSlice(extra.end, extra.data.param_types_len);
+ const cc = extra.data.cc;
+ return self.writeFnTypeCommon(stream, param_types, extra.data.return_type, var_args, cc, src);
}
- fn writeParamToStream(self: *Writer, stream: anytype, param_ptr: anytype) !void {
- const param = param_ptr.*;
- if (@typeInfo(@TypeOf(param)) == .Enum) {
- return stream.writeAll(@tagName(param));
- }
- switch (@TypeOf(param)) {
- *Inst => return self.writeInstParamToStream(stream, param),
- ?*Inst => return self.writeInstParamToStream(stream, param.?),
- []*Inst => {
- try stream.writeByte('[');
- for (param) |inst, i| {
- if (i != 0) {
- try stream.writeAll(", ");
- }
- try self.writeInstParamToStream(stream, inst);
- }
- try stream.writeByte(']');
- },
- Body => {
- try stream.writeAll("{\n");
- if (self.module.body_metadata.get(param_ptr)) |metadata| {
- if (metadata.deaths.len > 0) {
- try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("; deaths={");
- for (metadata.deaths) |death, i| {
- if (i != 0) try stream.writeAll(", ");
- try self.writeInstParamToStream(stream, death);
- }
- try stream.writeAll("}\n");
- }
- }
+ fn writeBoolBr(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].bool_br;
+ const extra = self.code.extraData(Inst.Block, inst_data.payload_index);
+ const body = self.code.extra[extra.end..][0..extra.data.body_len];
+ try self.writeInstRef(stream, inst_data.lhs);
+ try stream.writeAll(", {\n");
+ self.indent += 2;
+ try self.writeBody(stream, body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("})");
+ }
- for (param.instructions) |inst| {
- const my_i = self.next_instr_index;
- self.next_instr_index += 1;
- try self.inst_table.putNoClobber(inst, .{ .inst = inst, .index = my_i, .name = undefined });
- try stream.writeByteNTimes(' ', self.indent);
- try stream.print("%{d} ", .{my_i});
- if (inst.cast(Inst.Block)) |block| {
- const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{d}", .{my_i});
- try self.block_table.put(block, name);
- } else if (inst.cast(Inst.Loop)) |loop| {
- const name = try std.fmt.allocPrint(&self.arena.allocator, "loop_{d}", .{my_i});
- try self.loop_table.put(loop, name);
- }
- self.indent += 2;
- try self.writeInstToStream(stream, inst);
- if (self.module.metadata.get(inst)) |metadata| {
- try stream.print(" ; deaths=0b{b}", .{metadata.deaths});
- // This is conditionally compiled in because addresses mess up the tests due
- // to Address Space Layout Randomization. It's super useful when debugging
- // codegen.zig though.
- if (!std.builtin.is_test) {
- try stream.print(" 0x{x}", .{metadata.addr});
- }
- }
- self.indent -= 2;
- try stream.writeByte('\n');
- }
- try stream.writeByteNTimes(' ', self.indent - 2);
- try stream.writeByte('}');
- },
- bool => return stream.writeByte("01"[@boolToInt(param)]),
- []u8, []const u8 => return stream.print("\"{}\"", .{std.zig.fmtEscapes(param)}),
- BigIntConst, usize => return stream.print("{}", .{param}),
- TypedValue => return stream.print("TypedValue{{ .ty = {}, .val = {}}}", .{ param.ty, param.val }),
- *IrModule.Decl => return stream.print("Decl({s})", .{param.name}),
- *Inst.Block => {
- const name = self.block_table.get(param) orelse "!BADREF!";
- return stream.print("\"{}\"", .{std.zig.fmtEscapes(name)});
- },
- *Inst.Loop => {
- const name = self.loop_table.get(param).?;
- return stream.print("\"{}\"", .{std.zig.fmtEscapes(name)});
- },
- [][]const u8, []const []const u8 => {
- try stream.writeByte('[');
- for (param) |str, i| {
- if (i != 0) {
- try stream.writeAll(", ");
- }
- try stream.print("\"{}\"", .{std.zig.fmtEscapes(str)});
- }
- try stream.writeByte(']');
- },
- []Inst.SwitchBr.Case => {
- if (param.len == 0) {
- return stream.writeAll("{}");
- }
- try stream.writeAll("{\n");
- for (param) |*case, i| {
- if (i != 0) {
- try stream.writeAll(",\n");
- }
- try stream.writeByteNTimes(' ', self.indent);
- self.indent += 2;
- try self.writeParamToStream(stream, &case.item);
- try stream.writeAll(" => ");
- try self.writeParamToStream(stream, &case.body);
- self.indent -= 2;
- }
- try stream.writeByte('\n');
- try stream.writeByteNTimes(' ', self.indent - 2);
- try stream.writeByte('}');
- },
- else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),
- }
+ fn writeIntType(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const int_type = self.code.instructions.items(.data)[inst].int_type;
+ const prefix: u8 = switch (int_type.signedness) {
+ .signed => 'i',
+ .unsigned => 'u',
+ };
+ try stream.print("{c}{d}) ", .{ prefix, int_type.bit_count });
+ try self.writeSrc(stream, int_type.src());
}
- fn writeInstParamToStream(self: *Writer, stream: anytype, inst: *Inst) !void {
- if (self.inst_table.get(inst)) |info| {
- if (info.index) |i| {
- try stream.print("%{d}", .{info.index});
- } else {
- try stream.print("@{s}", .{info.name});
- }
- } else if (inst.cast(Inst.DeclVal)) |decl_val| {
- try stream.print("@{s}", .{decl_val.positionals.decl.name});
- } else {
- // This should be unreachable in theory, but since ZIR is used for debugging the compiler
- // we output some debug text instead.
- try stream.print("?{s}?", .{@tagName(inst.tag)});
- }
+ fn writeBreak(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].@"break";
+
+ try self.writeInstIndex(stream, inst_data.block_inst);
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, inst_data.operand);
+ try stream.writeAll(")");
}
-};
-/// For debugging purposes, prints a function representation to stderr.
-pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void {
- const allocator = old_module.gpa;
- var ctx: DumpTzir = .{
- .allocator = allocator,
- .arena = std.heap.ArenaAllocator.init(allocator),
- .old_module = &old_module,
- .module_fn = module_fn,
- .indent = 2,
- .inst_table = DumpTzir.InstTable.init(allocator),
- .partial_inst_table = DumpTzir.InstTable.init(allocator),
- .const_table = DumpTzir.InstTable.init(allocator),
- };
- defer ctx.inst_table.deinit();
- defer ctx.partial_inst_table.deinit();
- defer ctx.const_table.deinit();
- defer ctx.arena.deinit();
-
- switch (module_fn.state) {
- .queued => std.debug.print("(queued)", .{}),
- .inline_only => std.debug.print("(inline_only)", .{}),
- .in_progress => std.debug.print("(in_progress)", .{}),
- .sema_failure => std.debug.print("(sema_failure)", .{}),
- .dependency_failure => std.debug.print("(dependency_failure)", .{}),
- .success => {
- const writer = std.io.getStdErr().writer();
- ctx.dump(module_fn.body, writer) catch @panic("failed to dump TZIR");
- },
+ fn writeUnreachable(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].@"unreachable";
+ const safety_str = if (inst_data.safety) "safe" else "unsafe";
+ try stream.print("{s}) ", .{safety_str});
+ try self.writeSrc(stream, inst_data.src());
}
-}
-const DumpTzir = struct {
- allocator: *Allocator,
- arena: std.heap.ArenaAllocator,
- old_module: *const IrModule,
- module_fn: *IrModule.Fn,
- indent: usize,
- inst_table: InstTable,
- partial_inst_table: InstTable,
- const_table: InstTable,
- next_index: usize = 0,
- next_partial_index: usize = 0,
- next_const_index: usize = 0,
-
- const InstTable = std.AutoArrayHashMap(*ir.Inst, usize);
-
- /// TODO: Improve this code to include a stack of ir.Body and store the instructions
- /// in there. Now we are putting all the instructions in a function local table,
- /// however instructions that are in a Body can be thown away when the Body ends.
- fn dump(dtz: *DumpTzir, body: ir.Body, writer: std.fs.File.Writer) !void {
- // First pass to pre-populate the table so that we can show even invalid references.
- // Must iterate the same order we iterate the second time.
- // We also look for constants and put them in the const_table.
- try dtz.fetchInstsAndResolveConsts(body);
-
- std.debug.print("Module.Function(name={s}):\n", .{dtz.module_fn.owner_decl.name});
-
- for (dtz.const_table.items()) |entry| {
- const constant = entry.key.castTag(.constant).?;
- try writer.print(" @{d}: {} = {};\n", .{
- entry.value, constant.base.ty, constant.val,
- });
+ fn writeFnTypeCommon(
+ self: *Writer,
+ stream: anytype,
+ param_types: []const Inst.Ref,
+ ret_ty: Inst.Ref,
+ var_args: bool,
+ cc: Inst.Ref,
+ src: LazySrcLoc,
+ ) !void {
+ try stream.writeAll("[");
+ for (param_types) |param_type, i| {
+ if (i != 0) try stream.writeAll(", ");
+ try self.writeInstRef(stream, param_type);
}
-
- return dtz.dumpBody(body, writer);
+ try stream.writeAll("], ");
+ try self.writeInstRef(stream, ret_ty);
+ try self.writeOptionalInstRef(stream, ", cc=", cc);
+ try self.writeFlag(stream, ", var_args", var_args);
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, src);
}
- fn fetchInstsAndResolveConsts(dtz: *DumpTzir, body: ir.Body) error{OutOfMemory}!void {
- for (body.instructions) |inst| {
- try dtz.inst_table.put(inst, dtz.next_index);
- dtz.next_index += 1;
- switch (inst.tag) {
- .alloc,
- .retvoid,
- .unreach,
- .breakpoint,
- .dbg_stmt,
- .arg,
- => {},
-
- .ref,
- .ret,
- .bitcast,
- .not,
- .is_non_null,
- .is_non_null_ptr,
- .is_null,
- .is_null_ptr,
- .is_err,
- .is_err_ptr,
- .ptrtoint,
- .floatcast,
- .intcast,
- .load,
- .optional_payload,
- .optional_payload_ptr,
- .wrap_optional,
- .wrap_errunion_payload,
- .wrap_errunion_err,
- .unwrap_errunion_payload,
- .unwrap_errunion_err,
- .unwrap_errunion_payload_ptr,
- .unwrap_errunion_err_ptr,
- => {
- const un_op = inst.cast(ir.Inst.UnOp).?;
- try dtz.findConst(un_op.operand);
- },
+ fn writeSmallStr(
+ self: *Writer,
+ stream: anytype,
+ inst: Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const str = self.code.instructions.items(.data)[inst].small_str.get();
+ try stream.print("\"{}\")", .{std.zig.fmtEscapes(str)});
+ }
- .add,
- .addwrap,
- .sub,
- .subwrap,
- .mul,
- .mulwrap,
- .cmp_lt,
- .cmp_lte,
- .cmp_eq,
- .cmp_gte,
- .cmp_gt,
- .cmp_neq,
- .store,
- .bool_and,
- .bool_or,
- .bit_and,
- .bit_or,
- .xor,
- => {
- const bin_op = inst.cast(ir.Inst.BinOp).?;
- try dtz.findConst(bin_op.lhs);
- try dtz.findConst(bin_op.rhs);
- },
-
- .br => {
- const br = inst.castTag(.br).?;
- try dtz.findConst(&br.block.base);
- try dtz.findConst(br.operand);
- },
-
- .br_block_flat => {
- const br_block_flat = inst.castTag(.br_block_flat).?;
- try dtz.findConst(&br_block_flat.block.base);
- try dtz.fetchInstsAndResolveConsts(br_block_flat.body);
- },
-
- .br_void => {
- const br_void = inst.castTag(.br_void).?;
- try dtz.findConst(&br_void.block.base);
- },
-
- .block => {
- const block = inst.castTag(.block).?;
- try dtz.fetchInstsAndResolveConsts(block.body);
- },
-
- .condbr => {
- const condbr = inst.castTag(.condbr).?;
- try dtz.findConst(condbr.condition);
- try dtz.fetchInstsAndResolveConsts(condbr.then_body);
- try dtz.fetchInstsAndResolveConsts(condbr.else_body);
- },
-
- .loop => {
- const loop = inst.castTag(.loop).?;
- try dtz.fetchInstsAndResolveConsts(loop.body);
- },
- .call => {
- const call = inst.castTag(.call).?;
- try dtz.findConst(call.func);
- for (call.args) |arg| {
- try dtz.findConst(arg);
- }
- },
-
- // TODO fill out this debug printing
- .assembly,
- .constant,
- .varptr,
- .switchbr,
- => {},
- }
- }
+ fn writeSwitchCapture(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].switch_capture;
+ try self.writeInstIndex(stream, inst_data.switch_inst);
+ try stream.print(", {d})", .{inst_data.prong_index});
}
- fn dumpBody(dtz: *DumpTzir, body: ir.Body, writer: std.fs.File.Writer) (std.fs.File.WriteError || error{OutOfMemory})!void {
- for (body.instructions) |inst| {
- const my_index = dtz.next_partial_index;
- try dtz.partial_inst_table.put(inst, my_index);
- dtz.next_partial_index += 1;
-
- try writer.writeByteNTimes(' ', dtz.indent);
- try writer.print("%{d}: {} = {s}(", .{
- my_index, inst.ty, @tagName(inst.tag),
- });
- switch (inst.tag) {
- .alloc,
- .retvoid,
- .unreach,
- .breakpoint,
- .dbg_stmt,
- => try writer.writeAll(")\n"),
+ fn writeInstRef(self: *Writer, stream: anytype, ref: Inst.Ref) !void {
+ var i: usize = @enumToInt(ref);
- .ref,
- .ret,
- .bitcast,
- .not,
- .is_non_null,
- .is_null,
- .is_non_null_ptr,
- .is_null_ptr,
- .is_err,
- .is_err_ptr,
- .ptrtoint,
- .floatcast,
- .intcast,
- .load,
- .optional_payload,
- .optional_payload_ptr,
- .wrap_optional,
- .wrap_errunion_err,
- .wrap_errunion_payload,
- .unwrap_errunion_err,
- .unwrap_errunion_payload,
- .unwrap_errunion_payload_ptr,
- .unwrap_errunion_err_ptr,
- => {
- const un_op = inst.cast(ir.Inst.UnOp).?;
- const kinky = try dtz.writeInst(writer, un_op.operand);
- if (kinky != null) {
- try writer.writeAll(") // Instruction does not dominate all uses!\n");
- } else {
- try writer.writeAll(")\n");
- }
- },
+ if (i < Inst.Ref.typed_value_map.len) {
+ return stream.print("@{}", .{ref});
+ }
+ i -= Inst.Ref.typed_value_map.len;
- .add,
- .addwrap,
- .sub,
- .subwrap,
- .mul,
- .mulwrap,
- .cmp_lt,
- .cmp_lte,
- .cmp_eq,
- .cmp_gte,
- .cmp_gt,
- .cmp_neq,
- .store,
- .bool_and,
- .bool_or,
- .bit_and,
- .bit_or,
- .xor,
- => {
- const bin_op = inst.cast(ir.Inst.BinOp).?;
-
- const lhs_kinky = try dtz.writeInst(writer, bin_op.lhs);
- try writer.writeAll(", ");
- const rhs_kinky = try dtz.writeInst(writer, bin_op.rhs);
-
- if (lhs_kinky != null or rhs_kinky != null) {
- try writer.writeAll(") // Instruction does not dominate all uses!");
- if (lhs_kinky) |lhs| {
- try writer.print(" %{d}", .{lhs});
- }
- if (rhs_kinky) |rhs| {
- try writer.print(" %{d}", .{rhs});
- }
- try writer.writeAll("\n");
- } else {
- try writer.writeAll(")\n");
- }
- },
-
- .arg => {
- const arg = inst.castTag(.arg).?;
- try writer.print("{s})\n", .{arg.name});
- },
-
- .br => {
- const br = inst.castTag(.br).?;
-
- const lhs_kinky = try dtz.writeInst(writer, &br.block.base);
- try writer.writeAll(", ");
- const rhs_kinky = try dtz.writeInst(writer, br.operand);
-
- if (lhs_kinky != null or rhs_kinky != null) {
- try writer.writeAll(") // Instruction does not dominate all uses!");
- if (lhs_kinky) |lhs| {
- try writer.print(" %{d}", .{lhs});
- }
- if (rhs_kinky) |rhs| {
- try writer.print(" %{d}", .{rhs});
- }
- try writer.writeAll("\n");
- } else {
- try writer.writeAll(")\n");
- }
- },
-
- .br_block_flat => {
- const br_block_flat = inst.castTag(.br_block_flat).?;
- const block_kinky = try dtz.writeInst(writer, &br_block_flat.block.base);
- if (block_kinky != null) {
- try writer.writeAll(", { // Instruction does not dominate all uses!\n");
- } else {
- try writer.writeAll(", {\n");
- }
-
- const old_indent = dtz.indent;
- dtz.indent += 2;
- try dtz.dumpBody(br_block_flat.body, writer);
- dtz.indent = old_indent;
-
- try writer.writeByteNTimes(' ', dtz.indent);
- try writer.writeAll("})\n");
- },
-
- .br_void => {
- const br_void = inst.castTag(.br_void).?;
- const kinky = try dtz.writeInst(writer, &br_void.block.base);
- if (kinky) |_| {
- try writer.writeAll(") // Instruction does not dominate all uses!\n");
- } else {
- try writer.writeAll(")\n");
- }
- },
-
- .block => {
- const block = inst.castTag(.block).?;
-
- try writer.writeAll("{\n");
-
- const old_indent = dtz.indent;
- dtz.indent += 2;
- try dtz.dumpBody(block.body, writer);
- dtz.indent = old_indent;
-
- try writer.writeByteNTimes(' ', dtz.indent);
- try writer.writeAll("})\n");
- },
-
- .condbr => {
- const condbr = inst.castTag(.condbr).?;
-
- const condition_kinky = try dtz.writeInst(writer, condbr.condition);
- if (condition_kinky != null) {
- try writer.writeAll(", { // Instruction does not dominate all uses!\n");
- } else {
- try writer.writeAll(", {\n");
- }
-
- const old_indent = dtz.indent;
- dtz.indent += 2;
- try dtz.dumpBody(condbr.then_body, writer);
-
- try writer.writeByteNTimes(' ', old_indent);
- try writer.writeAll("}, {\n");
-
- try dtz.dumpBody(condbr.else_body, writer);
- dtz.indent = old_indent;
-
- try writer.writeByteNTimes(' ', old_indent);
- try writer.writeAll("})\n");
- },
-
- .loop => {
- const loop = inst.castTag(.loop).?;
-
- try writer.writeAll("{\n");
-
- const old_indent = dtz.indent;
- dtz.indent += 2;
- try dtz.dumpBody(loop.body, writer);
- dtz.indent = old_indent;
-
- try writer.writeByteNTimes(' ', dtz.indent);
- try writer.writeAll("})\n");
- },
-
- .call => {
- const call = inst.castTag(.call).?;
-
- const args_kinky = try dtz.allocator.alloc(?usize, call.args.len);
- defer dtz.allocator.free(args_kinky);
- std.mem.set(?usize, args_kinky, null);
- var any_kinky_args = false;
-
- const func_kinky = try dtz.writeInst(writer, call.func);
-
- for (call.args) |arg, i| {
- try writer.writeAll(", ");
-
- args_kinky[i] = try dtz.writeInst(writer, arg);
- any_kinky_args = any_kinky_args or args_kinky[i] != null;
- }
-
- if (func_kinky != null or any_kinky_args) {
- try writer.writeAll(") // Instruction does not dominate all uses!");
- if (func_kinky) |func_index| {
- try writer.print(" %{d}", .{func_index});
- }
- for (args_kinky) |arg_kinky| {
- if (arg_kinky) |arg_index| {
- try writer.print(" %{d}", .{arg_index});
- }
- }
- try writer.writeAll("\n");
- } else {
- try writer.writeAll(")\n");
- }
- },
-
- // TODO fill out this debug printing
- .assembly,
- .constant,
- .varptr,
- .switchbr,
- => {
- try writer.writeAll("!TODO!)\n");
- },
- }
+ if (i < self.param_count) {
+ return stream.print("${d}", .{i});
}
+ i -= self.param_count;
+
+ return self.writeInstIndex(stream, @intCast(Inst.Index, i));
}
- fn writeInst(dtz: *DumpTzir, writer: std.fs.File.Writer, inst: *ir.Inst) !?usize {
- if (dtz.partial_inst_table.get(inst)) |operand_index| {
- try writer.print("%{d}", .{operand_index});
- return null;
- } else if (dtz.const_table.get(inst)) |operand_index| {
- try writer.print("@{d}", .{operand_index});
- return null;
- } else if (dtz.inst_table.get(inst)) |operand_index| {
- try writer.print("%{d}", .{operand_index});
- return operand_index;
- } else {
- try writer.writeAll("!BADREF!");
- return null;
- }
+ fn writeInstIndex(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ return stream.print("%{d}", .{inst});
}
- fn findConst(dtz: *DumpTzir, operand: *ir.Inst) !void {
- if (operand.tag == .constant) {
- try dtz.const_table.put(operand, dtz.next_const_index);
- dtz.next_const_index += 1;
- }
+ fn writeOptionalInstRef(
+ self: *Writer,
+ stream: anytype,
+ prefix: []const u8,
+ inst: Inst.Ref,
+ ) !void {
+ if (inst == .none) return;
+ try stream.writeAll(prefix);
+ try self.writeInstRef(stream, inst);
}
-};
-/// For debugging purposes, like dumpFn but for unanalyzed zir blocks
-pub fn dumpZir(allocator: *Allocator, kind: []const u8, decl_name: [*:0]const u8, instructions: []*Inst) !void {
- var fib = std.heap.FixedBufferAllocator.init(&[_]u8{});
- var module = Module{
- .decls = &[_]*Module.Decl{},
- .arena = std.heap.ArenaAllocator.init(&fib.allocator),
- .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(&fib.allocator),
- .body_metadata = std.AutoHashMap(*Body, Module.BodyMetaData).init(&fib.allocator),
- };
- var write = Writer{
- .module = &module,
- .inst_table = InstPtrTable.init(allocator),
- .block_table = std.AutoHashMap(*Inst.Block, []const u8).init(allocator),
- .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator),
- .arena = std.heap.ArenaAllocator.init(allocator),
- .indent = 4,
- .next_instr_index = 0,
- };
- defer write.arena.deinit();
- defer write.inst_table.deinit();
- defer write.block_table.deinit();
- defer write.loop_table.deinit();
-
- try write.inst_table.ensureCapacity(@intCast(u32, instructions.len));
-
- const stderr = std.io.getStdErr().writer();
- try stderr.print("{s} {s} {{ // unanalyzed\n", .{ kind, decl_name });
-
- for (instructions) |inst| {
- const my_i = write.next_instr_index;
- write.next_instr_index += 1;
-
- if (inst.cast(Inst.Block)) |block| {
- const name = try std.fmt.allocPrint(&write.arena.allocator, "label_{d}", .{my_i});
- try write.block_table.put(block, name);
- } else if (inst.cast(Inst.Loop)) |loop| {
- const name = try std.fmt.allocPrint(&write.arena.allocator, "loop_{d}", .{my_i});
- try write.loop_table.put(loop, name);
- }
+ fn writeFlag(
+ self: *Writer,
+ stream: anytype,
+ name: []const u8,
+ flag: bool,
+ ) !void {
+ if (!flag) return;
+ try stream.writeAll(name);
+ }
- try write.inst_table.putNoClobber(inst, .{ .inst = inst, .index = my_i, .name = "inst" });
- try stderr.print(" %{d} ", .{my_i});
- try write.writeInstToStream(stderr, inst);
- try stderr.writeByte('\n');
+ fn writeSrc(self: *Writer, stream: anytype, src: LazySrcLoc) !void {
+ const tree = self.scope.tree();
+ const src_loc = src.toSrcLoc(self.scope);
+ const abs_byte_off = try src_loc.byteOffset();
+ const delta_line = std.zig.findLineColumn(tree.source, abs_byte_off);
+ try stream.print("{s}:{d}:{d}", .{
+ @tagName(src), delta_line.line + 1, delta_line.column + 1,
+ });
}
- try stderr.print("}} // {s} {s}\n\n", .{ kind, decl_name });
-}
+ fn writeBody(self: *Writer, stream: anytype, body: []const Inst.Index) !void {
+ for (body) |inst| {
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.print("%{d} ", .{inst});
+ try self.writeInstToStream(stream, inst);
+ try stream.writeByte('\n');
+ }
+ }
+};
diff --git a/src/zir_sema.zig b/src/zir_sema.zig
@@ -1,2597 +0,0 @@
-//! Semantic analysis of ZIR instructions.
-//! This file operates on a `Module` instance, transforming untyped ZIR
-//! instructions into semantically-analyzed IR instructions. It does type
-//! checking, comptime control flow, and safety-check generation. This is the
-//! the heart of the Zig compiler.
-//! When deciding if something goes into this file or into Module, here is a
-//! guiding principle: if it has to do with (untyped) ZIR instructions, it goes
-//! here. If the analysis operates on typed IR instructions, it goes in Module.
-
-const std = @import("std");
-const mem = std.mem;
-const Allocator = std.mem.Allocator;
-const assert = std.debug.assert;
-const log = std.log.scoped(.sema);
-
-const Value = @import("value.zig").Value;
-const Type = @import("type.zig").Type;
-const TypedValue = @import("TypedValue.zig");
-const ir = @import("ir.zig");
-const zir = @import("zir.zig");
-const Module = @import("Module.zig");
-const Inst = ir.Inst;
-const Body = ir.Body;
-const trace = @import("tracy.zig").trace;
-const Scope = Module.Scope;
-const InnerError = Module.InnerError;
-const Decl = Module.Decl;
-
-pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst {
- switch (old_inst.tag) {
- .alloc => return zirAlloc(mod, scope, old_inst.castTag(.alloc).?),
- .alloc_mut => return zirAllocMut(mod, scope, old_inst.castTag(.alloc_mut).?),
- .alloc_inferred => return zirAllocInferred(mod, scope, old_inst.castTag(.alloc_inferred).?, .inferred_alloc_const),
- .alloc_inferred_mut => return zirAllocInferred(mod, scope, old_inst.castTag(.alloc_inferred_mut).?, .inferred_alloc_mut),
- .arg => return zirArg(mod, scope, old_inst.castTag(.arg).?),
- .bitcast_ref => return zirBitcastRef(mod, scope, old_inst.castTag(.bitcast_ref).?),
- .bitcast_result_ptr => return zirBitcastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?),
- .block => return zirBlock(mod, scope, old_inst.castTag(.block).?, false),
- .block_comptime => return zirBlock(mod, scope, old_inst.castTag(.block_comptime).?, true),
- .block_flat => return zirBlockFlat(mod, scope, old_inst.castTag(.block_flat).?, false),
- .block_comptime_flat => return zirBlockFlat(mod, scope, old_inst.castTag(.block_comptime_flat).?, true),
- .@"break" => return zirBreak(mod, scope, old_inst.castTag(.@"break").?),
- .breakpoint => return zirBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?),
- .break_void => return zirBreakVoid(mod, scope, old_inst.castTag(.break_void).?),
- .call => return zirCall(mod, scope, old_inst.castTag(.call).?),
- .coerce_result_ptr => return zirCoerceResultPtr(mod, scope, old_inst.castTag(.coerce_result_ptr).?),
- .compile_error => return zirCompileError(mod, scope, old_inst.castTag(.compile_error).?),
- .compile_log => return zirCompileLog(mod, scope, old_inst.castTag(.compile_log).?),
- .@"const" => return zirConst(mod, scope, old_inst.castTag(.@"const").?),
- .dbg_stmt => return zirDbgStmt(mod, scope, old_inst.castTag(.dbg_stmt).?),
- .decl_ref => return zirDeclRef(mod, scope, old_inst.castTag(.decl_ref).?),
- .decl_ref_str => return zirDeclRefStr(mod, scope, old_inst.castTag(.decl_ref_str).?),
- .decl_val => return zirDeclVal(mod, scope, old_inst.castTag(.decl_val).?),
- .ensure_result_used => return zirEnsureResultUsed(mod, scope, old_inst.castTag(.ensure_result_used).?),
- .ensure_result_non_error => return zirEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?),
- .indexable_ptr_len => return zirIndexablePtrLen(mod, scope, old_inst.castTag(.indexable_ptr_len).?),
- .ref => return zirRef(mod, scope, old_inst.castTag(.ref).?),
- .resolve_inferred_alloc => return zirResolveInferredAlloc(mod, scope, old_inst.castTag(.resolve_inferred_alloc).?),
- .ret_ptr => return zirRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?),
- .ret_type => return zirRetType(mod, scope, old_inst.castTag(.ret_type).?),
- .store_to_block_ptr => return zirStoreToBlockPtr(mod, scope, old_inst.castTag(.store_to_block_ptr).?),
- .store_to_inferred_ptr => return zirStoreToInferredPtr(mod, scope, old_inst.castTag(.store_to_inferred_ptr).?),
- .single_const_ptr_type => return zirSimplePtrType(mod, scope, old_inst.castTag(.single_const_ptr_type).?, false, .One),
- .single_mut_ptr_type => return zirSimplePtrType(mod, scope, old_inst.castTag(.single_mut_ptr_type).?, true, .One),
- .many_const_ptr_type => return zirSimplePtrType(mod, scope, old_inst.castTag(.many_const_ptr_type).?, false, .Many),
- .many_mut_ptr_type => return zirSimplePtrType(mod, scope, old_inst.castTag(.many_mut_ptr_type).?, true, .Many),
- .c_const_ptr_type => return zirSimplePtrType(mod, scope, old_inst.castTag(.c_const_ptr_type).?, false, .C),
- .c_mut_ptr_type => return zirSimplePtrType(mod, scope, old_inst.castTag(.c_mut_ptr_type).?, true, .C),
- .const_slice_type => return zirSimplePtrType(mod, scope, old_inst.castTag(.const_slice_type).?, false, .Slice),
- .mut_slice_type => return zirSimplePtrType(mod, scope, old_inst.castTag(.mut_slice_type).?, true, .Slice),
- .ptr_type => return zirPtrType(mod, scope, old_inst.castTag(.ptr_type).?),
- .store => return zirStore(mod, scope, old_inst.castTag(.store).?),
- .set_eval_branch_quota => return zirSetEvalBranchQuota(mod, scope, old_inst.castTag(.set_eval_branch_quota).?),
- .str => return zirStr(mod, scope, old_inst.castTag(.str).?),
- .int => return zirInt(mod, scope, old_inst.castTag(.int).?),
- .int_type => return zirIntType(mod, scope, old_inst.castTag(.int_type).?),
- .loop => return zirLoop(mod, scope, old_inst.castTag(.loop).?),
- .param_type => return zirParamType(mod, scope, old_inst.castTag(.param_type).?),
- .ptrtoint => return zirPtrtoint(mod, scope, old_inst.castTag(.ptrtoint).?),
- .field_ptr => return zirFieldPtr(mod, scope, old_inst.castTag(.field_ptr).?),
- .field_val => return zirFieldVal(mod, scope, old_inst.castTag(.field_val).?),
- .field_ptr_named => return zirFieldPtrNamed(mod, scope, old_inst.castTag(.field_ptr_named).?),
- .field_val_named => return zirFieldValNamed(mod, scope, old_inst.castTag(.field_val_named).?),
- .deref => return zirDeref(mod, scope, old_inst.castTag(.deref).?),
- .as => return zirAs(mod, scope, old_inst.castTag(.as).?),
- .@"asm" => return zirAsm(mod, scope, old_inst.castTag(.@"asm").?),
- .unreachable_safe => return zirUnreachable(mod, scope, old_inst.castTag(.unreachable_safe).?, true),
- .unreachable_unsafe => return zirUnreachable(mod, scope, old_inst.castTag(.unreachable_unsafe).?, false),
- .@"return" => return zirReturn(mod, scope, old_inst.castTag(.@"return").?),
- .return_void => return zirReturnVoid(mod, scope, old_inst.castTag(.return_void).?),
- .@"fn" => return zirFn(mod, scope, old_inst.castTag(.@"fn").?),
- .@"export" => return zirExport(mod, scope, old_inst.castTag(.@"export").?),
- .primitive => return zirPrimitive(mod, scope, old_inst.castTag(.primitive).?),
- .fn_type => return zirFnType(mod, scope, old_inst.castTag(.fn_type).?, false),
- .fn_type_cc => return zirFnTypeCc(mod, scope, old_inst.castTag(.fn_type_cc).?, false),
- .fn_type_var_args => return zirFnType(mod, scope, old_inst.castTag(.fn_type_var_args).?, true),
- .fn_type_cc_var_args => return zirFnTypeCc(mod, scope, old_inst.castTag(.fn_type_cc_var_args).?, true),
- .intcast => return zirIntcast(mod, scope, old_inst.castTag(.intcast).?),
- .bitcast => return zirBitcast(mod, scope, old_inst.castTag(.bitcast).?),
- .floatcast => return zirFloatcast(mod, scope, old_inst.castTag(.floatcast).?),
- .elem_ptr => return zirElemPtr(mod, scope, old_inst.castTag(.elem_ptr).?),
- .elem_val => return zirElemVal(mod, scope, old_inst.castTag(.elem_val).?),
- .add => return zirArithmetic(mod, scope, old_inst.castTag(.add).?),
- .addwrap => return zirArithmetic(mod, scope, old_inst.castTag(.addwrap).?),
- .sub => return zirArithmetic(mod, scope, old_inst.castTag(.sub).?),
- .subwrap => return zirArithmetic(mod, scope, old_inst.castTag(.subwrap).?),
- .mul => return zirArithmetic(mod, scope, old_inst.castTag(.mul).?),
- .mulwrap => return zirArithmetic(mod, scope, old_inst.castTag(.mulwrap).?),
- .div => return zirArithmetic(mod, scope, old_inst.castTag(.div).?),
- .mod_rem => return zirArithmetic(mod, scope, old_inst.castTag(.mod_rem).?),
- .array_cat => return zirArrayCat(mod, scope, old_inst.castTag(.array_cat).?),
- .array_mul => return zirArrayMul(mod, scope, old_inst.castTag(.array_mul).?),
- .bit_and => return zirBitwise(mod, scope, old_inst.castTag(.bit_and).?),
- .bit_not => return zirBitNot(mod, scope, old_inst.castTag(.bit_not).?),
- .bit_or => return zirBitwise(mod, scope, old_inst.castTag(.bit_or).?),
- .xor => return zirBitwise(mod, scope, old_inst.castTag(.xor).?),
- .shl => return zirShl(mod, scope, old_inst.castTag(.shl).?),
- .shr => return zirShr(mod, scope, old_inst.castTag(.shr).?),
- .cmp_lt => return zirCmp(mod, scope, old_inst.castTag(.cmp_lt).?, .lt),
- .cmp_lte => return zirCmp(mod, scope, old_inst.castTag(.cmp_lte).?, .lte),
- .cmp_eq => return zirCmp(mod, scope, old_inst.castTag(.cmp_eq).?, .eq),
- .cmp_gte => return zirCmp(mod, scope, old_inst.castTag(.cmp_gte).?, .gte),
- .cmp_gt => return zirCmp(mod, scope, old_inst.castTag(.cmp_gt).?, .gt),
- .cmp_neq => return zirCmp(mod, scope, old_inst.castTag(.cmp_neq).?, .neq),
- .condbr => return zirCondbr(mod, scope, old_inst.castTag(.condbr).?),
- .is_null => return zirIsNull(mod, scope, old_inst.castTag(.is_null).?, false),
- .is_non_null => return zirIsNull(mod, scope, old_inst.castTag(.is_non_null).?, true),
- .is_null_ptr => return zirIsNullPtr(mod, scope, old_inst.castTag(.is_null_ptr).?, false),
- .is_non_null_ptr => return zirIsNullPtr(mod, scope, old_inst.castTag(.is_non_null_ptr).?, true),
- .is_err => return zirIsErr(mod, scope, old_inst.castTag(.is_err).?),
- .is_err_ptr => return zirIsErrPtr(mod, scope, old_inst.castTag(.is_err_ptr).?),
- .bool_not => return zirBoolNot(mod, scope, old_inst.castTag(.bool_not).?),
- .typeof => return zirTypeof(mod, scope, old_inst.castTag(.typeof).?),
- .typeof_peer => return zirTypeofPeer(mod, scope, old_inst.castTag(.typeof_peer).?),
- .optional_type => return zirOptionalType(mod, scope, old_inst.castTag(.optional_type).?),
- .optional_type_from_ptr_elem => return zirOptionalTypeFromPtrElem(mod, scope, old_inst.castTag(.optional_type_from_ptr_elem).?),
- .optional_payload_safe => return zirOptionalPayload(mod, scope, old_inst.castTag(.optional_payload_safe).?, true),
- .optional_payload_unsafe => return zirOptionalPayload(mod, scope, old_inst.castTag(.optional_payload_unsafe).?, false),
- .optional_payload_safe_ptr => return zirOptionalPayloadPtr(mod, scope, old_inst.castTag(.optional_payload_safe_ptr).?, true),
- .optional_payload_unsafe_ptr => return zirOptionalPayloadPtr(mod, scope, old_inst.castTag(.optional_payload_unsafe_ptr).?, false),
- .err_union_payload_safe => return zirErrUnionPayload(mod, scope, old_inst.castTag(.err_union_payload_safe).?, true),
- .err_union_payload_unsafe => return zirErrUnionPayload(mod, scope, old_inst.castTag(.err_union_payload_unsafe).?, false),
- .err_union_payload_safe_ptr => return zirErrUnionPayloadPtr(mod, scope, old_inst.castTag(.err_union_payload_safe_ptr).?, true),
- .err_union_payload_unsafe_ptr => return zirErrUnionPayloadPtr(mod, scope, old_inst.castTag(.err_union_payload_unsafe_ptr).?, false),
- .err_union_code => return zirErrUnionCode(mod, scope, old_inst.castTag(.err_union_code).?),
- .err_union_code_ptr => return zirErrUnionCodePtr(mod, scope, old_inst.castTag(.err_union_code_ptr).?),
- .ensure_err_payload_void => return zirEnsureErrPayloadVoid(mod, scope, old_inst.castTag(.ensure_err_payload_void).?),
- .array_type => return zirArrayType(mod, scope, old_inst.castTag(.array_type).?),
- .array_type_sentinel => return zirArrayTypeSentinel(mod, scope, old_inst.castTag(.array_type_sentinel).?),
- .enum_literal => return zirEnumLiteral(mod, scope, old_inst.castTag(.enum_literal).?),
- .merge_error_sets => return zirMergeErrorSets(mod, scope, old_inst.castTag(.merge_error_sets).?),
- .error_union_type => return zirErrorUnionType(mod, scope, old_inst.castTag(.error_union_type).?),
- .anyframe_type => return zirAnyframeType(mod, scope, old_inst.castTag(.anyframe_type).?),
- .error_set => return zirErrorSet(mod, scope, old_inst.castTag(.error_set).?),
- .error_value => return zirErrorValue(mod, scope, old_inst.castTag(.error_value).?),
- .slice => return zirSlice(mod, scope, old_inst.castTag(.slice).?),
- .slice_start => return zirSliceStart(mod, scope, old_inst.castTag(.slice_start).?),
- .import => return zirImport(mod, scope, old_inst.castTag(.import).?),
- .bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?),
- .bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?),
- .void_value => return mod.constVoid(scope, old_inst.src),
- .switchbr => return zirSwitchBr(mod, scope, old_inst.castTag(.switchbr).?, false),
- .switchbr_ref => return zirSwitchBr(mod, scope, old_inst.castTag(.switchbr_ref).?, true),
- .switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?),
- .@"await" => return zirAwait(mod, scope, old_inst.castTag(.@"await").?),
- .nosuspend_await => return zirAwait(mod, scope, old_inst.castTag(.nosuspend_await).?),
- .@"resume" => return zirResume(mod, scope, old_inst.castTag(.@"resume").?),
- .@"suspend" => return zirSuspend(mod, scope, old_inst.castTag(.@"suspend").?),
- .suspend_block => return zirSuspendBlock(mod, scope, old_inst.castTag(.suspend_block).?),
-
- .container_field_named,
- .container_field_typed,
- .container_field,
- .enum_type,
- .union_type,
- .struct_type,
- => return mod.fail(scope, old_inst.src, "TODO analyze container instructions", .{}),
- }
-}
-
-pub fn analyzeBody(mod: *Module, block: *Scope.Block, body: zir.Body) !void {
- const tracy = trace(@src());
- defer tracy.end();
-
- for (body.instructions) |src_inst| {
- const analyzed_inst = try analyzeInst(mod, &block.base, src_inst);
- try block.inst_table.putNoClobber(src_inst, analyzed_inst);
- if (analyzed_inst.ty.zigTypeTag() == .NoReturn) {
- break;
- }
- }
-}
-
-pub fn analyzeBodyValueAsType(
- mod: *Module,
- block_scope: *Scope.Block,
- zir_result_inst: *zir.Inst,
- body: zir.Body,
-) !Type {
- try analyzeBody(mod, block_scope, body);
- const result_inst = block_scope.inst_table.get(zir_result_inst).?;
- const val = try mod.resolveConstValue(&block_scope.base, result_inst);
- return val.toType(block_scope.base.arena());
-}
-
-pub fn resolveInst(mod: *Module, scope: *Scope, zir_inst: *zir.Inst) InnerError!*Inst {
- const block = scope.cast(Scope.Block).?;
- return block.inst_table.get(zir_inst).?; // Instruction does not dominate all uses!
-}
-
-fn resolveConstString(mod: *Module, scope: *Scope, old_inst: *zir.Inst) ![]u8 {
- const new_inst = try resolveInst(mod, scope, old_inst);
- const wanted_type = Type.initTag(.const_slice_u8);
- const coerced_inst = try mod.coerce(scope, wanted_type, new_inst);
- const val = try mod.resolveConstValue(scope, coerced_inst);
- return val.toAllocatedBytes(scope.arena());
-}
-
-fn resolveType(mod: *Module, scope: *Scope, old_inst: *zir.Inst) !Type {
- const new_inst = try resolveInst(mod, scope, old_inst);
- const wanted_type = Type.initTag(.@"type");
- const coerced_inst = try mod.coerce(scope, wanted_type, new_inst);
- const val = try mod.resolveConstValue(scope, coerced_inst);
- return val.toType(scope.arena());
-}
-
-/// Appropriate to call when the coercion has already been done by result
-/// location semantics. Asserts the value fits in the provided `Int` type.
-/// Only supports `Int` types 64 bits or less.
-fn resolveAlreadyCoercedInt(
- mod: *Module,
- scope: *Scope,
- old_inst: *zir.Inst,
- comptime Int: type,
-) !Int {
- comptime assert(@typeInfo(Int).Int.bits <= 64);
- const new_inst = try resolveInst(mod, scope, old_inst);
- const val = try mod.resolveConstValue(scope, new_inst);
- switch (@typeInfo(Int).Int.signedness) {
- .signed => return @intCast(Int, val.toSignedInt()),
- .unsigned => return @intCast(Int, val.toUnsignedInt()),
- }
-}
-
-fn resolveInt(mod: *Module, scope: *Scope, old_inst: *zir.Inst, dest_type: Type) !u64 {
- const new_inst = try resolveInst(mod, scope, old_inst);
- const coerced = try mod.coerce(scope, dest_type, new_inst);
- const val = try mod.resolveConstValue(scope, coerced);
-
- return val.toUnsignedInt();
-}
-
-pub fn resolveInstConst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue {
- const new_inst = try resolveInst(mod, scope, old_inst);
- const val = try mod.resolveConstValue(scope, new_inst);
- return TypedValue{
- .ty = new_inst.ty,
- .val = val,
- };
-}
-
-fn zirConst(mod: *Module, scope: *Scope, const_inst: *zir.Inst.Const) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions
- // after analysis.
- const typed_value_copy = try const_inst.positionals.typed_value.copy(scope.arena());
- return mod.constInst(scope, const_inst.base.src, typed_value_copy);
-}
-
-fn analyzeConstInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue {
- const new_inst = try analyzeInst(mod, scope, old_inst);
- return TypedValue{
- .ty = new_inst.ty,
- .val = try mod.resolveConstValue(scope, new_inst),
- };
-}
-
-fn zirBitcastRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.fail(scope, inst.base.src, "TODO implement zir_sema.zirBitcastRef", .{});
-}
-
-fn zirBitcastResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.fail(scope, inst.base.src, "TODO implement zir_sema.zirBitcastResultPtr", .{});
-}
-
-fn zirCoerceResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.fail(scope, inst.base.src, "TODO implement zirCoerceResultPtr", .{});
-}
-
-fn zirRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const b = try mod.requireFunctionBlock(scope, inst.base.src);
- const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
- const ret_type = fn_ty.fnReturnType();
- const ptr_type = try mod.simplePtrType(scope, inst.base.src, ret_type, true, .One);
- return mod.addNoOp(b, inst.base.src, ptr_type, .alloc);
-}
-
-fn zirRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const operand = try resolveInst(mod, scope, inst.positionals.operand);
- return mod.analyzeRef(scope, inst.base.src, operand);
-}
-
-fn zirRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const b = try mod.requireFunctionBlock(scope, inst.base.src);
- const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
- const ret_type = fn_ty.fnReturnType();
- return mod.constType(scope, inst.base.src, ret_type);
-}
-
-fn zirEnsureResultUsed(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const operand = try resolveInst(mod, scope, inst.positionals.operand);
- switch (operand.ty.zigTypeTag()) {
- .Void, .NoReturn => return mod.constVoid(scope, operand.src),
- else => return mod.fail(scope, operand.src, "expression value is ignored", .{}),
- }
-}
-
-fn zirEnsureResultNonError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const operand = try resolveInst(mod, scope, inst.positionals.operand);
- switch (operand.ty.zigTypeTag()) {
- .ErrorSet, .ErrorUnion => return mod.fail(scope, operand.src, "error is discarded", .{}),
- else => return mod.constVoid(scope, operand.src),
- }
-}
-
-fn zirIndexablePtrLen(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const array_ptr = try resolveInst(mod, scope, inst.positionals.operand);
- const elem_ty = array_ptr.ty.elemType();
- if (!elem_ty.isIndexable()) {
- const msg = msg: {
- const msg = try mod.errMsg(
- scope,
- inst.base.src,
- "type '{}' does not support indexing",
- .{elem_ty},
- );
- errdefer msg.destroy(mod.gpa);
- try mod.errNote(
- scope,
- inst.base.src,
- msg,
- "for loop operand must be an array, slice, tuple, or vector",
- .{},
- );
- break :msg msg;
- };
- return mod.failWithOwnedErrorMsg(scope, msg);
- }
- const result_ptr = try mod.namedFieldPtr(scope, inst.base.src, array_ptr, "len", inst.base.src);
- return mod.analyzeDeref(scope, inst.base.src, result_ptr, result_ptr.src);
-}
-
-fn zirAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const var_type = try resolveType(mod, scope, inst.positionals.operand);
- const ptr_type = try mod.simplePtrType(scope, inst.base.src, var_type, true, .One);
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- return mod.addNoOp(b, inst.base.src, ptr_type, .alloc);
-}
-
-fn zirAllocMut(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const var_type = try resolveType(mod, scope, inst.positionals.operand);
- try mod.validateVarType(scope, inst.base.src, var_type);
- const ptr_type = try mod.simplePtrType(scope, inst.base.src, var_type, true, .One);
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- return mod.addNoOp(b, inst.base.src, ptr_type, .alloc);
-}
-
-fn zirAllocInferred(
- mod: *Module,
- scope: *Scope,
- inst: *zir.Inst.NoOp,
- mut_tag: Type.Tag,
-) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const val_payload = try scope.arena().create(Value.Payload.InferredAlloc);
- val_payload.* = .{
- .data = .{},
- };
- // `Module.constInst` does not add the instruction to the block because it is
- // not needed in the case of constant values. However here, we plan to "downgrade"
- // to a normal instruction when we hit `resolve_inferred_alloc`. So we append
- // to the block even though it is currently a `.constant`.
- const result = try mod.constInst(scope, inst.base.src, .{
- .ty = switch (mut_tag) {
- .inferred_alloc_const => Type.initTag(.inferred_alloc_const),
- .inferred_alloc_mut => Type.initTag(.inferred_alloc_mut),
- else => unreachable,
- },
- .val = Value.initPayload(&val_payload.base),
- });
- const block = try mod.requireFunctionBlock(scope, inst.base.src);
- try block.instructions.append(mod.gpa, result);
- return result;
-}
-
-fn zirResolveInferredAlloc(
- mod: *Module,
- scope: *Scope,
- inst: *zir.Inst.UnOp,
-) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const ptr = try resolveInst(mod, scope, inst.positionals.operand);
- const ptr_val = ptr.castTag(.constant).?.val;
- const inferred_alloc = ptr_val.castTag(.inferred_alloc).?;
- const peer_inst_list = inferred_alloc.data.stored_inst_list.items;
- const final_elem_ty = try mod.resolvePeerTypes(scope, peer_inst_list);
- const var_is_mut = switch (ptr.ty.tag()) {
- .inferred_alloc_const => false,
- .inferred_alloc_mut => true,
- else => unreachable,
- };
- if (var_is_mut) {
- try mod.validateVarType(scope, inst.base.src, final_elem_ty);
- }
- const final_ptr_ty = try mod.simplePtrType(scope, inst.base.src, final_elem_ty, true, .One);
-
- // Change it to a normal alloc.
- ptr.ty = final_ptr_ty;
- ptr.tag = .alloc;
-
- return mod.constVoid(scope, inst.base.src);
-}
-
-fn zirStoreToBlockPtr(
- mod: *Module,
- scope: *Scope,
- inst: *zir.Inst.BinOp,
-) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const ptr = try resolveInst(mod, scope, inst.positionals.lhs);
- const value = try resolveInst(mod, scope, inst.positionals.rhs);
- const ptr_ty = try mod.simplePtrType(scope, inst.base.src, value.ty, true, .One);
- // TODO detect when this store should be done at compile-time. For example,
- // if expressions should force it when the condition is compile-time known.
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- const bitcasted_ptr = try mod.addUnOp(b, inst.base.src, ptr_ty, .bitcast, ptr);
- return mod.storePtr(scope, inst.base.src, bitcasted_ptr, value);
-}
-
-fn zirStoreToInferredPtr(
- mod: *Module,
- scope: *Scope,
- inst: *zir.Inst.BinOp,
-) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const ptr = try resolveInst(mod, scope, inst.positionals.lhs);
- const value = try resolveInst(mod, scope, inst.positionals.rhs);
- const inferred_alloc = ptr.castTag(.constant).?.val.castTag(.inferred_alloc).?;
- // Add the stored instruction to the set we will use to resolve peer types
- // for the inferred allocation.
- try inferred_alloc.data.stored_inst_list.append(scope.arena(), value);
- // Create a runtime bitcast instruction with exactly the type the pointer wants.
- const ptr_ty = try mod.simplePtrType(scope, inst.base.src, value.ty, true, .One);
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- const bitcasted_ptr = try mod.addUnOp(b, inst.base.src, ptr_ty, .bitcast, ptr);
- return mod.storePtr(scope, inst.base.src, bitcasted_ptr, value);
-}
-
-fn zirSetEvalBranchQuota(
- mod: *Module,
- scope: *Scope,
- inst: *zir.Inst.UnOp,
-) InnerError!*Inst {
- const b = try mod.requireFunctionBlock(scope, inst.base.src);
- const quota = try resolveAlreadyCoercedInt(mod, scope, inst.positionals.operand, u32);
- if (b.branch_quota.* < quota)
- b.branch_quota.* = quota;
- return mod.constVoid(scope, inst.base.src);
-}
-
-fn zirStore(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const ptr = try resolveInst(mod, scope, inst.positionals.lhs);
- const value = try resolveInst(mod, scope, inst.positionals.rhs);
- return mod.storePtr(scope, inst.base.src, ptr, value);
-}
-
-fn zirParamType(mod: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const fn_inst = try resolveInst(mod, scope, inst.positionals.func);
- const arg_index = inst.positionals.arg_index;
-
- const fn_ty: Type = switch (fn_inst.ty.zigTypeTag()) {
- .Fn => fn_inst.ty,
- .BoundFn => {
- return mod.fail(scope, fn_inst.src, "TODO implement zirParamType for method call syntax", .{});
- },
- else => {
- return mod.fail(scope, fn_inst.src, "expected function, found '{}'", .{fn_inst.ty});
- },
- };
-
- const param_count = fn_ty.fnParamLen();
- if (arg_index >= param_count) {
- if (fn_ty.fnIsVarArgs()) {
- return mod.constType(scope, inst.base.src, Type.initTag(.var_args_param));
- }
- return mod.fail(scope, inst.base.src, "arg index {d} out of bounds; '{}' has {d} argument(s)", .{
- arg_index,
- fn_ty,
- param_count,
- });
- }
-
- // TODO support generic functions
- const param_type = fn_ty.fnParamType(arg_index);
- return mod.constType(scope, inst.base.src, param_type);
-}
-
-fn zirStr(mod: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- // The bytes references memory inside the ZIR module, which can get deallocated
- // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena.
- var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa);
- errdefer new_decl_arena.deinit();
- const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes);
-
- const decl_ty = try Type.Tag.array_u8_sentinel_0.create(&new_decl_arena.allocator, arena_bytes.len);
- const decl_val = try Value.Tag.bytes.create(&new_decl_arena.allocator, arena_bytes);
-
- const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{
- .ty = decl_ty,
- .val = decl_val,
- });
- return mod.analyzeDeclRef(scope, str_inst.base.src, new_decl);
-}
-
-fn zirInt(mod: *Module, scope: *Scope, inst: *zir.Inst.Int) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- return mod.constIntBig(scope, inst.base.src, Type.initTag(.comptime_int), inst.positionals.int);
-}
-
-fn zirExport(mod: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const symbol_name = try resolveConstString(mod, scope, export_inst.positionals.symbol_name);
- const exported_decl = mod.lookupDeclName(scope, export_inst.positionals.decl_name) orelse
- return mod.fail(scope, export_inst.base.src, "decl '{s}' not found", .{export_inst.positionals.decl_name});
- try mod.analyzeExport(scope, export_inst.base.src, symbol_name, exported_decl);
- return mod.constVoid(scope, export_inst.base.src);
-}
-
-fn zirCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const msg = try resolveConstString(mod, scope, inst.positionals.operand);
- return mod.fail(scope, inst.base.src, "{s}", .{msg});
-}
-
-fn zirCompileLog(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileLog) InnerError!*Inst {
- var managed = mod.compile_log_text.toManaged(mod.gpa);
- defer mod.compile_log_text = managed.moveToUnmanaged();
- const writer = managed.writer();
-
- for (inst.positionals.to_log) |arg_inst, i| {
- if (i != 0) try writer.print(", ", .{});
-
- const arg = try resolveInst(mod, scope, arg_inst);
- if (arg.value()) |val| {
- try writer.print("@as({}, {})", .{ arg.ty, val });
- } else {
- try writer.print("@as({}, [runtime value])", .{arg.ty});
- }
- }
- try writer.print("\n", .{});
-
- const gop = try mod.compile_log_decls.getOrPut(mod.gpa, scope.ownerDecl().?);
- if (!gop.found_existing) {
- gop.entry.value = .{
- .file_scope = scope.getFileScope(),
- .byte_offset = inst.base.src,
- };
- }
- return mod.constVoid(scope, inst.base.src);
-}
-
-fn zirArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const b = try mod.requireFunctionBlock(scope, inst.base.src);
- if (b.inlining) |inlining| {
- const param_index = inlining.param_index;
- inlining.param_index += 1;
- return inlining.casted_args[param_index];
- }
- const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
- const param_index = b.instructions.items.len;
- const param_count = fn_ty.fnParamLen();
- if (param_index >= param_count) {
- return mod.fail(scope, inst.base.src, "parameter index {d} outside list of length {d}", .{
- param_index,
- param_count,
- });
- }
- const param_type = fn_ty.fnParamType(param_index);
- const name = try scope.arena().dupeZ(u8, inst.positionals.name);
- return mod.addArg(b, inst.base.src, param_type, name);
-}
-
-fn zirLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const parent_block = scope.cast(Scope.Block).?;
-
- // Reserve space for a Loop instruction so that generated Break instructions can
- // point to it, even if it doesn't end up getting used because the code ends up being
- // comptime evaluated.
- const loop_inst = try parent_block.arena.create(Inst.Loop);
- loop_inst.* = .{
- .base = .{
- .tag = Inst.Loop.base_tag,
- .ty = Type.initTag(.noreturn),
- .src = inst.base.src,
- },
- .body = undefined,
- };
-
- var child_block: Scope.Block = .{
- .parent = parent_block,
- .inst_table = parent_block.inst_table,
- .func = parent_block.func,
- .owner_decl = parent_block.owner_decl,
- .src_decl = parent_block.src_decl,
- .instructions = .{},
- .arena = parent_block.arena,
- .inlining = parent_block.inlining,
- .is_comptime = parent_block.is_comptime,
- .branch_quota = parent_block.branch_quota,
- };
- defer child_block.instructions.deinit(mod.gpa);
-
- try analyzeBody(mod, &child_block, inst.positionals.body);
-
- // Loop repetition is implied so the last instruction may or may not be a noreturn instruction.
-
- try parent_block.instructions.append(mod.gpa, &loop_inst.base);
- loop_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) };
- return &loop_inst.base;
-}
-
-fn zirBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const parent_block = scope.cast(Scope.Block).?;
-
- var child_block = parent_block.makeSubBlock();
- defer child_block.instructions.deinit(mod.gpa);
- child_block.is_comptime = child_block.is_comptime or is_comptime;
-
- try analyzeBody(mod, &child_block, inst.positionals.body);
-
- // Move the analyzed instructions into the parent block arena.
- const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items);
- try parent_block.instructions.appendSlice(mod.gpa, copied_instructions);
-
- // The result of a flat block is the last instruction.
- const zir_inst_list = inst.positionals.body.instructions;
- const last_zir_inst = zir_inst_list[zir_inst_list.len - 1];
- return resolveInst(mod, scope, last_zir_inst);
-}
-
-fn zirBlock(
- mod: *Module,
- scope: *Scope,
- inst: *zir.Inst.Block,
- is_comptime: bool,
-) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const parent_block = scope.cast(Scope.Block).?;
-
- // Reserve space for a Block instruction so that generated Break instructions can
- // point to it, even if it doesn't end up getting used because the code ends up being
- // comptime evaluated.
- const block_inst = try parent_block.arena.create(Inst.Block);
- block_inst.* = .{
- .base = .{
- .tag = Inst.Block.base_tag,
- .ty = undefined, // Set after analysis.
- .src = inst.base.src,
- },
- .body = undefined,
- };
-
- var child_block: Scope.Block = .{
- .parent = parent_block,
- .inst_table = parent_block.inst_table,
- .func = parent_block.func,
- .owner_decl = parent_block.owner_decl,
- .src_decl = parent_block.src_decl,
- .instructions = .{},
- .arena = parent_block.arena,
- // TODO @as here is working around a stage1 miscompilation bug :(
- .label = @as(?Scope.Block.Label, Scope.Block.Label{
- .zir_block = inst,
- .merges = .{
- .results = .{},
- .br_list = .{},
- .block_inst = block_inst,
- },
- }),
- .inlining = parent_block.inlining,
- .is_comptime = is_comptime or parent_block.is_comptime,
- .branch_quota = parent_block.branch_quota,
- };
- const merges = &child_block.label.?.merges;
-
- defer child_block.instructions.deinit(mod.gpa);
- defer merges.results.deinit(mod.gpa);
- defer merges.br_list.deinit(mod.gpa);
-
- try analyzeBody(mod, &child_block, inst.positionals.body);
-
- return analyzeBlockBody(mod, scope, &child_block, merges);
-}
-
-fn analyzeBlockBody(
- mod: *Module,
- scope: *Scope,
- child_block: *Scope.Block,
- merges: *Scope.Block.Merges,
-) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const parent_block = scope.cast(Scope.Block).?;
-
- // Blocks must terminate with noreturn instruction.
- assert(child_block.instructions.items.len != 0);
- assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn());
-
- if (merges.results.items.len == 0) {
- // No need for a block instruction. We can put the new instructions
- // directly into the parent block.
- const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items);
- try parent_block.instructions.appendSlice(mod.gpa, copied_instructions);
- return copied_instructions[copied_instructions.len - 1];
- }
- if (merges.results.items.len == 1) {
- const last_inst_index = child_block.instructions.items.len - 1;
- const last_inst = child_block.instructions.items[last_inst_index];
- if (last_inst.breakBlock()) |br_block| {
- if (br_block == merges.block_inst) {
- // No need for a block instruction. We can put the new instructions directly
- // into the parent block. Here we omit the break instruction.
- const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items[0..last_inst_index]);
- try parent_block.instructions.appendSlice(mod.gpa, copied_instructions);
- return merges.results.items[0];
- }
- }
- }
- // It is impossible to have the number of results be > 1 in a comptime scope.
- assert(!child_block.is_comptime); // Should already got a compile error in the condbr condition.
-
- // Need to set the type and emit the Block instruction. This allows machine code generation
- // to emit a jump instruction to after the block when it encounters the break.
- try parent_block.instructions.append(mod.gpa, &merges.block_inst.base);
- const resolved_ty = try mod.resolvePeerTypes(scope, merges.results.items);
- merges.block_inst.base.ty = resolved_ty;
- merges.block_inst.body = .{
- .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items),
- };
- // Now that the block has its type resolved, we need to go back into all the break
- // instructions, and insert type coercion on the operands.
- for (merges.br_list.items) |br| {
- if (br.operand.ty.eql(resolved_ty)) {
- // No type coercion needed.
- continue;
- }
- var coerce_block = parent_block.makeSubBlock();
- defer coerce_block.instructions.deinit(mod.gpa);
- const coerced_operand = try mod.coerce(&coerce_block.base, resolved_ty, br.operand);
- // If no instructions were produced, such as in the case of a coercion of a
- // constant value to a new type, we can simply point the br operand to it.
- if (coerce_block.instructions.items.len == 0) {
- br.operand = coerced_operand;
- continue;
- }
- assert(coerce_block.instructions.items[coerce_block.instructions.items.len - 1] == coerced_operand);
- // Here we depend on the br instruction having been over-allocated (if necessary)
- // inide analyzeBreak so that it can be converted into a br_block_flat instruction.
- const br_src = br.base.src;
- const br_ty = br.base.ty;
- const br_block_flat = @ptrCast(*Inst.BrBlockFlat, br);
- br_block_flat.* = .{
- .base = .{
- .src = br_src,
- .ty = br_ty,
- .tag = .br_block_flat,
- },
- .block = merges.block_inst,
- .body = .{
- .instructions = try parent_block.arena.dupe(*Inst, coerce_block.instructions.items),
- },
- };
- }
- return &merges.block_inst.base;
-}
-
-fn zirBreakpoint(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .breakpoint);
-}
-
-fn zirBreak(mod: *Module, scope: *Scope, inst: *zir.Inst.Break) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const operand = try resolveInst(mod, scope, inst.positionals.operand);
- const block = inst.positionals.block;
- return analyzeBreak(mod, scope, inst.base.src, block, operand);
-}
-
-fn zirBreakVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const block = inst.positionals.block;
- const void_inst = try mod.constVoid(scope, inst.base.src);
- return analyzeBreak(mod, scope, inst.base.src, block, void_inst);
-}
-
-fn analyzeBreak(
- mod: *Module,
- scope: *Scope,
- src: usize,
- zir_block: *zir.Inst.Block,
- operand: *Inst,
-) InnerError!*Inst {
- var opt_block = scope.cast(Scope.Block);
- while (opt_block) |block| {
- if (block.label) |*label| {
- if (label.zir_block == zir_block) {
- const b = try mod.requireFunctionBlock(scope, src);
- // Here we add a br instruction, but we over-allocate a little bit
- // (if necessary) to make it possible to convert the instruction into
- // a br_block_flat instruction later.
- const br = @ptrCast(*Inst.Br, try b.arena.alignedAlloc(
- u8,
- Inst.convertable_br_align,
- Inst.convertable_br_size,
- ));
- br.* = .{
- .base = .{
- .tag = .br,
- .ty = Type.initTag(.noreturn),
- .src = src,
- },
- .operand = operand,
- .block = label.merges.block_inst,
- };
- try b.instructions.append(mod.gpa, &br.base);
- try label.merges.results.append(mod.gpa, operand);
- try label.merges.br_list.append(mod.gpa, br);
- return &br.base;
- }
- }
- opt_block = block.parent;
- } else unreachable;
-}
-
-fn zirDbgStmt(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- if (scope.cast(Scope.Block)) |b| {
- if (!b.is_comptime) {
- return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .dbg_stmt);
- }
- }
- return mod.constVoid(scope, inst.base.src);
-}
-
-fn zirDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const decl_name = try resolveConstString(mod, scope, inst.positionals.name);
- return mod.analyzeDeclRefByName(scope, inst.base.src, decl_name);
-}
-
-fn zirDeclRef(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.analyzeDeclRef(scope, inst.base.src, inst.positionals.decl);
-}
-
-fn zirDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.analyzeDeclVal(scope, inst.base.src, inst.positionals.decl);
-}
-
-fn zirCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const func = try resolveInst(mod, scope, inst.positionals.func);
- if (func.ty.zigTypeTag() != .Fn)
- return mod.fail(scope, inst.positionals.func.src, "type '{}' not a function", .{func.ty});
-
- const cc = func.ty.fnCallingConvention();
- if (cc == .Naked) {
- // TODO add error note: declared here
- return mod.fail(
- scope,
- inst.positionals.func.src,
- "unable to call function with naked calling convention",
- .{},
- );
- }
- const call_params_len = inst.positionals.args.len;
- const fn_params_len = func.ty.fnParamLen();
- if (func.ty.fnIsVarArgs()) {
- assert(cc == .C);
- if (call_params_len < fn_params_len) {
- // TODO add error note: declared here
- return mod.fail(
- scope,
- inst.positionals.func.src,
- "expected at least {d} argument(s), found {d}",
- .{ fn_params_len, call_params_len },
- );
- }
- } else if (fn_params_len != call_params_len) {
- // TODO add error note: declared here
- return mod.fail(
- scope,
- inst.positionals.func.src,
- "expected {d} argument(s), found {d}",
- .{ fn_params_len, call_params_len },
- );
- }
-
- if (inst.positionals.modifier == .compile_time) {
- return mod.fail(scope, inst.base.src, "TODO implement comptime function calls", .{});
- }
- if (inst.positionals.modifier != .auto) {
- return mod.fail(scope, inst.base.src, "TODO implement call with modifier {}", .{inst.positionals.modifier});
- }
-
- // TODO handle function calls of generic functions
- const casted_args = try scope.arena().alloc(*Inst, call_params_len);
- for (inst.positionals.args) |src_arg, i| {
- // the args are already casted to the result of a param type instruction.
- casted_args[i] = try resolveInst(mod, scope, src_arg);
- }
-
- const ret_type = func.ty.fnReturnType();
-
- const b = try mod.requireFunctionBlock(scope, inst.base.src);
- const is_comptime_call = b.is_comptime or inst.positionals.modifier == .compile_time;
- const is_inline_call = is_comptime_call or inst.positionals.modifier == .always_inline or
- func.ty.fnCallingConvention() == .Inline;
- if (is_inline_call) {
- const func_val = try mod.resolveConstValue(scope, func);
- const module_fn = switch (func_val.tag()) {
- .function => func_val.castTag(.function).?.data,
- .extern_fn => return mod.fail(scope, inst.base.src, "{s} call of extern function", .{
- @as([]const u8, if (is_comptime_call) "comptime" else "inline"),
- }),
- else => unreachable,
- };
-
- // Analyze the ZIR. The same ZIR gets analyzed into a runtime function
- // or an inlined call depending on what union tag the `label` field is
- // set to in the `Scope.Block`.
- // This block instruction will be used to capture the return value from the
- // inlined function.
- const block_inst = try scope.arena().create(Inst.Block);
- block_inst.* = .{
- .base = .{
- .tag = Inst.Block.base_tag,
- .ty = ret_type,
- .src = inst.base.src,
- },
- .body = undefined,
- };
- // If this is the top of the inline/comptime call stack, we use this data.
- // Otherwise we pass on the shared data from the parent scope.
- var shared_inlining = Scope.Block.Inlining.Shared{
- .branch_count = 0,
- .caller = b.func,
- };
- // This one is shared among sub-blocks within the same callee, but not
- // shared among the entire inline/comptime call stack.
- var inlining = Scope.Block.Inlining{
- .shared = if (b.inlining) |inlining| inlining.shared else &shared_inlining,
- .param_index = 0,
- .casted_args = casted_args,
- .merges = .{
- .results = .{},
- .br_list = .{},
- .block_inst = block_inst,
- },
- };
- var inst_table = Scope.Block.InstTable.init(mod.gpa);
- defer inst_table.deinit();
-
- var child_block: Scope.Block = .{
- .parent = null,
- .inst_table = &inst_table,
- .func = module_fn,
- .owner_decl = scope.ownerDecl().?,
- .src_decl = module_fn.owner_decl,
- .instructions = .{},
- .arena = scope.arena(),
- .label = null,
- .inlining = &inlining,
- .is_comptime = is_comptime_call,
- .branch_quota = b.branch_quota,
- };
-
- const merges = &child_block.inlining.?.merges;
-
- defer child_block.instructions.deinit(mod.gpa);
- defer merges.results.deinit(mod.gpa);
- defer merges.br_list.deinit(mod.gpa);
-
- try mod.emitBackwardBranch(&child_block, inst.base.src);
-
- // This will have return instructions analyzed as break instructions to
- // the block_inst above.
- try analyzeBody(mod, &child_block, module_fn.zir);
-
- return analyzeBlockBody(mod, scope, &child_block, merges);
- }
-
- return mod.addCall(b, inst.base.src, ret_type, func, casted_args);
-}
-
-fn zirFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const fn_type = try resolveType(mod, scope, fn_inst.positionals.fn_type);
- const new_func = try scope.arena().create(Module.Fn);
- new_func.* = .{
- .state = if (fn_type.fnCallingConvention() == .Inline) .inline_only else .queued,
- .zir = fn_inst.positionals.body,
- .body = undefined,
- .owner_decl = scope.ownerDecl().?,
- };
- return mod.constInst(scope, fn_inst.base.src, .{
- .ty = fn_type,
- .val = try Value.Tag.function.create(scope.arena(), new_func),
- });
-}
-
-fn zirAwait(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- return mod.fail(scope, inst.base.src, "TODO implement await", .{});
-}
-
-fn zirResume(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- return mod.fail(scope, inst.base.src, "TODO implement resume", .{});
-}
-
-fn zirSuspend(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- return mod.fail(scope, inst.base.src, "TODO implement suspend", .{});
-}
-
-fn zirSuspendBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst {
- return mod.fail(scope, inst.base.src, "TODO implement suspend", .{});
-}
-
-fn zirIntType(mod: *Module, scope: *Scope, inttype: *zir.Inst.IntType) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.fail(scope, inttype.base.src, "TODO implement inttype", .{});
-}
-
-fn zirOptionalType(mod: *Module, scope: *Scope, optional: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const child_type = try resolveType(mod, scope, optional.positionals.operand);
-
- return mod.constType(scope, optional.base.src, try mod.optionalType(scope, child_type));
-}
-
-fn zirOptionalTypeFromPtrElem(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const ptr = try resolveInst(mod, scope, inst.positionals.operand);
- const elem_ty = ptr.ty.elemType();
-
- return mod.constType(scope, inst.base.src, try mod.optionalType(scope, elem_ty));
-}
-
-fn zirArrayType(mod: *Module, scope: *Scope, array: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- // TODO these should be lazily evaluated
- const len = try resolveInstConst(mod, scope, array.positionals.lhs);
- const elem_type = try resolveType(mod, scope, array.positionals.rhs);
-
- return mod.constType(scope, array.base.src, try mod.arrayType(scope, len.val.toUnsignedInt(), null, elem_type));
-}
-
-fn zirArrayTypeSentinel(mod: *Module, scope: *Scope, array: *zir.Inst.ArrayTypeSentinel) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- // TODO these should be lazily evaluated
- const len = try resolveInstConst(mod, scope, array.positionals.len);
- const sentinel = try resolveInstConst(mod, scope, array.positionals.sentinel);
- const elem_type = try resolveType(mod, scope, array.positionals.elem_type);
-
- return mod.constType(scope, array.base.src, try mod.arrayType(scope, len.val.toUnsignedInt(), sentinel.val, elem_type));
-}
-
-fn zirErrorUnionType(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const error_union = try resolveType(mod, scope, inst.positionals.lhs);
- const payload = try resolveType(mod, scope, inst.positionals.rhs);
-
- if (error_union.zigTypeTag() != .ErrorSet) {
- return mod.fail(scope, inst.base.src, "expected error set type, found {}", .{error_union.elemType()});
- }
-
- return mod.constType(scope, inst.base.src, try mod.errorUnionType(scope, error_union, payload));
-}
-
-fn zirAnyframeType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const return_type = try resolveType(mod, scope, inst.positionals.operand);
-
- return mod.constType(scope, inst.base.src, try mod.anyframeType(scope, return_type));
-}
-
-fn zirErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- // The declarations arena will store the hashmap.
- var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa);
- errdefer new_decl_arena.deinit();
-
- const payload = try new_decl_arena.allocator.create(Value.Payload.ErrorSet);
- payload.* = .{
- .base = .{ .tag = .error_set },
- .data = .{
- .fields = .{},
- .decl = undefined, // populated below
- },
- };
- try payload.data.fields.ensureCapacity(&new_decl_arena.allocator, @intCast(u32, inst.positionals.fields.len));
-
- for (inst.positionals.fields) |field_name| {
- const entry = try mod.getErrorValue(field_name);
- if (payload.data.fields.fetchPutAssumeCapacity(entry.key, {})) |_| {
- return mod.fail(scope, inst.base.src, "duplicate error: '{s}'", .{field_name});
- }
- }
- // TODO create name in format "error:line:column"
- const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{
- .ty = Type.initTag(.type),
- .val = Value.initPayload(&payload.base),
- });
- payload.data.decl = new_decl;
- return mod.analyzeDeclVal(scope, inst.base.src, new_decl);
-}
-
-fn zirErrorValue(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorValue) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- // Create an anonymous error set type with only this error value, and return the value.
- const entry = try mod.getErrorValue(inst.positionals.name);
- const result_type = try Type.Tag.error_set_single.create(scope.arena(), entry.key);
- return mod.constInst(scope, inst.base.src, .{
- .ty = result_type,
- .val = try Value.Tag.@"error".create(scope.arena(), .{
- .name = entry.key,
- }),
- });
-}
-
-fn zirMergeErrorSets(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const rhs_ty = try resolveType(mod, scope, inst.positionals.rhs);
- const lhs_ty = try resolveType(mod, scope, inst.positionals.lhs);
- if (rhs_ty.zigTypeTag() != .ErrorSet)
- return mod.fail(scope, inst.positionals.rhs.src, "expected error set type, found {}", .{rhs_ty});
- if (lhs_ty.zigTypeTag() != .ErrorSet)
- return mod.fail(scope, inst.positionals.lhs.src, "expected error set type, found {}", .{lhs_ty});
-
- // anything merged with anyerror is anyerror
- if (lhs_ty.tag() == .anyerror or rhs_ty.tag() == .anyerror)
- return mod.constInst(scope, inst.base.src, .{
- .ty = Type.initTag(.type),
- .val = Value.initTag(.anyerror_type),
- });
- // The declarations arena will store the hashmap.
- var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa);
- errdefer new_decl_arena.deinit();
-
- const payload = try new_decl_arena.allocator.create(Value.Payload.ErrorSet);
- payload.* = .{
- .base = .{ .tag = .error_set },
- .data = .{
- .fields = .{},
- .decl = undefined, // populated below
- },
- };
- try payload.data.fields.ensureCapacity(&new_decl_arena.allocator, @intCast(u32, switch (rhs_ty.tag()) {
- .error_set_single => 1,
- .error_set => rhs_ty.castTag(.error_set).?.data.typed_value.most_recent.typed_value.val.castTag(.error_set).?.data.fields.size,
- else => unreachable,
- } + switch (lhs_ty.tag()) {
- .error_set_single => 1,
- .error_set => lhs_ty.castTag(.error_set).?.data.typed_value.most_recent.typed_value.val.castTag(.error_set).?.data.fields.size,
- else => unreachable,
- }));
-
- switch (lhs_ty.tag()) {
- .error_set_single => {
- const name = lhs_ty.castTag(.error_set_single).?.data;
- payload.data.fields.putAssumeCapacity(name, {});
- },
- .error_set => {
- var multiple = lhs_ty.castTag(.error_set).?.data.typed_value.most_recent.typed_value.val.castTag(.error_set).?.data.fields;
- var it = multiple.iterator();
- while (it.next()) |entry| {
- payload.data.fields.putAssumeCapacity(entry.key, entry.value);
- }
- },
- else => unreachable,
- }
-
- switch (rhs_ty.tag()) {
- .error_set_single => {
- const name = rhs_ty.castTag(.error_set_single).?.data;
- payload.data.fields.putAssumeCapacity(name, {});
- },
- .error_set => {
- var multiple = rhs_ty.castTag(.error_set).?.data.typed_value.most_recent.typed_value.val.castTag(.error_set).?.data.fields;
- var it = multiple.iterator();
- while (it.next()) |entry| {
- payload.data.fields.putAssumeCapacity(entry.key, entry.value);
- }
- },
- else => unreachable,
- }
- // TODO create name in format "error:line:column"
- const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{
- .ty = Type.initTag(.type),
- .val = Value.initPayload(&payload.base),
- });
- payload.data.decl = new_decl;
-
- return mod.analyzeDeclVal(scope, inst.base.src, new_decl);
-}
-
-fn zirEnumLiteral(mod: *Module, scope: *Scope, inst: *zir.Inst.EnumLiteral) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const duped_name = try scope.arena().dupe(u8, inst.positionals.name);
- return mod.constInst(scope, inst.base.src, .{
- .ty = Type.initTag(.enum_literal),
- .val = try Value.Tag.enum_literal.create(scope.arena(), duped_name),
- });
-}
-
-/// Pointer in, pointer out.
-fn zirOptionalPayloadPtr(
- mod: *Module,
- scope: *Scope,
- unwrap: *zir.Inst.UnOp,
- safety_check: bool,
-) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const optional_ptr = try resolveInst(mod, scope, unwrap.positionals.operand);
- assert(optional_ptr.ty.zigTypeTag() == .Pointer);
-
- const opt_type = optional_ptr.ty.elemType();
- if (opt_type.zigTypeTag() != .Optional) {
- return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{opt_type});
- }
-
- const child_type = try opt_type.optionalChildAlloc(scope.arena());
- const child_pointer = try mod.simplePtrType(scope, unwrap.base.src, child_type, !optional_ptr.ty.isConstPtr(), .One);
-
- if (optional_ptr.value()) |pointer_val| {
- const val = try pointer_val.pointerDeref(scope.arena());
- if (val.isNull()) {
- return mod.fail(scope, unwrap.base.src, "unable to unwrap null", .{});
- }
- // The same Value represents the pointer to the optional and the payload.
- return mod.constInst(scope, unwrap.base.src, .{
- .ty = child_pointer,
- .val = pointer_val,
- });
- }
-
- const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
- if (safety_check and mod.wantSafety(scope)) {
- const is_non_null = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .is_non_null_ptr, optional_ptr);
- try mod.addSafetyCheck(b, is_non_null, .unwrap_null);
- }
- return mod.addUnOp(b, unwrap.base.src, child_pointer, .optional_payload_ptr, optional_ptr);
-}
-
-/// Value in, value out.
-fn zirOptionalPayload(
- mod: *Module,
- scope: *Scope,
- unwrap: *zir.Inst.UnOp,
- safety_check: bool,
-) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
- const opt_type = operand.ty;
- if (opt_type.zigTypeTag() != .Optional) {
- return mod.fail(scope, unwrap.base.src, "expected optional type, found {}", .{opt_type});
- }
-
- const child_type = try opt_type.optionalChildAlloc(scope.arena());
-
- if (operand.value()) |val| {
- if (val.isNull()) {
- return mod.fail(scope, unwrap.base.src, "unable to unwrap null", .{});
- }
- return mod.constInst(scope, unwrap.base.src, .{
- .ty = child_type,
- .val = val,
- });
- }
-
- const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
- if (safety_check and mod.wantSafety(scope)) {
- const is_non_null = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .is_non_null, operand);
- try mod.addSafetyCheck(b, is_non_null, .unwrap_null);
- }
- return mod.addUnOp(b, unwrap.base.src, child_type, .optional_payload, operand);
-}
-
-/// Value in, value out
-fn zirErrUnionPayload(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp, safety_check: bool) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
- if (operand.ty.zigTypeTag() != .ErrorUnion)
- return mod.fail(scope, operand.src, "expected error union type, found '{}'", .{operand.ty});
-
- if (operand.value()) |val| {
- if (val.getError()) |name| {
- return mod.fail(scope, unwrap.base.src, "caught unexpected error '{s}'", .{name});
- }
- const data = val.castTag(.error_union).?.data;
- return mod.constInst(scope, unwrap.base.src, .{
- .ty = operand.ty.castTag(.error_union).?.data.payload,
- .val = data,
- });
- }
- const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
- if (safety_check and mod.wantSafety(scope)) {
- const is_non_err = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .is_err, operand);
- try mod.addSafetyCheck(b, is_non_err, .unwrap_errunion);
- }
- return mod.addUnOp(b, unwrap.base.src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_payload, operand);
-}
-
-/// Pointer in, pointer out
-fn zirErrUnionPayloadPtr(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp, safety_check: bool) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
- assert(operand.ty.zigTypeTag() == .Pointer);
-
- if (operand.ty.elemType().zigTypeTag() != .ErrorUnion)
- return mod.fail(scope, unwrap.base.src, "expected error union type, found {}", .{operand.ty.elemType()});
-
- const operand_pointer_ty = try mod.simplePtrType(scope, unwrap.base.src, operand.ty.elemType().castTag(.error_union).?.data.payload, !operand.ty.isConstPtr(), .One);
-
- if (operand.value()) |pointer_val| {
- const val = try pointer_val.pointerDeref(scope.arena());
- if (val.getError()) |name| {
- return mod.fail(scope, unwrap.base.src, "caught unexpected error '{s}'", .{name});
- }
- const data = val.castTag(.error_union).?.data;
- // The same Value represents the pointer to the error union and the payload.
- return mod.constInst(scope, unwrap.base.src, .{
- .ty = operand_pointer_ty,
- .val = try Value.Tag.ref_val.create(
- scope.arena(),
- data,
- ),
- });
- }
-
- const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
- if (safety_check and mod.wantSafety(scope)) {
- const is_non_err = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .is_err, operand);
- try mod.addSafetyCheck(b, is_non_err, .unwrap_errunion);
- }
- return mod.addUnOp(b, unwrap.base.src, operand_pointer_ty, .unwrap_errunion_payload_ptr, operand);
-}
-
-/// Value in, value out
-fn zirErrUnionCode(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
- if (operand.ty.zigTypeTag() != .ErrorUnion)
- return mod.fail(scope, unwrap.base.src, "expected error union type, found '{}'", .{operand.ty});
-
- if (operand.value()) |val| {
- assert(val.getError() != null);
- const data = val.castTag(.error_union).?.data;
- return mod.constInst(scope, unwrap.base.src, .{
- .ty = operand.ty.castTag(.error_union).?.data.error_set,
- .val = data,
- });
- }
-
- const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
- return mod.addUnOp(b, unwrap.base.src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_err, operand);
-}
-
-/// Pointer in, value out
-fn zirErrUnionCodePtr(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
- assert(operand.ty.zigTypeTag() == .Pointer);
-
- if (operand.ty.elemType().zigTypeTag() != .ErrorUnion)
- return mod.fail(scope, unwrap.base.src, "expected error union type, found {}", .{operand.ty.elemType()});
-
- if (operand.value()) |pointer_val| {
- const val = try pointer_val.pointerDeref(scope.arena());
- assert(val.getError() != null);
- const data = val.castTag(.error_union).?.data;
- return mod.constInst(scope, unwrap.base.src, .{
- .ty = operand.ty.elemType().castTag(.error_union).?.data.error_set,
- .val = data,
- });
- }
-
- const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
- return mod.addUnOp(b, unwrap.base.src, operand.ty.castTag(.error_union).?.data.payload, .unwrap_errunion_err_ptr, operand);
-}
-
-fn zirEnsureErrPayloadVoid(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const operand = try resolveInst(mod, scope, unwrap.positionals.operand);
- if (operand.ty.zigTypeTag() != .ErrorUnion)
- return mod.fail(scope, unwrap.base.src, "expected error union type, found '{}'", .{operand.ty});
- if (operand.ty.castTag(.error_union).?.data.payload.zigTypeTag() != .Void) {
- return mod.fail(scope, unwrap.base.src, "expression value is ignored", .{});
- }
- return mod.constVoid(scope, unwrap.base.src);
-}
-
-fn zirFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType, var_args: bool) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- return fnTypeCommon(
- mod,
- scope,
- &fntype.base,
- fntype.positionals.param_types,
- fntype.positionals.return_type,
- .Unspecified,
- var_args,
- );
-}
-
-fn zirFnTypeCc(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnTypeCc, var_args: bool) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const cc_tv = try resolveInstConst(mod, scope, fntype.positionals.cc);
- // TODO once we're capable of importing and analyzing decls from
- // std.builtin, this needs to change
- const cc_str = cc_tv.val.castTag(.enum_literal).?.data;
- const cc = std.meta.stringToEnum(std.builtin.CallingConvention, cc_str) orelse
- return mod.fail(scope, fntype.positionals.cc.src, "Unknown calling convention {s}", .{cc_str});
- return fnTypeCommon(
- mod,
- scope,
- &fntype.base,
- fntype.positionals.param_types,
- fntype.positionals.return_type,
- cc,
- var_args,
- );
-}
-
-fn fnTypeCommon(
- mod: *Module,
- scope: *Scope,
- zir_inst: *zir.Inst,
- zir_param_types: []*zir.Inst,
- zir_return_type: *zir.Inst,
- cc: std.builtin.CallingConvention,
- var_args: bool,
-) InnerError!*Inst {
- const return_type = try resolveType(mod, scope, zir_return_type);
-
- // Hot path for some common function types.
- if (zir_param_types.len == 0 and !var_args) {
- if (return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) {
- return mod.constType(scope, zir_inst.src, Type.initTag(.fn_noreturn_no_args));
- }
-
- if (return_type.zigTypeTag() == .Void and cc == .Unspecified) {
- return mod.constType(scope, zir_inst.src, Type.initTag(.fn_void_no_args));
- }
-
- if (return_type.zigTypeTag() == .NoReturn and cc == .Naked) {
- return mod.constType(scope, zir_inst.src, Type.initTag(.fn_naked_noreturn_no_args));
- }
-
- if (return_type.zigTypeTag() == .Void and cc == .C) {
- return mod.constType(scope, zir_inst.src, Type.initTag(.fn_ccc_void_no_args));
- }
- }
-
- const arena = scope.arena();
- const param_types = try arena.alloc(Type, zir_param_types.len);
- for (zir_param_types) |param_type, i| {
- const resolved = try resolveType(mod, scope, param_type);
- // TODO skip for comptime params
- if (!resolved.isValidVarType(false)) {
- return mod.fail(scope, param_type.src, "parameter of type '{}' must be declared comptime", .{resolved});
- }
- param_types[i] = resolved;
- }
-
- const fn_ty = try Type.Tag.function.create(arena, .{
- .param_types = param_types,
- .return_type = return_type,
- .cc = cc,
- .is_var_args = var_args,
- });
- return mod.constType(scope, zir_inst.src, fn_ty);
-}
-
-fn zirPrimitive(mod: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.constInst(scope, primitive.base.src, primitive.positionals.tag.toTypedValue());
-}
-
-fn zirAs(mod: *Module, scope: *Scope, as: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const dest_type = try resolveType(mod, scope, as.positionals.lhs);
- const new_inst = try resolveInst(mod, scope, as.positionals.rhs);
- return mod.coerce(scope, dest_type, new_inst);
-}
-
-fn zirPtrtoint(mod: *Module, scope: *Scope, ptrtoint: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const ptr = try resolveInst(mod, scope, ptrtoint.positionals.operand);
- if (ptr.ty.zigTypeTag() != .Pointer) {
- return mod.fail(scope, ptrtoint.positionals.operand.src, "expected pointer, found '{}'", .{ptr.ty});
- }
- // TODO handle known-pointer-address
- const b = try mod.requireRuntimeBlock(scope, ptrtoint.base.src);
- const ty = Type.initTag(.usize);
- return mod.addUnOp(b, ptrtoint.base.src, ty, .ptrtoint, ptr);
-}
-
-fn zirFieldVal(mod: *Module, scope: *Scope, inst: *zir.Inst.Field) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const object = try resolveInst(mod, scope, inst.positionals.object);
- const field_name = inst.positionals.field_name;
- const object_ptr = try mod.analyzeRef(scope, inst.base.src, object);
- const result_ptr = try mod.namedFieldPtr(scope, inst.base.src, object_ptr, field_name, inst.base.src);
- return mod.analyzeDeref(scope, inst.base.src, result_ptr, result_ptr.src);
-}
-
-fn zirFieldPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.Field) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const object_ptr = try resolveInst(mod, scope, inst.positionals.object);
- const field_name = inst.positionals.field_name;
- return mod.namedFieldPtr(scope, inst.base.src, object_ptr, field_name, inst.base.src);
-}
-
-fn zirFieldValNamed(mod: *Module, scope: *Scope, inst: *zir.Inst.FieldNamed) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const object = try resolveInst(mod, scope, inst.positionals.object);
- const field_name = try resolveConstString(mod, scope, inst.positionals.field_name);
- const fsrc = inst.positionals.field_name.src;
- const object_ptr = try mod.analyzeRef(scope, inst.base.src, object);
- const result_ptr = try mod.namedFieldPtr(scope, inst.base.src, object_ptr, field_name, fsrc);
- return mod.analyzeDeref(scope, inst.base.src, result_ptr, result_ptr.src);
-}
-
-fn zirFieldPtrNamed(mod: *Module, scope: *Scope, inst: *zir.Inst.FieldNamed) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const object_ptr = try resolveInst(mod, scope, inst.positionals.object);
- const field_name = try resolveConstString(mod, scope, inst.positionals.field_name);
- const fsrc = inst.positionals.field_name.src;
- return mod.namedFieldPtr(scope, inst.base.src, object_ptr, field_name, fsrc);
-}
-
-fn zirIntcast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const dest_type = try resolveType(mod, scope, inst.positionals.lhs);
- const operand = try resolveInst(mod, scope, inst.positionals.rhs);
-
- const dest_is_comptime_int = switch (dest_type.zigTypeTag()) {
- .ComptimeInt => true,
- .Int => false,
- else => return mod.fail(
- scope,
- inst.positionals.lhs.src,
- "expected integer type, found '{}'",
- .{
- dest_type,
- },
- ),
- };
-
- switch (operand.ty.zigTypeTag()) {
- .ComptimeInt, .Int => {},
- else => return mod.fail(
- scope,
- inst.positionals.rhs.src,
- "expected integer type, found '{}'",
- .{operand.ty},
- ),
- }
-
- if (operand.value() != null) {
- return mod.coerce(scope, dest_type, operand);
- } else if (dest_is_comptime_int) {
- return mod.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_int'", .{});
- }
-
- return mod.fail(scope, inst.base.src, "TODO implement analyze widen or shorten int", .{});
-}
-
-fn zirBitcast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const dest_type = try resolveType(mod, scope, inst.positionals.lhs);
- const operand = try resolveInst(mod, scope, inst.positionals.rhs);
- return mod.bitcast(scope, dest_type, operand);
-}
-
-fn zirFloatcast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const dest_type = try resolveType(mod, scope, inst.positionals.lhs);
- const operand = try resolveInst(mod, scope, inst.positionals.rhs);
-
- const dest_is_comptime_float = switch (dest_type.zigTypeTag()) {
- .ComptimeFloat => true,
- .Float => false,
- else => return mod.fail(
- scope,
- inst.positionals.lhs.src,
- "expected float type, found '{}'",
- .{
- dest_type,
- },
- ),
- };
-
- switch (operand.ty.zigTypeTag()) {
- .ComptimeFloat, .Float, .ComptimeInt => {},
- else => return mod.fail(
- scope,
- inst.positionals.rhs.src,
- "expected float type, found '{}'",
- .{operand.ty},
- ),
- }
-
- if (operand.value() != null) {
- return mod.coerce(scope, dest_type, operand);
- } else if (dest_is_comptime_float) {
- return mod.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_float'", .{});
- }
-
- return mod.fail(scope, inst.base.src, "TODO implement analyze widen or shorten float", .{});
-}
-
-fn zirElemVal(mod: *Module, scope: *Scope, inst: *zir.Inst.Elem) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const array = try resolveInst(mod, scope, inst.positionals.array);
- const array_ptr = try mod.analyzeRef(scope, inst.base.src, array);
- const elem_index = try resolveInst(mod, scope, inst.positionals.index);
- const result_ptr = try mod.elemPtr(scope, inst.base.src, array_ptr, elem_index);
- return mod.analyzeDeref(scope, inst.base.src, result_ptr, result_ptr.src);
-}
-
-fn zirElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.Elem) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const array_ptr = try resolveInst(mod, scope, inst.positionals.array);
- const elem_index = try resolveInst(mod, scope, inst.positionals.index);
- return mod.elemPtr(scope, inst.base.src, array_ptr, elem_index);
-}
-
-fn zirSlice(mod: *Module, scope: *Scope, inst: *zir.Inst.Slice) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr);
- const start = try resolveInst(mod, scope, inst.positionals.start);
- const end = if (inst.kw_args.end) |end| try resolveInst(mod, scope, end) else null;
- const sentinel = if (inst.kw_args.sentinel) |sentinel| try resolveInst(mod, scope, sentinel) else null;
-
- return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, end, sentinel);
-}
-
-fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const array_ptr = try resolveInst(mod, scope, inst.positionals.lhs);
- const start = try resolveInst(mod, scope, inst.positionals.rhs);
-
- return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null);
-}
-
-fn zirSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const start = try resolveInst(mod, scope, inst.positionals.lhs);
- const end = try resolveInst(mod, scope, inst.positionals.rhs);
-
- switch (start.ty.zigTypeTag()) {
- .Int, .ComptimeInt => {},
- else => return mod.constVoid(scope, inst.base.src),
- }
- switch (end.ty.zigTypeTag()) {
- .Int, .ComptimeInt => {},
- else => return mod.constVoid(scope, inst.base.src),
- }
- // .switch_range must be inside a comptime scope
- const start_val = start.value().?;
- const end_val = end.value().?;
- if (start_val.compare(.gte, end_val)) {
- return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{});
- }
- return mod.constVoid(scope, inst.base.src);
-}
-
-fn zirSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr, ref: bool) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const target_ptr = try resolveInst(mod, scope, inst.positionals.target);
- const target = if (ref)
- try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target.src)
- else
- target_ptr;
- try validateSwitch(mod, scope, target, inst);
-
- if (try mod.resolveDefinedValue(scope, target)) |target_val| {
- for (inst.positionals.cases) |case| {
- const resolved = try resolveInst(mod, scope, case.item);
- const casted = try mod.coerce(scope, target.ty, resolved);
- const item = try mod.resolveConstValue(scope, casted);
-
- if (target_val.eql(item)) {
- try analyzeBody(mod, scope.cast(Scope.Block).?, case.body);
- return mod.constNoReturn(scope, inst.base.src);
- }
- }
- try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
- return mod.constNoReturn(scope, inst.base.src);
- }
-
- if (inst.positionals.cases.len == 0) {
- // no cases just analyze else_branch
- try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body);
- return mod.constNoReturn(scope, inst.base.src);
- }
-
- const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);
- const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len);
-
- var case_block: Scope.Block = .{
- .parent = parent_block,
- .inst_table = parent_block.inst_table,
- .func = parent_block.func,
- .owner_decl = parent_block.owner_decl,
- .src_decl = parent_block.src_decl,
- .instructions = .{},
- .arena = parent_block.arena,
- .inlining = parent_block.inlining,
- .is_comptime = parent_block.is_comptime,
- .branch_quota = parent_block.branch_quota,
- };
- defer case_block.instructions.deinit(mod.gpa);
-
- for (inst.positionals.cases) |case, i| {
- // Reset without freeing.
- case_block.instructions.items.len = 0;
-
- const resolved = try resolveInst(mod, scope, case.item);
- const casted = try mod.coerce(scope, target.ty, resolved);
- const item = try mod.resolveConstValue(scope, casted);
-
- try analyzeBody(mod, &case_block, case.body);
-
- cases[i] = .{
- .item = item,
- .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) },
- };
- }
-
- case_block.instructions.items.len = 0;
- try analyzeBody(mod, &case_block, inst.positionals.else_body);
-
- const else_body: ir.Body = .{
- .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items),
- };
-
- return mod.addSwitchBr(parent_block, inst.base.src, target, cases, else_body);
-}
-
-fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void {
- // validate usage of '_' prongs
- if (inst.positionals.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) {
- return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{});
- // TODO notes "'_' prong here" inst.positionals.cases[last].src
- }
-
- // check that target type supports ranges
- if (inst.positionals.range) |range_inst| {
- switch (target.ty.zigTypeTag()) {
- .Int, .ComptimeInt => {},
- else => {
- return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty});
- // TODO notes "range used here" range_inst.src
- },
- }
- }
-
- // validate for duplicate items/missing else prong
- switch (target.ty.zigTypeTag()) {
- .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}),
- .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
- .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}),
- .Int, .ComptimeInt => {
- var range_set = @import("RangeSet.zig").init(mod.gpa);
- defer range_set.deinit();
-
- for (inst.positionals.items) |item| {
- const maybe_src = if (item.castTag(.switch_range)) |range| blk: {
- const start_resolved = try resolveInst(mod, scope, range.positionals.lhs);
- const start_casted = try mod.coerce(scope, target.ty, start_resolved);
- const end_resolved = try resolveInst(mod, scope, range.positionals.rhs);
- const end_casted = try mod.coerce(scope, target.ty, end_resolved);
-
- break :blk try range_set.add(
- try mod.resolveConstValue(scope, start_casted),
- try mod.resolveConstValue(scope, end_casted),
- item.src,
- );
- } else blk: {
- const resolved = try resolveInst(mod, scope, item);
- const casted = try mod.coerce(scope, target.ty, resolved);
- const value = try mod.resolveConstValue(scope, casted);
- break :blk try range_set.add(value, value, item.src);
- };
-
- if (maybe_src) |previous_src| {
- return mod.fail(scope, item.src, "duplicate switch value", .{});
- // TODO notes "previous value is here" previous_src
- }
- }
-
- if (target.ty.zigTypeTag() == .Int) {
- var arena = std.heap.ArenaAllocator.init(mod.gpa);
- defer arena.deinit();
-
- const start = try target.ty.minInt(&arena, mod.getTarget());
- const end = try target.ty.maxInt(&arena, mod.getTarget());
- if (try range_set.spans(start, end)) {
- if (inst.positionals.special_prong == .@"else") {
- return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
- }
- return;
- }
- }
-
- if (inst.positionals.special_prong != .@"else") {
- return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
- }
- },
- .Bool => {
- var true_count: u8 = 0;
- var false_count: u8 = 0;
- for (inst.positionals.items) |item| {
- const resolved = try resolveInst(mod, scope, item);
- const casted = try mod.coerce(scope, Type.initTag(.bool), resolved);
- if ((try mod.resolveConstValue(scope, casted)).toBool()) {
- true_count += 1;
- } else {
- false_count += 1;
- }
-
- if (true_count + false_count > 2) {
- return mod.fail(scope, item.src, "duplicate switch value", .{});
- }
- }
- if ((true_count + false_count < 2) and inst.positionals.special_prong != .@"else") {
- return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
- }
- if ((true_count + false_count == 2) and inst.positionals.special_prong == .@"else") {
- return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
- }
- },
- .EnumLiteral, .Void, .Fn, .Pointer, .Type => {
- if (inst.positionals.special_prong != .@"else") {
- return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty});
- }
-
- var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa);
- defer seen_values.deinit();
-
- for (inst.positionals.items) |item| {
- const resolved = try resolveInst(mod, scope, item);
- const casted = try mod.coerce(scope, target.ty, resolved);
- const val = try mod.resolveConstValue(scope, casted);
-
- if (try seen_values.fetchPut(val, item.src)) |prev| {
- return mod.fail(scope, item.src, "duplicate switch value", .{});
- // TODO notes "previous value here" prev.value
- }
- }
- },
-
- .ErrorUnion,
- .NoReturn,
- .Array,
- .Struct,
- .Undefined,
- .Null,
- .Optional,
- .BoundFn,
- .Opaque,
- .Vector,
- .Frame,
- .AnyFrame,
- .ComptimeFloat,
- .Float,
- => {
- return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty});
- },
- }
-}
-
-fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const operand = try resolveConstString(mod, scope, inst.positionals.operand);
-
- const file_scope = mod.analyzeImport(scope, inst.base.src, operand) catch |err| switch (err) {
- error.ImportOutsidePkgPath => {
- return mod.fail(scope, inst.base.src, "import of file outside package path: '{s}'", .{operand});
- },
- error.FileNotFound => {
- return mod.fail(scope, inst.base.src, "unable to find '{s}'", .{operand});
- },
- else => {
- // TODO: make sure this gets retried and not cached
- return mod.fail(scope, inst.base.src, "unable to open '{s}': {s}", .{ operand, @errorName(err) });
- },
- };
- return mod.constType(scope, inst.base.src, file_scope.root_container.ty);
-}
-
-fn zirShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.fail(scope, inst.base.src, "TODO implement zirShl", .{});
-}
-
-fn zirShr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.fail(scope, inst.base.src, "TODO implement zirShr", .{});
-}
-
-fn zirBitwise(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const lhs = try resolveInst(mod, scope, inst.positionals.lhs);
- const rhs = try resolveInst(mod, scope, inst.positionals.rhs);
-
- const instructions = &[_]*Inst{ lhs, rhs };
- const resolved_type = try mod.resolvePeerTypes(scope, instructions);
- const casted_lhs = try mod.coerce(scope, resolved_type, lhs);
- const casted_rhs = try mod.coerce(scope, resolved_type, rhs);
-
- const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
- resolved_type.elemType()
- else
- resolved_type;
-
- const scalar_tag = scalar_type.zigTypeTag();
-
- if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) {
- if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
- return mod.fail(scope, inst.base.src, "vector length mismatch: {d} and {d}", .{
- lhs.ty.arrayLen(),
- rhs.ty.arrayLen(),
- });
- }
- return mod.fail(scope, inst.base.src, "TODO implement support for vectors in zirBitwise", .{});
- } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) {
- return mod.fail(scope, inst.base.src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
- lhs.ty,
- rhs.ty,
- });
- }
-
- const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
-
- if (!is_int) {
- return mod.fail(scope, inst.base.src, "invalid operands to binary bitwise expression: '{s}' and '{s}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) });
- }
-
- if (casted_lhs.value()) |lhs_val| {
- if (casted_rhs.value()) |rhs_val| {
- if (lhs_val.isUndef() or rhs_val.isUndef()) {
- return mod.constInst(scope, inst.base.src, .{
- .ty = resolved_type,
- .val = Value.initTag(.undef),
- });
- }
- return mod.fail(scope, inst.base.src, "TODO implement comptime bitwise operations", .{});
- }
- }
-
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- const ir_tag = switch (inst.base.tag) {
- .bit_and => Inst.Tag.bit_and,
- .bit_or => Inst.Tag.bit_or,
- .xor => Inst.Tag.xor,
- else => unreachable,
- };
-
- return mod.addBinOp(b, inst.base.src, scalar_type, ir_tag, casted_lhs, casted_rhs);
-}
-
-fn zirBitNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.fail(scope, inst.base.src, "TODO implement zirBitNot", .{});
-}
-
-fn zirArrayCat(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.fail(scope, inst.base.src, "TODO implement zirArrayCat", .{});
-}
-
-fn zirArrayMul(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- return mod.fail(scope, inst.base.src, "TODO implement zirArrayMul", .{});
-}
-
-fn zirArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const lhs = try resolveInst(mod, scope, inst.positionals.lhs);
- const rhs = try resolveInst(mod, scope, inst.positionals.rhs);
-
- const instructions = &[_]*Inst{ lhs, rhs };
- const resolved_type = try mod.resolvePeerTypes(scope, instructions);
- const casted_lhs = try mod.coerce(scope, resolved_type, lhs);
- const casted_rhs = try mod.coerce(scope, resolved_type, rhs);
-
- const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
- resolved_type.elemType()
- else
- resolved_type;
-
- const scalar_tag = scalar_type.zigTypeTag();
-
- if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) {
- if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
- return mod.fail(scope, inst.base.src, "vector length mismatch: {d} and {d}", .{
- lhs.ty.arrayLen(),
- rhs.ty.arrayLen(),
- });
- }
- return mod.fail(scope, inst.base.src, "TODO implement support for vectors in zirBinOp", .{});
- } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) {
- return mod.fail(scope, inst.base.src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
- lhs.ty,
- rhs.ty,
- });
- }
-
- const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
- const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat;
-
- if (!is_int and !(is_float and floatOpAllowed(inst.base.tag))) {
- return mod.fail(scope, inst.base.src, "invalid operands to binary expression: '{s}' and '{s}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) });
- }
-
- if (casted_lhs.value()) |lhs_val| {
- if (casted_rhs.value()) |rhs_val| {
- if (lhs_val.isUndef() or rhs_val.isUndef()) {
- return mod.constInst(scope, inst.base.src, .{
- .ty = resolved_type,
- .val = Value.initTag(.undef),
- });
- }
- return analyzeInstComptimeOp(mod, scope, scalar_type, inst, lhs_val, rhs_val);
- }
- }
-
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- const ir_tag: Inst.Tag = switch (inst.base.tag) {
- .add => .add,
- .addwrap => .addwrap,
- .sub => .sub,
- .subwrap => .subwrap,
- .mul => .mul,
- .mulwrap => .mulwrap,
- else => return mod.fail(scope, inst.base.src, "TODO implement arithmetic for operand '{s}''", .{@tagName(inst.base.tag)}),
- };
-
- return mod.addBinOp(b, inst.base.src, scalar_type, ir_tag, casted_lhs, casted_rhs);
-}
-
-/// Analyzes operands that are known at comptime
-fn analyzeInstComptimeOp(mod: *Module, scope: *Scope, res_type: Type, inst: *zir.Inst.BinOp, lhs_val: Value, rhs_val: Value) InnerError!*Inst {
- // incase rhs is 0, simply return lhs without doing any calculations
- // TODO Once division is implemented we should throw an error when dividing by 0.
- if (rhs_val.compareWithZero(.eq)) {
- return mod.constInst(scope, inst.base.src, .{
- .ty = res_type,
- .val = lhs_val,
- });
- }
- const is_int = res_type.isInt() or res_type.zigTypeTag() == .ComptimeInt;
-
- const value = switch (inst.base.tag) {
- .add => blk: {
- const val = if (is_int)
- try Module.intAdd(scope.arena(), lhs_val, rhs_val)
- else
- try mod.floatAdd(scope, res_type, inst.base.src, lhs_val, rhs_val);
- break :blk val;
- },
- .sub => blk: {
- const val = if (is_int)
- try Module.intSub(scope.arena(), lhs_val, rhs_val)
- else
- try mod.floatSub(scope, res_type, inst.base.src, lhs_val, rhs_val);
- break :blk val;
- },
- else => return mod.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{s}'", .{@tagName(inst.base.tag)}),
- };
-
- log.debug("{s}({}, {}) result: {}", .{ @tagName(inst.base.tag), lhs_val, rhs_val, value });
-
- return mod.constInst(scope, inst.base.src, .{
- .ty = res_type,
- .val = value,
- });
-}
-
-fn zirDeref(mod: *Module, scope: *Scope, deref: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const ptr = try resolveInst(mod, scope, deref.positionals.operand);
- return mod.analyzeDeref(scope, deref.base.src, ptr, deref.positionals.operand.src);
-}
-
-fn zirAsm(mod: *Module, scope: *Scope, assembly: *zir.Inst.Asm) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const return_type = try resolveType(mod, scope, assembly.positionals.return_type);
- const asm_source = try resolveConstString(mod, scope, assembly.positionals.asm_source);
- const output = if (assembly.kw_args.output) |o| try resolveConstString(mod, scope, o) else null;
-
- const arena = scope.arena();
- const inputs = try arena.alloc([]const u8, assembly.kw_args.inputs.len);
- const clobbers = try arena.alloc([]const u8, assembly.kw_args.clobbers.len);
- const args = try arena.alloc(*Inst, assembly.kw_args.args.len);
-
- for (inputs) |*elem, i| {
- elem.* = try arena.dupe(u8, assembly.kw_args.inputs[i]);
- }
- for (clobbers) |*elem, i| {
- elem.* = try arena.dupe(u8, assembly.kw_args.clobbers[i]);
- }
- for (args) |*elem, i| {
- const arg = try resolveInst(mod, scope, assembly.kw_args.args[i]);
- elem.* = try mod.coerce(scope, Type.initTag(.usize), arg);
- }
-
- const b = try mod.requireRuntimeBlock(scope, assembly.base.src);
- const inst = try b.arena.create(Inst.Assembly);
- inst.* = .{
- .base = .{
- .tag = .assembly,
- .ty = return_type,
- .src = assembly.base.src,
- },
- .asm_source = asm_source,
- .is_volatile = assembly.kw_args.@"volatile",
- .output = output,
- .inputs = inputs,
- .clobbers = clobbers,
- .args = args,
- };
- try b.instructions.append(mod.gpa, &inst.base);
- return &inst.base;
-}
-
-fn zirCmp(
- mod: *Module,
- scope: *Scope,
- inst: *zir.Inst.BinOp,
- op: std.math.CompareOperator,
-) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const lhs = try resolveInst(mod, scope, inst.positionals.lhs);
- const rhs = try resolveInst(mod, scope, inst.positionals.rhs);
-
- const is_equality_cmp = switch (op) {
- .eq, .neq => true,
- else => false,
- };
- const lhs_ty_tag = lhs.ty.zigTypeTag();
- const rhs_ty_tag = rhs.ty.zigTypeTag();
- if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {
- // null == null, null != null
- return mod.constBool(scope, inst.base.src, op == .eq);
- } else if (is_equality_cmp and
- ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
- rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
- {
- // comparing null with optionals
- const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs;
- return mod.analyzeIsNull(scope, inst.base.src, opt_operand, op == .neq);
- } else if (is_equality_cmp and
- ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))
- {
- return mod.fail(scope, inst.base.src, "TODO implement C pointer cmp", .{});
- } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
- const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty;
- return mod.fail(scope, inst.base.src, "comparison of '{}' with null", .{non_null_type});
- } else if (is_equality_cmp and
- ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or
- (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))
- {
- return mod.fail(scope, inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
- } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
- if (!is_equality_cmp) {
- return mod.fail(scope, inst.base.src, "{s} operator not allowed for errors", .{@tagName(op)});
- }
- if (rhs.value()) |rval| {
- if (lhs.value()) |lval| {
- // TODO optimisation oppurtunity: evaluate if std.mem.eql is faster with the names, or calling to Module.getErrorValue to get the values and then compare them is faster
- return mod.constBool(scope, inst.base.src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq));
- }
- }
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- return mod.addBinOp(b, inst.base.src, Type.initTag(.bool), if (op == .eq) .cmp_eq else .cmp_neq, lhs, rhs);
- } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) {
- // This operation allows any combination of integer and float types, regardless of the
- // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
- // numeric types.
- return mod.cmpNumeric(scope, inst.base.src, lhs, rhs, op);
- } else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) {
- if (!is_equality_cmp) {
- return mod.fail(scope, inst.base.src, "{s} operator not allowed for types", .{@tagName(op)});
- }
- return mod.constBool(scope, inst.base.src, lhs.value().?.eql(rhs.value().?) == (op == .eq));
- }
- return mod.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{});
-}
-
-fn zirTypeof(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const operand = try resolveInst(mod, scope, inst.positionals.operand);
- return mod.constType(scope, inst.base.src, operand.ty);
-}
-
-fn zirTypeofPeer(mod: *Module, scope: *Scope, inst: *zir.Inst.TypeOfPeer) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- var insts_to_res = try mod.gpa.alloc(*ir.Inst, inst.positionals.items.len);
- defer mod.gpa.free(insts_to_res);
- for (inst.positionals.items) |item, i| {
- insts_to_res[i] = try resolveInst(mod, scope, item);
- }
- const pt_res = try mod.resolvePeerTypes(scope, insts_to_res);
- return mod.constType(scope, inst.base.src, pt_res);
-}
-
-fn zirBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const uncasted_operand = try resolveInst(mod, scope, inst.positionals.operand);
- const bool_type = Type.initTag(.bool);
- const operand = try mod.coerce(scope, bool_type, uncasted_operand);
- if (try mod.resolveDefinedValue(scope, operand)) |val| {
- return mod.constBool(scope, inst.base.src, !val.toBool());
- }
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- return mod.addUnOp(b, inst.base.src, bool_type, .not, operand);
-}
-
-fn zirBoolOp(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const bool_type = Type.initTag(.bool);
- const uncasted_lhs = try resolveInst(mod, scope, inst.positionals.lhs);
- const lhs = try mod.coerce(scope, bool_type, uncasted_lhs);
- const uncasted_rhs = try resolveInst(mod, scope, inst.positionals.rhs);
- const rhs = try mod.coerce(scope, bool_type, uncasted_rhs);
-
- const is_bool_or = inst.base.tag == .bool_or;
-
- if (lhs.value()) |lhs_val| {
- if (rhs.value()) |rhs_val| {
- if (is_bool_or) {
- return mod.constBool(scope, inst.base.src, lhs_val.toBool() or rhs_val.toBool());
- } else {
- return mod.constBool(scope, inst.base.src, lhs_val.toBool() and rhs_val.toBool());
- }
- }
- }
- const b = try mod.requireRuntimeBlock(scope, inst.base.src);
- return mod.addBinOp(b, inst.base.src, bool_type, if (is_bool_or) .bool_or else .bool_and, lhs, rhs);
-}
-
-fn zirIsNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const operand = try resolveInst(mod, scope, inst.positionals.operand);
- return mod.analyzeIsNull(scope, inst.base.src, operand, invert_logic);
-}
-
-fn zirIsNullPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const ptr = try resolveInst(mod, scope, inst.positionals.operand);
- const loaded = try mod.analyzeDeref(scope, inst.base.src, ptr, ptr.src);
- return mod.analyzeIsNull(scope, inst.base.src, loaded, invert_logic);
-}
-
-fn zirIsErr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const operand = try resolveInst(mod, scope, inst.positionals.operand);
- return mod.analyzeIsErr(scope, inst.base.src, operand);
-}
-
-fn zirIsErrPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const ptr = try resolveInst(mod, scope, inst.positionals.operand);
- const loaded = try mod.analyzeDeref(scope, inst.base.src, ptr, ptr.src);
- return mod.analyzeIsErr(scope, inst.base.src, loaded);
-}
-
-fn zirCondbr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const uncasted_cond = try resolveInst(mod, scope, inst.positionals.condition);
- const cond = try mod.coerce(scope, Type.initTag(.bool), uncasted_cond);
-
- const parent_block = scope.cast(Scope.Block).?;
-
- if (try mod.resolveDefinedValue(scope, cond)) |cond_val| {
- const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body;
- try analyzeBody(mod, parent_block, body.*);
- return mod.constNoReturn(scope, inst.base.src);
- }
-
- var true_block: Scope.Block = .{
- .parent = parent_block,
- .inst_table = parent_block.inst_table,
- .func = parent_block.func,
- .owner_decl = parent_block.owner_decl,
- .src_decl = parent_block.src_decl,
- .instructions = .{},
- .arena = parent_block.arena,
- .inlining = parent_block.inlining,
- .is_comptime = parent_block.is_comptime,
- .branch_quota = parent_block.branch_quota,
- };
- defer true_block.instructions.deinit(mod.gpa);
- try analyzeBody(mod, &true_block, inst.positionals.then_body);
-
- var false_block: Scope.Block = .{
- .parent = parent_block,
- .inst_table = parent_block.inst_table,
- .func = parent_block.func,
- .owner_decl = parent_block.owner_decl,
- .src_decl = parent_block.src_decl,
- .instructions = .{},
- .arena = parent_block.arena,
- .inlining = parent_block.inlining,
- .is_comptime = parent_block.is_comptime,
- .branch_quota = parent_block.branch_quota,
- };
- defer false_block.instructions.deinit(mod.gpa);
- try analyzeBody(mod, &false_block, inst.positionals.else_body);
-
- const then_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) };
- const else_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) };
- return mod.addCondBr(parent_block, inst.base.src, cond, then_body, else_body);
-}
-
-fn zirUnreachable(
- mod: *Module,
- scope: *Scope,
- unreach: *zir.Inst.NoOp,
- safety_check: bool,
-) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const b = try mod.requireRuntimeBlock(scope, unreach.base.src);
- // TODO Add compile error for @optimizeFor occurring too late in a scope.
- if (safety_check and mod.wantSafety(scope)) {
- return mod.safetyPanic(b, unreach.base.src, .unreach);
- } else {
- return mod.addNoOp(b, unreach.base.src, Type.initTag(.noreturn), .unreach);
- }
-}
-
-fn zirReturn(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const operand = try resolveInst(mod, scope, inst.positionals.operand);
- const b = try mod.requireFunctionBlock(scope, inst.base.src);
-
- if (b.inlining) |inlining| {
- // We are inlining a function call; rewrite the `ret` as a `break`.
- try inlining.merges.results.append(mod.gpa, operand);
- const br = try mod.addBr(b, inst.base.src, inlining.merges.block_inst, operand);
- return &br.base;
- }
-
- return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand);
-}
-
-fn zirReturnVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const b = try mod.requireFunctionBlock(scope, inst.base.src);
- if (b.inlining) |inlining| {
- // We are inlining a function call; rewrite the `retvoid` as a `breakvoid`.
- const void_inst = try mod.constVoid(scope, inst.base.src);
- try inlining.merges.results.append(mod.gpa, void_inst);
- const br = try mod.addBr(b, inst.base.src, inlining.merges.block_inst, void_inst);
- return &br.base;
- }
-
- if (b.func) |func| {
- // Need to emit a compile error if returning void is not allowed.
- const void_inst = try mod.constVoid(scope, inst.base.src);
- const fn_ty = func.owner_decl.typed_value.most_recent.typed_value.ty;
- const casted_void = try mod.coerce(scope, fn_ty.fnReturnType(), void_inst);
- if (casted_void.ty.zigTypeTag() != .Void) {
- return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, casted_void);
- }
- }
- return mod.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid);
-}
-
-fn floatOpAllowed(tag: zir.Inst.Tag) bool {
- // extend this swich as additional operators are implemented
- return switch (tag) {
- .add, .sub => true,
- else => false,
- };
-}
-
-fn zirSimplePtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, mutable: bool, size: std.builtin.TypeInfo.Pointer.Size) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- const elem_type = try resolveType(mod, scope, inst.positionals.operand);
- const ty = try mod.simplePtrType(scope, inst.base.src, elem_type, mutable, size);
- return mod.constType(scope, inst.base.src, ty);
-}
-
-fn zirPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.PtrType) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
- // TODO lazy values
- const @"align" = if (inst.kw_args.@"align") |some|
- @truncate(u32, try resolveInt(mod, scope, some, Type.initTag(.u32)))
- else
- 0;
- const bit_offset = if (inst.kw_args.align_bit_start) |some|
- @truncate(u16, try resolveInt(mod, scope, some, Type.initTag(.u16)))
- else
- 0;
- const host_size = if (inst.kw_args.align_bit_end) |some|
- @truncate(u16, try resolveInt(mod, scope, some, Type.initTag(.u16)))
- else
- 0;
-
- if (host_size != 0 and bit_offset >= host_size * 8)
- return mod.fail(scope, inst.base.src, "bit offset starts after end of host integer", .{});
-
- const sentinel = if (inst.kw_args.sentinel) |some|
- (try resolveInstConst(mod, scope, some)).val
- else
- null;
-
- const elem_type = try resolveType(mod, scope, inst.positionals.child_type);
-
- const ty = try mod.ptrType(
- scope,
- inst.base.src,
- elem_type,
- sentinel,
- @"align",
- bit_offset,
- host_size,
- inst.kw_args.mutable,
- inst.kw_args.@"allowzero",
- inst.kw_args.@"volatile",
- inst.kw_args.size,
- );
- return mod.constType(scope, inst.base.src, ty);
-}
diff --git a/test/compile_errors.zig b/test/compile_errors.zig
@@ -1027,9 +1027,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
\\}
\\fn foo() void {}
, &[_][]const u8{
- "tmp.zig:3:21: error: async call in nosuspend scope",
"tmp.zig:4:9: error: suspend in nosuspend scope",
- "tmp.zig:5:9: error: resume in nosuspend scope",
});
cases.add("atomicrmw with bool op not .Xchg",
diff --git a/test/stage1/behavior/async_fn.zig b/test/stage1/behavior/async_fn.zig
@@ -1,5 +1,5 @@
const std = @import("std");
-const builtin = @import("builtin");
+const builtin = std.builtin;
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectEqualStrings = std.testing.expectEqualStrings;
@@ -1545,6 +1545,68 @@ test "nosuspend on function calls" {
expectEqual(@as(i32, 42), (try nosuspend S1.d()).b);
}
+test "nosuspend on async function calls" {
+ const S0 = struct {
+ b: i32 = 42,
+ };
+ const S1 = struct {
+ fn c() S0 {
+ return S0{};
+ }
+ fn d() !S0 {
+ return S0{};
+ }
+ };
+ var frame_c = nosuspend async S1.c();
+ expectEqual(@as(i32, 42), (await frame_c).b);
+ var frame_d = nosuspend async S1.d();
+ expectEqual(@as(i32, 42), (try await frame_d).b);
+}
+
+// test "resume nosuspend async function calls" {
+// const S0 = struct {
+// b: i32 = 42,
+// };
+// const S1 = struct {
+// fn c() S0 {
+// suspend;
+// return S0{};
+// }
+// fn d() !S0 {
+// suspend;
+// return S0{};
+// }
+// };
+// var frame_c = nosuspend async S1.c();
+// resume frame_c;
+// expectEqual(@as(i32, 42), (await frame_c).b);
+// var frame_d = nosuspend async S1.d();
+// resume frame_d;
+// expectEqual(@as(i32, 42), (try await frame_d).b);
+// }
+
+test "nosuspend resume async function calls" {
+ const S0 = struct {
+ b: i32 = 42,
+ };
+ const S1 = struct {
+ fn c() S0 {
+ suspend;
+ return S0{};
+ }
+ fn d() !S0 {
+ suspend;
+ return S0{};
+ }
+ };
+ var frame_c = async S1.c();
+ nosuspend resume frame_c;
+ expectEqual(@as(i32, 42), (await frame_c).b);
+ var frame_d = async S1.d();
+ nosuspend resume frame_d;
+ expectEqual(@as(i32, 42), (try await frame_d).b);
+}
+
test "avoid forcing frame alignment resolution implicit cast to *c_void" {
const S = struct {
var x: ?*c_void = null;
diff --git a/test/stage2/arm.zig b/test/stage2/arm.zig
@@ -378,4 +378,45 @@ pub fn addCases(ctx: *TestContext) !void {
"",
);
}
+
+ {
+ var case = ctx.exe("save function return values in callee preserved register", linux_arm);
+ // Here, it is necessary to save the result of bar() into a
+ // callee preserved register, otherwise it will be overwritten
+ // by the first parameter to baz.
+ case.addCompareOutput(
+ \\export fn _start() noreturn {
+ \\ assert(foo() == 43);
+ \\ exit();
+ \\}
+ \\
+ \\fn foo() u32 {
+ \\ return bar() + baz(42);
+ \\}
+ \\
+ \\fn bar() u32 {
+ \\ return 1;
+ \\}
+ \\
+ \\fn baz(x: u32) u32 {
+ \\ return x;
+ \\}
+ \\
+ \\fn assert(ok: bool) void {
+ \\ if (!ok) unreachable;
+ \\}
+ \\
+ \\fn exit() noreturn {
+ \\ asm volatile ("svc #0"
+ \\ :
+ \\ : [number] "{r7}" (1),
+ \\ [arg1] "{r0}" (0)
+ \\ : "memory"
+ \\ );
+ \\ unreachable;
+ \\}
+ ,
+ "",
+ );
+ }
}
diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig
@@ -39,6 +39,21 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
\\fn unused() void {}
, "yo!" ++ std.cstr.line_sep);
+
+ // Comptime return type and calling convention expected.
+ case.addError(
+ \\var x: i32 = 1234;
+ \\export fn main() x {
+ \\ return 0;
+ \\}
+ \\export fn foo() callconv(y) c_int {
+ \\ return 0;
+ \\}
+ \\var y: i32 = 1234;
+ , &.{
+ ":2:18: error: unable to resolve comptime value",
+ ":5:26: error: unable to resolve comptime value",
+ });
}
{
@@ -55,6 +70,42 @@ pub fn addCases(ctx: *TestContext) !void {
}
{
+ var case = ctx.exeFromCompiledC("@intToError", .{});
+
+ case.addCompareOutput(
+ \\pub export fn main() c_int {
+ \\ // comptime checks
+ \\ const a = error.A;
+ \\ const b = error.B;
+ \\ const c = @intToError(2);
+ \\ const d = @intToError(1);
+ \\ if (!(c == b)) unreachable;
+ \\ if (!(a == d)) unreachable;
+ \\ // runtime checks
+ \\ var x = error.A;
+ \\ var y = error.B;
+ \\ var z = @intToError(2);
+ \\ var f = @intToError(1);
+ \\ if (!(y == z)) unreachable;
+ \\ if (!(x == f)) unreachable;
+ \\ return 0;
+ \\}
+ , "");
+ case.addError(
+ \\pub export fn main() c_int {
+ \\ const c = @intToError(0);
+ \\ return 0;
+ \\}
+ , &.{":2:27: error: integer value 0 represents no error"});
+ case.addError(
+ \\pub export fn main() c_int {
+ \\ const c = @intToError(3);
+ \\ return 0;
+ \\}
+ , &.{":2:27: error: integer value 3 represents no error"});
+ }
+
+ {
var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64);
// Exit with 0
@@ -243,6 +294,134 @@ pub fn addCases(ctx: *TestContext) !void {
\\ return a - 4;
\\}
, "");
+
+ // Switch expression missing else case.
+ case.addError(
+ \\export fn main() c_int {
+ \\ var cond: c_int = 0;
+ \\ const a: c_int = switch (cond) {
+ \\ 1 => 1,
+ \\ 2 => 2,
+ \\ 3 => 3,
+ \\ 4 => 4,
+ \\ };
+ \\ return a - 4;
+ \\}
+ , &.{":3:22: error: switch must handle all possibilities"});
+
+ // Switch expression, has an unreachable prong.
+ case.addCompareOutput(
+ \\export fn main() c_int {
+ \\ var cond: c_int = 0;
+ \\ const a: c_int = switch (cond) {
+ \\ 1 => 1,
+ \\ 2 => 2,
+ \\ 99...300, 12 => 3,
+ \\ 0 => 4,
+ \\ 13 => unreachable,
+ \\ else => 5,
+ \\ };
+ \\ return a - 4;
+ \\}
+ , "");
+
+ // Switch expression, has an unreachable prong and prongs write
+ // to result locations.
+ case.addCompareOutput(
+ \\export fn main() c_int {
+ \\ var cond: c_int = 0;
+ \\ var a: c_int = switch (cond) {
+ \\ 1 => 1,
+ \\ 2 => 2,
+ \\ 99...300, 12 => 3,
+ \\ 0 => 4,
+ \\ 13 => unreachable,
+ \\ else => 5,
+ \\ };
+ \\ return a - 4;
+ \\}
+ , "");
+
+ // Integer switch expression has duplicate case value.
+ case.addError(
+ \\export fn main() c_int {
+ \\ var cond: c_int = 0;
+ \\ const a: c_int = switch (cond) {
+ \\ 1 => 1,
+ \\ 2 => 2,
+ \\ 96, 11...13, 97 => 3,
+ \\ 0 => 4,
+ \\ 90, 12 => 100,
+ \\ else => 5,
+ \\ };
+ \\ return a - 4;
+ \\}
+ , &.{
+ ":8:13: error: duplicate switch value",
+ ":6:15: note: previous value here",
+ });
+
+ // Boolean switch expression has duplicate case value.
+ case.addError(
+ \\export fn main() c_int {
+ \\ var a: bool = false;
+ \\ const b: c_int = switch (a) {
+ \\ false => 1,
+ \\ true => 2,
+ \\ false => 3,
+ \\ };
+ \\}
+ , &.{
+ ":6:9: error: duplicate switch value",
+ });
+
+ // Sparse (no range capable) switch expression has duplicate case value.
+ case.addError(
+ \\export fn main() c_int {
+ \\ const A: type = i32;
+ \\ const b: c_int = switch (A) {
+ \\ i32 => 1,
+ \\ bool => 2,
+ \\ f64, i32 => 3,
+ \\ else => 4,
+ \\ };
+ \\}
+ , &.{
+ ":6:14: error: duplicate switch value",
+ ":4:9: note: previous value here",
+ });
+
+ // Ranges not allowed for some kinds of switches.
+ case.addError(
+ \\export fn main() c_int {
+ \\ const A: type = i32;
+ \\ const b: c_int = switch (A) {
+ \\ i32 => 1,
+ \\ bool => 2,
+ \\ f16...f64 => 3,
+ \\ else => 4,
+ \\ };
+ \\}
+ , &.{
+ ":3:30: error: ranges not allowed when switching on type 'type'",
+ ":6:12: note: range here",
+ });
+
+ // Switch expression has unreachable else prong.
+ case.addError(
+ \\export fn main() c_int {
+ \\ var a: u2 = 0;
+ \\ const b: i32 = switch (a) {
+ \\ 0 => 10,
+ \\ 1 => 20,
+ \\ 2 => 30,
+ \\ 3 => 40,
+ \\ else => 50,
+ \\ };
+ \\}
+ , &.{
+ ":8:14: error: unreachable else prong; all cases already handled",
+ });
}
//{
// var case = ctx.exeFromCompiledC("optionals", .{});
@@ -271,6 +450,7 @@ pub fn addCases(ctx: *TestContext) !void {
// \\}
// , "");
//}
+
{
var case = ctx.exeFromCompiledC("errors", .{});
case.addCompareOutput(
diff --git a/test/stage2/test.zig b/test/stage2/test.zig
@@ -355,7 +355,7 @@ pub fn addCases(ctx: *TestContext) !void {
\\ const z = @TypeOf(true, 1);
\\ unreachable;
\\}
- , &[_][]const u8{":2:29: error: incompatible types: 'bool' and 'comptime_int'"});
+ , &[_][]const u8{":2:15: error: incompatible types: 'bool' and 'comptime_int'"});
}
{
@@ -621,6 +621,43 @@ pub fn addCases(ctx: *TestContext) !void {
"hello\nhello\nhello\nhello\n",
);
+ // inline while requires the condition to be comptime known.
+ case.addError(
+ \\export fn _start() noreturn {
+ \\ var i: u32 = 0;
+ \\ inline while (i < 4) : (i += 1) print();
+ \\ assert(i == 4);
+ \\
+ \\ exit();
+ \\}
+ \\
+ \\fn print() void {
+ \\ asm volatile ("syscall"
+ \\ :
+ \\ : [number] "{rax}" (1),
+ \\ [arg1] "{rdi}" (1),
+ \\ [arg2] "{rsi}" (@ptrToInt("hello\n")),
+ \\ [arg3] "{rdx}" (6)
+ \\ : "rcx", "r11", "memory"
+ \\ );
+ \\ return;
+ \\}
+ \\
+ \\pub fn assert(ok: bool) void {
+ \\ if (!ok) unreachable; // assertion failure
+ \\}
+ \\
+ \\fn exit() noreturn {
+ \\ asm volatile ("syscall"
+ \\ :
+ \\ : [number] "{rax}" (231),
+ \\ [arg1] "{rdi}" (0)
+ \\ : "rcx", "r11", "memory"
+ \\ );
+ \\ unreachable;
+ \\}
+ , &[_][]const u8{":3:21: error: unable to resolve comptime value"});
+
// Labeled blocks (no conditional branch)
case.addCompareOutput(
\\export fn _start() noreturn {
@@ -1070,7 +1107,7 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
\\fn x() void {}
, &[_][]const u8{
- ":11:8: error: found compile log statement",
+ ":9:5: error: found compile log statement",
":4:5: note: also here",
});
}
@@ -1294,10 +1331,9 @@ pub fn addCases(ctx: *TestContext) !void {
,
"",
);
- // TODO this should be :8:21 not :8:19. we need to improve source locations
- // to be relative to the containing Decl so that they can survive when the byte
- // offset of a previous Decl changes. Here the change from 7 to 999 introduces
- // +2 to the byte offset and makes the error location wrong by 2 bytes.
+ // This additionally tests that the compile error reports the correct source location.
+ // Without storing source locations relative to the owner decl, the compile error
+ // here would be off by 2 bytes (from the "7" -> "999").
case.addError(
\\export fn _start() noreturn {
\\ const y = fibonacci(999);
@@ -1318,7 +1354,7 @@ pub fn addCases(ctx: *TestContext) !void {
\\ );
\\ unreachable;
\\}
- , &[_][]const u8{":8:19: error: evaluation exceeded 1000 backwards branches"});
+ , &[_][]const u8{":8:21: error: evaluation exceeded 1000 backwards branches"});
}
{
var case = ctx.exe("orelse at comptime", linux_x64);
@@ -1442,6 +1478,7 @@ pub fn addCases(ctx: *TestContext) !void {
,
"",
);
+
case.addCompareOutput(
\\export fn _start() noreturn {
\\ const i: anyerror!u64 = error.B;
@@ -1464,6 +1501,7 @@ pub fn addCases(ctx: *TestContext) !void {
,
"",
);
+
case.addCompareOutput(
\\export fn _start() noreturn {
\\ const a: anyerror!comptime_int = 42;
@@ -1485,11 +1523,12 @@ pub fn addCases(ctx: *TestContext) !void {
\\ unreachable;
\\}
, "");
+
case.addCompareOutput(
\\export fn _start() noreturn {
- \\const a: anyerror!u32 = error.B;
- \\_ = &(a catch |err| assert(err == error.B));
- \\exit();
+ \\ const a: anyerror!u32 = error.B;
+ \\ _ = &(a catch |err| assert(err == error.B));
+ \\ exit();
\\}
\\fn assert(b: bool) void {
\\ if (!b) unreachable;
@@ -1504,6 +1543,7 @@ pub fn addCases(ctx: *TestContext) !void {
\\ unreachable;
\\}
, "");
+
case.addCompareOutput(
\\export fn _start() noreturn {
\\ const a: anyerror!u32 = error.Bar;