diff --git a/CMakeLists.txt b/CMakeLists.txt index d83dfa3efb..6d3d564648 100644 --- 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/lib/std/enums.zig b/lib/std/enums.zig index bddda38c9f..a868bdeb26 100644 --- 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/zig.zig b/lib/std/zig.zig index 197d7c2c59..cff07a2bd2 100644 --- 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 index 0b0459ec88..a0e7754896 100644 --- 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 index 805ee95571..029d8ede50 100644 --- 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 index 640f25829a..7add8383f3 100644 --- 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 index 78d4e63bfe..e1fa799954 100644 --- 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, - - /// When this is returned, index will be the position of the character. - InvalidCharacter, + InvalidStringLiteral, }; -/// 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 { +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, +}; + +/// 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] == '"'); - - var list = std.ArrayList(u8).init(allocator); - errdefer list.deinit(); - const slice = bytes[1..]; - try list.ensureCapacity(slice.len - 1); + + const prev_len = buf.items.len; + try buf.ensureCapacity(prev_len + slice.len - 1); + errdefer buf.shrinkRetainingCapacity(prev_len); + + 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 new file mode 100644 index 0000000000..2c3c1871ac --- /dev/null +++ b/src/AstGen.zig @@ -0,0 +1,4275 @@ +//! 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, tree.containerDecl(node)), + .container_decl_two, .container_decl_two_trailing => { + var buffer: [2]ast.Node.Index = undefined; + return containerDecl(gz, scope, rl, tree.containerDeclTwo(&buffer, node)); + }, + .container_decl_arg, + .container_decl_arg_trailing, + => return containerDecl(gz, scope, rl, tree.containerDeclArg(node)), + + .tagged_union, + .tagged_union_trailing, + => return containerDecl(gz, scope, rl, tree.taggedUnion(node)), + .tagged_union_two, .tagged_union_two_trailing => { + var buffer: [2]ast.Node.Index = undefined; + return containerDecl(gz, scope, rl, tree.taggedUnionTwo(&buffer, node)); + }, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + => return containerDecl(gz, scope, rl, 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, + .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( + 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, + => 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, + => 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, + container_decl: ast.full.ContainerDecl, +) InnerError!zir.Inst.Ref { + return gz.astgen.mod.failTok(scope, container_decl.ast.main_token, "TODO implement container decls", .{}); +} + +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 mod = gz.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 string_bytes = &gz.astgen.string_bytes; + const str_index = @intCast(u32, string_bytes.items.len); + try mod.appendIdentStr(scope, field_ident, string_bytes); + try string_bytes.append(mod.gpa, 0); + 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 mod = gz.astgen.mod; + const string_bytes = &gz.astgen.string_bytes; + const str_index = @intCast(u32, string_bytes.items.len); + try mod.appendIdentStr(scope, ident_token, string_bytes); + try string_bytes.append(mod.gpa, 0); + 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 = std.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 = std.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 index d80beb477a..b086e513b9 100644 --- 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 index 585925c4a0..8037785232 100644 --- 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 fullyQualifiedNameHash(self: Decl) Scope.NameHash { - return self.container.fullyQualifiedNameHash(mem.spanZ(self.name)); + pub fn srcToken(decl: Decl) u32 { + const tree = &decl.container.file_scope.tree; + return tree.firstToken(decl.srcNode()); } - pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue { - const tvm = self.typedValueManaged() orelse return error.AnalysisFail; + pub fn srcByteOffset(decl: Decl) u32 { + const tree = &decl.container.file_scope.tree; + return tree.tokens.items(.start)[decl.srcToken()]; + } + + pub fn fullyQualifiedNameHash(decl: Decl) Scope.NameHash { + return decl.container.fullyQualifiedNameHash(mem.spanZ(decl.name)); + } + + 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,32 @@ 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, +}; + +/// 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 +364,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 +390,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 +422,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 +522,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 +534,7 @@ pub const Scope = struct { .local_val => unreachable, .local_ptr => unreachable, .block => unreachable, - .gen_suspend => unreachable, - .gen_nosuspend => unreachable, + .decl_ref => unreachable, } } @@ -499,40 +545,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 +571,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 +587,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 +629,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 +685,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 +718,232 @@ 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; + } }; - /// 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 +955,456 @@ 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 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 +1413,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 +1428,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 +1463,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 +1488,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, + /// 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, + }; + } + + pub fn byteOffset(src_loc: SrcLoc) !u32 { + switch (src_loc.lazy) { + .unneeded => unreachable, + + .byte_abs => |byte_index| return byte_index, + + .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(self: *Module) void { - const gpa = self.gpa; +pub fn deinit(mod: *Module) void { + const gpa = mod.gpa; - self.compile_log_text.deinit(gpa); + mod.compile_log_text.deinit(gpa); - self.zig_cache_artifact_directory.handle.close(); + mod.zig_cache_artifact_directory.handle.close(); - self.deletion_set.deinit(gpa); + mod.deletion_set.deinit(gpa); - for (self.decl_table.items()) |entry| { - entry.value.destroy(self); + for (mod.decl_table.items()) |entry| { + entry.value.destroy(mod); } - self.decl_table.deinit(gpa); + mod.decl_table.deinit(gpa); - for (self.failed_decls.items()) |entry| { + for (mod.failed_decls.items()) |entry| { entry.value.destroy(gpa); } - self.failed_decls.deinit(gpa); + mod.failed_decls.deinit(gpa); - for (self.emit_h_failed_decls.items()) |entry| { + for (mod.emit_h_failed_decls.items()) |entry| { entry.value.destroy(gpa); } - self.emit_h_failed_decls.deinit(gpa); + mod.emit_h_failed_decls.deinit(gpa); - for (self.failed_files.items()) |entry| { + for (mod.failed_files.items()) |entry| { entry.value.destroy(gpa); } - self.failed_files.deinit(gpa); + mod.failed_files.deinit(gpa); - for (self.failed_exports.items()) |entry| { + for (mod.failed_exports.items()) |entry| { entry.value.destroy(gpa); } - self.failed_exports.deinit(gpa); + mod.failed_exports.deinit(gpa); - self.compile_log_decls.deinit(gpa); + mod.compile_log_decls.deinit(gpa); - for (self.decl_exports.items()) |entry| { + for (mod.decl_exports.items()) |entry| { const export_list = entry.value; gpa.free(export_list); } - self.decl_exports.deinit(gpa); + mod.decl_exports.deinit(gpa); - for (self.export_owners.items()) |entry| { + for (mod.export_owners.items()) |entry| { freeExportList(gpa, entry.value); } - self.export_owners.deinit(gpa); + mod.export_owners.deinit(gpa); - self.symbol_exports.deinit(gpa); - self.root_scope.destroy(gpa); + mod.symbol_exports.deinit(gpa); + mod.root_scope.destroy(gpa); - var it = self.global_error_set.iterator(); + var it = mod.global_error_set.iterator(); while (it.next()) |entry| { gpa.free(entry.key); } - self.global_error_set.deinit(gpa); + mod.global_error_set.deinit(gpa); - for (self.import_table.items()) |entry| { + 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 +2341,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, + + 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); + + const block_expr = node_datas[decl_node].lhs; + _ = try AstGen.comptimeExpr(&gen_scope, &gen_scope.base, .none, block_expr); + + 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 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 inst_table = Scope.Block.InstTable.init(mod.gpa); - defer inst_table.deinit(); - - var branch_quota: u32 = default_eval_branch_quota; + 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 +2408,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 +2415,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 +2439,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 +2464,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 +2533,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 +2545,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; + 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 = 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, + 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 +2652,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 +2741,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 +2783,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 +2802,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 +2811,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 +2841,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 +2849,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 +2987,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 +3026,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 +3309,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 +3324,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 +3354,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 +3374,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 +3385,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 +3398,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 +3421,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 +3433,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 +3442,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 +3451,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 +3486,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 +3631,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 +3752,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 +3765,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 +3863,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 +3899,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 +3916,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); + return mod.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); -} - -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 +3958,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 +3966,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 +4121,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 +4154,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 +4187,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 +4199,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 +4215,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 +4230,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 +4243,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 +4267,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 +4299,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 +4337,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 getTarget(mod: Module) Target { + return mod.comp.bin_file.options.target; } -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 optimizeMode(mod: Module) std.builtin.Mode { + return mod.comp.bin_file.options.optimize_mode; } -pub fn getTarget(self: Module) Target { - return self.comp.bin_file.options.target; -} - -pub fn optimizeMode(self: Module) std.builtin.Mode { - return self.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.*, - }); - } -} - -pub fn namedFieldPtr( +/// 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, - 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 }, - ); - } - }, - .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 => {}, - } - }, - .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}), - } - }, - else => {}, + 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); } - return mod.fail(scope, src, "type '{}' does not support field access", .{elem_ty}); } -pub fn elemPtr( +/// Appends the result to `buf`. +pub fn parseStrLit( 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}); + 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]}, + ); + }, + .expected_hex_digits => |bad_index| { + return mod.failOff( + scope, + token_starts[token] + offset + @intCast(u32, bad_index), + "expected hex digits after '\\x'", + .{}, + ); + }, + .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'", + .{}, + ); + }, } - - 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 index 5daacbbf08..bd511a5921 100644 --- 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 new file mode 100644 index 0000000000..876016df8c --- /dev/null +++ b/src/Sema.zig @@ -0,0 +1,4897 @@ +//! 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), + + // 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; + }, + + // 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 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 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 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}), + } + }, + else => {}, + } + return sema.mod.fail(&block.base, src, "type '{}' does not support field access", .{elem_ty}); +} + +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 deleted file mode 100644 index 903c385fef..0000000000 --- a/src/astgen.zig +++ /dev/null @@ -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 index bc3480ca01..d27122efc9 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -17,6 +17,7 @@ const DW = std.dwarf; const leb128 = std.leb; const log = std.log.scoped(.codegen); const build_options = @import("build_options"); +const LazySrcLoc = Module.LazySrcLoc; /// The codegen-related data that is stored in `ir.Inst.Block` instructions. pub const BlockData = struct { @@ -498,7 +499,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { 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, }; @@ -791,8 +792,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 +801,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 @@ -897,6 +898,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).?), @@ -978,7 +981,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { /// 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 { + fn copyToTmpRegister(self: *Self, src: LazySrcLoc, 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. @@ -1457,7 +1460,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 +1623,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 +1709,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); @@ -1807,7 +1810,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { 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 @@ -2234,7 +2237,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) { @@ -2324,8 +2327,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; } @@ -2562,6 +2569,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; @@ -2571,7 +2586,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); @@ -2628,7 +2643,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); @@ -2692,7 +2707,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); @@ -2705,7 +2720,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); @@ -2767,7 +2782,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}); } @@ -2799,7 +2814,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}); } @@ -2829,7 +2844,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}); } @@ -2859,7 +2874,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}); } @@ -2909,7 +2924,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), @@ -2921,7 +2936,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, @@ -3160,7 +3175,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, @@ -3703,7 +3718,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(); @@ -3778,7 +3793,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); @@ -3992,13 +4007,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; } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index af8d2d272d..6e68a43607 100644 --- 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 index 7233dbdd07..cd601debda 100644 --- 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", @@ -143,79 +145,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_pathZ: [:0]const u8, - 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, - - 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; @@ -250,34 +215,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); } @@ -289,7 +241,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); @@ -310,13 +262,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()) { @@ -328,44 +277,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 { +pub const DeclGen = struct { + object: *Object, + module: *Module, + decl: *Module.Decl, + err_msg: ?*Module.ErrorMsg, + + 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; - const src = decl.src(); - - self.src_loc = decl.srcLoc(); 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! @@ -374,20 +347,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(); - try self.genBody(func.body); + 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 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); } } - fn genBody(self: *LLVMIRModule, body: ir.Body) error{ OutOfMemory, CodegenFail }!void { + /// 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: *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).?), @@ -425,13 +671,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 @@ -441,12 +687,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); @@ -471,27 +717,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) { @@ -506,21 +757,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); @@ -531,7 +782,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, @@ -540,7 +791,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 @@ -553,25 +804,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); @@ -586,8 +837,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); @@ -597,20 +848,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(), @@ -623,15 +874,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(), @@ -644,12 +895,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, "") @@ -657,12 +908,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, "") @@ -670,44 +921,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); @@ -729,242 +980,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 index d5f68eca81..827f6c366a 100644 --- 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 index 95896d523b..c09af2507e 100644 --- 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 @@ -139,7 +144,6 @@ pub const Inst = struct { .retvoid, .unreach, .breakpoint, - .dbg_stmt, => NoOp, .ref, @@ -152,6 +156,8 @@ pub const Inst = struct { .is_null_ptr, .is_err, .is_err_ptr, + .int_to_error, + .error_to_int, .ptrtoint, .floatcast, .intcast, @@ -199,6 +205,7 @@ pub const Inst = struct { .loop => Loop, .varptr => VarPtr, .switchbr => SwitchBr, + .dbg_stmt => DbgStmt, }; } @@ -360,7 +367,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, @@ -584,8 +592,512 @@ 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); + } + }, + + // 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"); + } + }, + + // 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 index 440f52c49f..eed2d0b213 100644 --- 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 index 8b2b13eb71..33c37f8efe 100644 --- 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 index 314e443f3a..3d074f4043 100644 --- 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 index 517fabaf3e..761a4a8e1d 100644 --- 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 index 2996125585..9e7e0541b1 100644 --- 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; @@ -2461,15 +2458,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 +3275,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/translate_c.zig b/src/translate_c.zig index 3bc9cb5457..9ad90a5320 100644 --- 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 index 9599a165fd..333854296e 100644 --- a/src/type.zig +++ b/src/type.zig @@ -92,11 +92,7 @@ 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, .var_args_param => unreachable, // can be any type } @@ -173,6 +169,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 +310,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 info_a = a.ptrInfo().data; + const info_b = b.ptrInfo().data; + if (!info_a.pointee_type.eql(info_b.pointee_type)) + return false; + 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; + + 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; } - const is_slice_a = isSlice(a); - const is_slice_b = isSlice(b); - if (is_slice_a != is_slice_b) - return false; - const ptr_size_a = ptrSize(a); - const ptr_size_b = ptrSize(b); - if (ptr_size_a != ptr_size_b) - return false; - - std.debug.panic("TODO implement more pointer Type equality comparison: {} and {}", .{ - a, b, - }); + return true; }, .Int => { // Detect that e.g. u64 != usize, even if the bits match on a particular target. @@ -399,7 +527,6 @@ pub const Type = extern union { .const_slice_u8, .enum_literal, .anyerror_void_error_union, - .@"anyframe", .inferred_alloc_const, .inferred_alloc_mut, .var_args_param, @@ -420,7 +547,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 +606,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), } } @@ -551,7 +674,6 @@ pub const Type = extern union { // TODO this should print the structs name .empty_struct => return out_stream.writeAll("struct {}"), - .@"anyframe" => return out_stream.writeAll("anyframe"), .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 +701,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 +831,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 +841,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,8 +935,6 @@ 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, @@ -839,10 +950,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, @@ -863,7 +970,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 +1046,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,10 +1098,6 @@ pub const Type = extern union { @panic("TODO abiAlignment error union"); }, - .@"enum" => self.cast(Payload.Enum).?.abiAlignment(target), - .@"struct" => @panic("TODO"), - .@"union" => @panic("TODO"), - .c_void, .void, .type, @@ -1038,7 +1165,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 +1246,6 @@ pub const Type = extern union { } @panic("TODO abiSize error union"); }, - - .@"enum" => @panic("TODO"), - .@"struct" => @panic("TODO"), - .@"union" => @panic("TODO"), }; } @@ -1186,15 +1309,10 @@ 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", .@"opaque", .var_args_param, => false, @@ -1264,15 +1382,10 @@ 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", .@"opaque", .var_args_param, => unreachable, @@ -1361,17 +1474,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => false, @@ -1442,17 +1550,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => false, @@ -1532,17 +1635,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => false, @@ -1617,17 +1715,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => false, @@ -1744,17 +1837,12 @@ 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, .empty_struct => unreachable, .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, - .@"enum" => unreachable, - .@"struct" => unreachable, - .@"union" => unreachable, .@"opaque" => unreachable, .var_args_param => unreachable, @@ -1897,17 +1985,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => unreachable, @@ -1972,17 +2055,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => unreachable, @@ -2062,17 +2140,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => false, @@ -2148,17 +2221,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => false, @@ -2220,17 +2288,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => unreachable, @@ -2320,17 +2383,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => false, @@ -2441,17 +2499,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => unreachable, @@ -2528,17 +2581,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => unreachable, @@ -2614,17 +2662,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => unreachable, @@ -2700,17 +2743,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => unreachable, @@ -2783,17 +2821,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => unreachable, @@ -2866,17 +2899,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => unreachable, @@ -2949,17 +2977,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => false, @@ -3016,8 +3039,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,10 +3046,6 @@ 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"), - .empty_struct => return Value.initTag(.empty_struct_value), .void => return Value.initTag(.void_value), .noreturn => return Value.initTag(.unreachable_value), @@ -3128,17 +3145,12 @@ 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, .inferred_alloc_const, .inferred_alloc_mut, - .@"enum", - .@"struct", - .@"union", .@"opaque", .var_args_param, => return false, @@ -3220,8 +3232,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, @@ -3234,10 +3244,7 @@ pub const Type = extern union { => unreachable, .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 +3303,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,7 +3357,6 @@ 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. @@ -3379,13 +3389,9 @@ 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", pub const last_no_payload_tag = Tag.inferred_alloc_const; @@ -3435,7 +3441,6 @@ 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, @@ -3457,25 +3462,22 @@ 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, + .empty_struct => Payload.ContainerScope, }; } @@ -3550,6 +3552,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 +3607,8 @@ 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"); }; }; diff --git a/src/type/Enum.zig b/src/type/Enum.zig deleted file mode 100644 index 4dfd5f6e44..0000000000 --- a/src/type/Enum.zig +++ /dev/null @@ -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 deleted file mode 100644 index 24e3a0dcad..0000000000 --- a/src/type/Struct.zig +++ /dev/null @@ -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 deleted file mode 100644 index 5c7acf7d36..0000000000 --- a/src/type/Union.zig +++ /dev/null @@ -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 index 3fd2889fc8..269cceafa4 100644 --- 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,18 @@ 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, + + empty_struct_value, + empty_array, // See last_no_payload_tag below. // After this, the tag requires a payload. ty, @@ -100,14 +102,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 +121,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 +155,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, @@ -193,7 +195,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 +276,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 +310,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, @@ -400,7 +402,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 +430,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,7 +464,6 @@ 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"), // TODO this should print `NAME{}` .empty_struct_value => return out_stream.writeAll("struct {}{}"), @@ -510,15 +512,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 +550,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 +584,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 +596,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, @@ -654,6 +644,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 +678,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,7 +695,6 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, - .error_set, .error_union, .@"error", .empty_struct_value, @@ -741,6 +731,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 +765,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,7 +782,6 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, - .error_set, .@"error", .error_union, .empty_struct_value, @@ -828,6 +818,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 +852,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,7 +869,6 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, - .error_set, .@"error", .error_union, .empty_struct_value, @@ -942,6 +932,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 +966,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,7 +984,6 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, - .error_set, .@"error", .error_union, .empty_struct_value, @@ -1034,6 +1024,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 +1058,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,7 +1075,6 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, - .error_set, .@"error", .error_union, .empty_struct_value, @@ -1191,6 +1181,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 +1215,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,7 +1235,6 @@ pub const Value = extern union { .void_value, .unreachable_value, .enum_literal, - .error_set, .@"error", .error_union, .empty_struct_value, @@ -1275,6 +1265,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 +1299,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,7 +1313,6 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, - .error_set, .@"error", .error_union, .empty_struct_value, @@ -1427,6 +1417,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 +1451,12 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, - .anyframe_type, .ty, => { - // 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 +1571,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 +1605,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,7 +1628,6 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, - .error_set, .@"error", .error_union, .empty_struct_value, @@ -1672,6 +1658,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 +1692,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,7 +1715,6 @@ pub const Value = extern union { .void_value, .unreachable_value, .enum_literal, - .error_set, .@"error", .error_union, .empty_struct_value, @@ -1776,6 +1762,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 +1796,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,7 +1819,6 @@ pub const Value = extern union { .float_128, .void_value, .enum_literal, - .error_set, .@"error", .error_union, .empty_struct_value, @@ -1858,6 +1844,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 +1878,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,7 +1902,6 @@ pub const Value = extern union { .float_128, .void_value, .enum_literal, - .error_set, .empty_struct_value, => null, @@ -1960,6 +1946,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 +1980,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, @@ -2137,18 +2123,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 index 0b9df55624..8f9d43a8ae 100644 --- 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,636 @@ 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; -/// 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. +/// 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 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, /// 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, /// Returns whether the instruction is one of the control flow "noreturn" types. /// Function calls do not count. @@ -540,23 +653,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 +683,22 @@ pub const Inst = struct { .cmp_neq, .coerce_result_ptr, .@"const", - .dbg_stmt, + .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 +716,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 +746,1450 @@ 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, => 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; + /// 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, + /// `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), + }, + + .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), + }, + }); + }; + + /// 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 }; + } + }, + /// 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 }; + } + }, + 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); + } + + 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, + }, + + // 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); } } - 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, - - positionals: struct {}, - kw_args: struct {}, - }; - - pub const UnOp = struct { - base: Inst, - - positionals: struct { - operand: *Inst, - }, - kw_args: struct {}, - }; - - pub const BinOp = struct { - base: Inst, - - positionals: struct { - lhs: *Inst, - rhs: *Inst, - }, - kw_args: struct {}, - }; - - pub const Arg = struct { - pub const base_tag = Tag.arg; - base: Inst, - - positionals: struct { - /// This exists to be passed to the arg TZIR instruction, which - /// needs it for debug info. - name: []const u8, - }, - kw_args: struct {}, - }; - - pub const Block = struct { - pub const base_tag = Tag.block; - base: Inst, - - positionals: struct { - body: Body, - }, - kw_args: struct {}, - }; - - pub const Break = struct { - pub const base_tag = Tag.@"break"; - base: Inst, - - positionals: struct { - block: *Block, - operand: *Inst, - }, - kw_args: struct {}, - }; - - pub const BreakVoid = struct { - pub const base_tag = Tag.break_void; - base: Inst, - - positionals: struct { - block: *Block, - }, - kw_args: struct {}, - }; - - // TODO break this into multiple call instructions to avoid paying the cost - // of the calling convention field most of the time. - 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 {}, - }; - - pub const DeclRef = struct { - pub const base_tag = Tag.decl_ref; - base: Inst, - - positionals: struct { - decl: *IrModule.Decl, - }, - kw_args: struct {}, - }; - - pub const DeclRefStr = struct { - pub const base_tag = Tag.decl_ref_str; - base: Inst, - - positionals: struct { - name: *Inst, - }, - kw_args: struct {}, - }; - - pub const DeclVal = struct { - pub const base_tag = Tag.decl_val; - base: Inst, - - positionals: struct { - decl: *IrModule.Decl, - }, - kw_args: struct {}, - }; - - pub const CompileLog = struct { - pub const base_tag = Tag.compile_log; - base: Inst, - - positionals: struct { - to_log: []*Inst, - }, - kw_args: struct {}, - }; - - pub const Const = struct { - pub const base_tag = Tag.@"const"; - base: Inst, - - positionals: struct { - typed_value: TypedValue, - }, - kw_args: struct {}, - }; - - pub const Str = struct { - pub const base_tag = Tag.str; - base: Inst, - - positionals: struct { - bytes: []const u8, - }, - kw_args: struct {}, - }; - - pub const Int = struct { - pub const base_tag = Tag.int; - base: Inst, - - positionals: struct { - int: BigIntConst, - }, - kw_args: struct {}, - }; - - pub const Loop = struct { - pub const base_tag = Tag.loop; - base: Inst, - - positionals: struct { - body: Body, - }, - kw_args: struct {}, - }; - - pub const Field = struct { - base: Inst, - - positionals: struct { - object: *Inst, - field_name: []const u8, - }, - kw_args: struct {}, - }; - - pub const FieldNamed = struct { - base: Inst, - - positionals: struct { - object: *Inst, - field_name: *Inst, - }, - 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 { - 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 Fn = struct { - pub const base_tag = Tag.@"fn"; - base: Inst, - - positionals: struct { - fn_type: *Inst, - body: Body, - }, - kw_args: struct {}, - }; - - pub const FnType = struct { - pub const base_tag = Tag.fn_type; - base: Inst, - - positionals: struct { - param_types: []*Inst, - return_type: *Inst, - }, - kw_args: struct {}, + asm_source: Ref, + return_type: Ref, + /// May be omitted. + output: Ref, + args_len: u32, + clobbers_len: u32, }; + /// 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 { - pub const base_tag = Tag.fn_type_cc; - base: Inst, - - positionals: struct { - param_types: []*Inst, - return_type: *Inst, - cc: *Inst, - }, - kw_args: struct {}, + return_type: Ref, + cc: Ref, + param_types_len: u32, }; - pub const IntType = struct { - pub const base_tag = Tag.int_type; - base: Inst, - - positionals: struct { - signed: *Inst, - bits: *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 Export = struct { - pub const base_tag = Tag.@"export"; - base: Inst, - - positionals: struct { - symbol_name: *Inst, - decl_name: []const u8, - }, - kw_args: struct {}, + /// 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, }; - pub const ParamType = struct { - pub const base_tag = Tag.param_type; - base: Inst, - - positionals: struct { - func: *Inst, - arg_index: usize, - }, - 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, }; - 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 {}, + /// Stored inside extra, with trailing arguments according to `args_len`. + /// Each argument is a `Ref`. + pub const Call = struct { + callee: Ref, + args_len: u32, }; + /// 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 { - pub const base_tag = Tag.condbr; - base: Inst, - - positionals: struct { - condition: *Inst, - then_body: Body, - else_body: Body, - }, - kw_args: struct {}, + condition: Ref, + then_body_len: u32, + else_body_len: u32, }; + /// 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 { - 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, - }, + elem_type: Ref, }; 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 {}, + sentinel: Ref, + elem_type: Ref, }; - pub const EnumLiteral = struct { - pub const base_tag = Tag.enum_literal; - base: Inst, - - positionals: struct { - name: []const u8, - }, - kw_args: struct {}, + pub const SliceStart = struct { + lhs: Ref, + start: Ref, }; - pub const ErrorSet = struct { - pub const base_tag = Tag.error_set; - base: Inst, - - positionals: struct { - fields: [][]const u8, - }, - kw_args: struct {}, + pub const SliceEnd = struct { + lhs: Ref, + start: Ref, + end: Ref, }; - pub const ErrorValue = struct { - pub const base_tag = Tag.error_value; - base: Inst, - - positionals: struct { - name: []const u8, - }, - kw_args: struct {}, + pub const SliceSentinel = struct { + lhs: Ref, + start: Ref, + end: Ref, + sentinel: Ref, }; - pub const Slice = struct { - pub const base_tag = Tag.slice; - base: Inst, - - positionals: struct { - array_ptr: *Inst, - start: *Inst, - }, - kw_args: struct { - end: ?*Inst = null, - sentinel: ?*Inst = null, - }, + /// The meaning of these operands depends on the corresponding `Tag`. + pub const Bin = struct { + lhs: Ref, + rhs: Ref, }; - pub const TypeOfPeer = struct { - pub const base_tag = .typeof_peer; - base: Inst, - positionals: struct { - items: []*Inst, - }, - kw_args: struct {}, + /// 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, }; - pub const ContainerFieldNamed = struct { - pub const base_tag = Tag.container_field_named; - base: Inst, - - positionals: struct { - bytes: []const u8, - }, - 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 ContainerFieldTyped = struct { - pub const base_tag = Tag.container_field_typed; - base: Inst, - - positionals: struct { - bytes: []const u8, - ty: *Inst, - }, - kw_args: struct {}, + pub const Field = struct { + lhs: Ref, + /// Offset into `string_bytes`. + field_name_start: u32, }; - pub const ContainerField = struct { - pub const base_tag = Tag.container_field; - base: Inst, - - positionals: struct { - bytes: []const u8, - }, - kw_args: struct { - ty: ?*Inst = null, - init: ?*Inst = null, - alignment: ?*Inst = null, - is_comptime: bool = false, - }, + pub const FieldNamed = struct { + lhs: Ref, + field_name: Ref, }; - pub const EnumType = struct { - pub const base_tag = Tag.enum_type; - base: Inst, - - positionals: struct { - fields: []*Inst, - }, - kw_args: struct { - tag_type: ?*Inst = null, - layout: std.builtin.TypeInfo.ContainerLayout = .Auto, - }, - }; - - pub const StructType = struct { - pub const base_tag = Tag.struct_type; - base: Inst, - - positionals: struct { - fields: []*Inst, - }, - kw_args: struct { - layout: std.builtin.TypeInfo.ContainerLayout = .Auto, - }, - }; - - pub const UnionType = struct { - pub const base_tag = Tag.union_type; - base: Inst, - - positionals: struct { - fields: []*Inst, - }, - kw_args: struct { - init_inst: ?*Inst = null, - has_enum_token: bool, - layout: std.builtin.TypeInfo.ContainerLayout = .Auto, - }, - }; - - 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 {}, - - pub const SpecialProng = enum { - none, - @"else", - underscore, - }; - - pub const Case = struct { - item: *Inst, - body: Body, - }; + pub const As = struct { + dest_type: Ref, + operand: Ref, }; }; -pub const ErrorMsg = struct { - byte_offset: usize, - msg: []const u8, -}; - -pub const Body = struct { - instructions: []*Inst, -}; - -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, - - inst: *Inst, - }; - - pub const MetaData = struct { - deaths: ir.Inst.DeathsInt, - addr: usize, - }; - - pub const BodyMetaData = struct { - deaths: []*Inst, - }; - - pub fn deinit(self: *Module, allocator: *Allocator) void { - self.metadata.deinit(); - self.body_metadata.deinit(); - allocator.free(self.decls); - self.arena.deinit(); - self.* = undefined; - } - - /// 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 {}; - } - - const DeclAndIndex = struct { - decl: *Decl, - index: usize, - }; - - /// 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, - }; - } - } - return null; - } - - pub fn findInstDecl(self: Module, inst: *Inst) ?DeclAndIndex { - for (self.decls) |decl, i| { - if (decl.inst == inst) { - return DeclAndIndex{ - .decl = decl, - .index = i, - }; - } - } - return null; - } - - /// 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)); - - for (self.decls) |decl, decl_i| { - try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name }); - } - - 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'); - } - } -}; - -const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize, name: []const u8 }); +pub const SpecialProng = enum { none, @"else", under }; 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, + gpa: *Allocator, + arena: *Allocator, + scope: *Module.Scope, + code: Code, indent: usize, - next_instr_index: usize, + param_count: usize, fn writeInstToStream( 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); - } + 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, + => 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, + => 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, + => try self.writePlNodeBlock(stream, inst), + + .condbr, + .condbr_inline, + => try self.writePlNodeCondBr(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, + .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)"), } - unreachable; // all tags handled } - fn writeInstToStreamGeneric( - self: *Writer, - stream: anytype, - comptime inst_tag: Inst.Tag, - base: *Inst, - ) (@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)); - } - - 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 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(')'); } - 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"); - } - } - - 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 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()); } - 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}); + 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()); + } + + 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)"); + } + + 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)"); + } + + 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}); + } + + 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)"); + } + + 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)"); + } + + 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}); + } + + 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)}); + } + + 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()); + } + + 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()); + } + + 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); + + 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()); + } + + 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()); + } + + 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()); + } + + 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, + }; + }, + }; + + try self.writeInstRef(stream, extra.data.operand); + + 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("}"); + } + + 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; + + 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("}"); } - } 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)}); + } + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + 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, + }; + }, + }; + + try self.writeInstRef(stream, extra.data.operand); + + 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("}"); + } + + 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; + + 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("}"); + } + } + { + 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); + } + + 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("}"); + } + } + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + 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 (operands) |operand, i| { + if (i != 0) try stream.writeAll(", "); + try self.writeInstRef(stream, operand); + } + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + 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()); + } + + 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()); + } + + 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 writeNode( + self: *Writer, + stream: anytype, + inst: Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + 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 writeStrTok( + self: *Writer, + stream: anytype, + inst: Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + 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()); + } + + 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); + } + + 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 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("})"); + } + + 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 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(")"); + } + + 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()); + } + + 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); + } + 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 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)}); + } + + 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 writeInstRef(self: *Writer, stream: anytype, ref: Inst.Ref) !void { + var i: usize = @enumToInt(ref); + + if (i < Inst.Ref.typed_value_map.len) { + return stream.print("@{}", .{ref}); + } + i -= Inst.Ref.typed_value_map.len; + + if (i < self.param_count) { + return stream.print("${d}", .{i}); + } + i -= self.param_count; + + return self.writeInstIndex(stream, @intCast(Inst.Index, i)); + } + + fn writeInstIndex(self: *Writer, stream: anytype, inst: Inst.Index) !void { + return stream.print("%{d}", .{inst}); + } + + 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); + } + + fn writeFlag( + self: *Writer, + stream: anytype, + name: []const u8, + flag: bool, + ) !void { + if (!flag) return; + try stream.writeAll(name); + } + + 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, + }); + } + + 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'); } } }; - -/// 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"); - }, - } -} - -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, - }); - } - - return dtz.dumpBody(body, writer); - } - - 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); - }, - - .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 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"), - - .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"); - } - }, - - .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"); - }, - } - } - } - - 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 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; - } - } -}; - -/// 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); - } - - 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'); - } - - try stderr.print("}} // {s} {s}\n\n", .{ kind, decl_name }); -} diff --git a/src/zir_sema.zig b/src/zir_sema.zig deleted file mode 100644 index 3f540be9cb..0000000000 --- a/src/zir_sema.zig +++ /dev/null @@ -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/stage2/cbe.zig b/test/stage2/cbe.zig index 8a8a8ca224..e54f4b9650 100644 --- 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", + }); } { @@ -54,6 +69,42 @@ pub fn addCases(ctx: *TestContext) !void { , "Hello, world!" ++ std.cstr.line_sep); } + { + 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); @@ -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 index d475f5dff0..4ef172e65d 100644 --- 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;