From 06e785ccf50aa6081b42a6486111dc7cda63c96c Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Fri, 10 Nov 2023 21:21:42 +0100 Subject: [PATCH 01/30] ErrorBuilder improvements ErrorBuilder: - new `unified` option - new `file_name_visibility` option - new `removeUnusedFiles` function - formatting functions have been rewritten from scratch - tests coverage offsets.zig: - new `offsets.multilineLocAtIndex` function (and wrappers) --- src/offsets.zig | 49 +++- tests/ErrorBuilder.zig | 482 ++++++++++++++++++++++++++++++++------ tests/utility/offsets.zig | 17 ++ 3 files changed, 474 insertions(+), 74 deletions(-) diff --git a/src/offsets.zig b/src/offsets.zig index fb12987ed..bad8a9bbd 100644 --- a/src/offsets.zig +++ b/src/offsets.zig @@ -22,7 +22,7 @@ pub const Encoding = enum { pub const Loc = std.zig.Token.Loc; pub fn indexToPosition(text: []const u8, index: usize, encoding: Encoding) types.Position { - const last_line_start = if (std.mem.lastIndexOf(u8, text[0..index], "\n")) |line| line + 1 else 0; + const last_line_start = if (std.mem.lastIndexOfScalar(u8, text[0..index], '\n')) |line| line + 1 else 0; const line_count = std.mem.count(u8, text[0..last_line_start], "\n"); return .{ @@ -379,6 +379,53 @@ pub fn lineSliceAtPosition(text: []const u8, position: types.Position, encoding: return locToSlice(text, lineLocAtPosition(text, position, encoding)); } +/// return the source location +/// that starts `n` lines before the line at which `index` is located +/// and ends `n` lines after the line at which `index` is located. +/// `n == 0` is equivalent to calling `lineLocAtIndex`. +pub fn multilineLocAtIndex(text: []const u8, index: usize, n: usize) Loc { + const start = blk: { + var i: usize = index; + var num_lines: usize = 0; + while (i != 0) : (i -= 1) { + if (text[i - 1] != '\n') continue; + if (num_lines >= n) break :blk i; + num_lines += 1; + } + break :blk 0; + }; + const end = blk: { + var i: usize = index; + var num_lines: usize = 0; + while (i < text.len) : (i += 1) { + if (text[i] != '\n') continue; + if (num_lines >= n) break :blk i; + num_lines += 1; + } + break :blk text.len; + }; + + return .{ + .start = start, + .end = end, + }; +} + +/// see `multilineLocAtIndex` +pub fn multilineSliceAtIndex(text: []const u8, index: usize, n: usize) []const u8 { + return locToSlice(text, multilineLocAtIndex(text, index, n)); +} + +/// see `multilineLocAtIndex` +pub fn multilineLocAtPosition(text: []const u8, position: types.Position, n: usize, encoding: Encoding) Loc { + return lineLocAtIndex(text, positionToIndex(text, position, n, encoding)); +} + +/// see `multilineLocAtIndex` +pub fn multilineSliceAtPosition(text: []const u8, position: types.Position, n: usize, encoding: Encoding) []const u8 { + return locToSlice(text, multilineLocAtPosition(text, position, n, encoding)); +} + pub fn lineLocUntilIndex(text: []const u8, index: usize) Loc { return .{ .start = if (std.mem.lastIndexOfScalar(u8, text[0..index], '\n')) |idx| idx + 1 else 0, diff --git a/tests/ErrorBuilder.zig b/tests/ErrorBuilder.zig index 61a503919..f5ce8732e 100644 --- a/tests/ErrorBuilder.zig +++ b/tests/ErrorBuilder.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const zls = @import("zls"); @@ -9,6 +10,15 @@ const ErrorBuilder = @This(); allocator: std.mem.Allocator, files: std.StringArrayHashMapUnmanaged(File) = .{}, message_count: usize = 0, +/// similar to `git diff --unified` +/// show error messages with n lines of context. +/// null will show the whole file +unified: ?usize = 3, +file_name_visibility: enum { + never, + multi_file, + always, +} = .multi_file, pub fn init(allocator: std.mem.Allocator) ErrorBuilder { return ErrorBuilder{ .allocator = allocator }; @@ -22,20 +32,20 @@ pub fn deinit(builder: *ErrorBuilder) void { file.messages.deinit(builder.allocator); } builder.files.deinit(builder.allocator); + builder.* = undefined; } /// assumes `name` and `source` outlives the `ErrorBuilder` pub fn addFile(builder: *ErrorBuilder, name: []const u8, source: []const u8) error{OutOfMemory}!void { const gop = try builder.files.getOrPutValue(builder.allocator, name, .{ .source = source }); - if (gop.found_existing) - std.debug.panic("file '{s}' already exists", .{name}); + assertFmt(!gop.found_existing, "file '{s}' already exists", .{name}); } -pub fn removeFile(builder: *ErrorBuilder, name: []const u8) error{OutOfMemory}!void { - const found = builder.files.remove(name); - if (!found) - std.debug.panic("file '{s}' doesn't exist", .{name}); - builder.message_count -= found.messages.items.len; +/// preserves insertion order +pub fn removeFile(builder: *ErrorBuilder, name: []const u8) void { + const found = builder.files.fetchOrderedRemove(name); + assertFmt(found != null, "file '{s}' doesn't exist", .{name}); + builder.message_count -= found.?.value.messages.items.len; } pub fn msgAtLoc( @@ -46,38 +56,27 @@ pub fn msgAtLoc( level: std.log.Level, args: anytype, ) error{OutOfMemory}!void { - if (loc.start > loc.end) - std.debug.panic("invalid source location {}", .{loc}); - const file = builder.files.getPtr(file_name) orelse - std.debug.panic("file '{s}' doesn't exist", .{file_name}); - if (loc.end > file.source.len) - std.debug.panic("source location {} is outside file source (len: {d})", .{ loc, file.source.len }); const message = try std.fmt.allocPrint(builder.allocator, fmt, args); errdefer builder.allocator.free(message); - - try file.messages.append(builder.allocator, .{ - .loc = loc, - .level = level, - .message = message, - }); - builder.message_count += 1; + try builder.appendMessage(message, file_name, loc, level); } pub fn msgAtIndex( builder: *ErrorBuilder, comptime fmt: []const u8, - file: []const u8, + file_name: []const u8, source_index: usize, level: std.log.Level, args: anytype, ) error{OutOfMemory}!void { - return msgAtLoc(builder, fmt, file, .{ .start = source_index, .end = source_index }, level, args); + return msgAtLoc(builder, fmt, file_name, .{ .start = source_index, .end = source_index }, level, args); } -pub fn hasMessages(builder: *ErrorBuilder) bool { +pub fn hasMessages(builder: ErrorBuilder) bool { return builder.message_count != 0; } +/// remove every message from all files pub fn clearMessages(builder: *ErrorBuilder) void { for (builder.files.values()) |*file| { for (file.messages.items) |item| { @@ -88,59 +87,197 @@ pub fn clearMessages(builder: *ErrorBuilder) void { builder.message_count = 0; } -pub fn write(builder: *ErrorBuilder, writer: anytype, tty_config: std.io.tty.Config) !void { - for (builder.files.keys(), builder.files.values()) |file_name, file| { - if (file.messages.items.len == 0) continue; +/// remove all files that contain no messages +pub fn removeUnusedFiles(builder: *ErrorBuilder) void { + var i: usize = 0; + while (i < builder.files.count()) : (i += 1) { + const file: *File = &builder.files.values()[i]; + if (file.messages.items.len == 0) { + file.messages.deinit(builder.allocator); // there may still be capacity remaining + builder.files.swapRemoveAt(i); + } else { + i += 1; + } + } +} - std.mem.sort(MsgItem, file.messages.items, file.source, ErrorBuilder.lessThan); +pub const FormatContext = struct { + builder: *const ErrorBuilder, + tty_config: ?std.io.tty.Config = null, +}; - try writer.writeByte('\n'); - if (builder.files.count() > 1) { - try writer.print("{s}:\n", .{file_name}); - } +pub fn format( + builder: *const ErrorBuilder, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + _ = options; + if (fmt.len != 0) std.fmt.invalidFmtError(fmt, builder.*); + try write(.{ .builder = builder }, writer); +} - var start: usize = 0; - for (file.messages.items) |item| { - const line = offsets.lineLocAtIndex(file.source, item.loc.start); - defer start = line.end; - - try writer.writeAll(file.source[start..line.end]); - try writer.writeByte('\n'); - for (line.start..item.loc.start) |_| try writer.writeByte(' '); - for (item.loc.start..item.loc.end) |_| try writer.writeByte('^'); - if (item.loc.start == item.loc.end) try writer.writeByte('^'); - - const level_txt: []const u8 = switch (item.level) { - .err => "error", - .warn => "warning", - .info => "info", - .debug => "debug", - }; - const color: std.io.tty.Color = switch (item.level) { - .err => .red, - .warn => .yellow, - .info => .white, - .debug => .white, - }; - try tty_config.setColor(writer, color); - try writer.print(" {s}: ", .{level_txt}); - try tty_config.setColor(writer, .reset); - try writer.writeAll(item.message); - } +pub fn formatContext( + context: FormatContext, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + _ = options; + if (fmt.len != 0) std.fmt.invalidFmtError(fmt, context.builder.*); + try write(context, writer); +} - try writer.writeAll(file.source[start..file.source.len]); - try writer.writeByte('\n'); - } +pub fn fmtContext(builder: *const ErrorBuilder, tty_config: std.io.tty.Config) std.fmt.Formatter(formatContext) { + return .{ .data = .{ + .builder = builder, + .tty_config = tty_config, + } }; } -pub fn writeDebug(builder: *ErrorBuilder) void { - std.debug.getStderrMutex().lock(); - defer std.debug.getStderrMutex().unlock(); +pub fn writeDebug(builder: *const ErrorBuilder) void { const stderr = std.io.getStdErr(); const tty_config = std.io.tty.detectConfig(stderr); // does zig trim the output or why is this needed? stderr.writeAll(" ") catch return; - nosuspend builder.write(stderr.writer(), tty_config) catch return; + std.debug.print("\n{}\n", .{builder.fmtContext(tty_config)}); +} + +// +// +// + +fn assertFmt(ok: bool, comptime fmt: []const u8, args: anytype) void { + if (builtin.mode == .Debug or builtin.is_test) { + if (!ok) std.debug.panic(fmt, args); + } else { + std.debug.assert(ok); + } +} + +fn appendMessage( + builder: *ErrorBuilder, + message: []const u8, + file_name: []const u8, + loc: offsets.Loc, + level: std.log.Level, +) error{OutOfMemory}!void { + assertFmt(loc.start <= loc.end, "invalid source location [{d}..{d}]", .{ loc.start, loc.end }); + const file: *File = blk: { + const file = builder.files.getPtr(file_name); + assertFmt(file != null, "file '{s}' doesn't exist", .{file_name}); + break :blk file.?; + }; + assertFmt( + loc.end <= file.source.len, + "source location [{d}..{d}] is outside file source (len: {d})", + .{ loc.start, loc.end, file.source.len }, + ); + assertFmt( + std.mem.count(u8, offsets.locToSlice(file.source, loc), "\n") == 0, + "source location [{d}..{d}] must span a single line", + .{ loc.start, loc.end }, + ); + + const Context = struct { + items: []MsgItem, + source: []const u8, + + pub fn lessThan(ctx: @This(), a: usize, b: usize) bool { + return MsgItem.lessThan(ctx.source, ctx.items[a], ctx.items[b]); + } + + pub fn swap(ctx: @This(), a: usize, b: usize) void { + return std.mem.swap(MsgItem, &ctx.items[a], &ctx.items[b]); + } + }; + + try file.messages.append(builder.allocator, .{ + .loc = loc, + .level = level, + .message = message, + }); + std.sort.insertionContext( + file.messages.items.len -| 2, + file.messages.items.len, + Context{ .items = file.messages.items, .source = file.source }, + ); + builder.message_count += 1; +} + +fn write(context: FormatContext, writer: anytype) @TypeOf(writer).Error!void { + const builder = context.builder; + for (builder.files.keys(), builder.files.values()) |file_name, file| { + if (file.messages.items.len == 0) continue; + + std.debug.assert(std.sort.isSorted(MsgItem, file.messages.items, file.source, MsgItem.lessThan)); + + if (builder.file_name_visibility == .always or + builder.file_name_visibility == .multi_file and builder.files.count() > 1) + { + try writer.print("{s}:\n", .{file_name}); + } + + var it = MsgItemIterator{ + .source = file.source, + .messages = file.messages.items, + }; + + var last_line_end: usize = 0; + var last_line_end_with_unified: usize = 0; + + while (it.next()) |line_messages| { + std.debug.assert(line_messages.len > 0); + + const some_line_source_index = line_messages[0].loc.start; + const line_loc = offsets.lineLocAtIndex(file.source, some_line_source_index); + defer last_line_end = line_loc.end; + + const unified_loc = if (builder.unified) |n| + offsets.multilineLocAtIndex(file.source, some_line_source_index, n) + else + offsets.Loc{ + .start = 0, + .end = file.source.len, + }; + defer last_line_end_with_unified = unified_loc.end; + + if (last_line_end_with_unified == 0) { // start + try writer.writeAll(file.source[unified_loc.start..line_loc.end]); + } else if (last_line_end_with_unified < unified_loc.start) { // no intersection + try writer.writeAll(file.source[last_line_end..@min(last_line_end_with_unified + 1, file.source.len)]); + try writer.writeAll(file.source[unified_loc.start..line_loc.end]); + } else { // intersection (we can merge) + try writer.writeAll(file.source[last_line_end..line_loc.end]); + } + + for (line_messages) |item| { + try writer.writeByte('\n'); + for (line_loc.start..item.loc.start) |_| try writer.writeByte(' '); + for (item.loc.start..item.loc.end) |_| try writer.writeByte('^'); + if (item.loc.start == item.loc.end) try writer.writeByte('^'); + + const level_txt: []const u8 = switch (item.level) { + .err => "error", + .warn => "warning", + .info => "info", + .debug => "debug", + }; + const color: std.io.tty.Color = switch (item.level) { + .err => .red, + .warn => .yellow, + .info => .white, + .debug => .white, + }; + if (context.tty_config) |tty| tty.setColor(writer, color) catch {}; + try writer.print(" {s}: ", .{level_txt}); + if (context.tty_config) |tty| tty.setColor(writer, .reset) catch {}; + try writer.writeAll(item.message); + } + } + + try writer.writeAll(file.source[last_line_end..last_line_end_with_unified]); + } } const File = struct { @@ -152,16 +289,215 @@ const MsgItem = struct { loc: offsets.Loc, level: std.log.Level, message: []const u8, + + fn lessThan(source: []const u8, lhs: MsgItem, rhs: MsgItem) bool { + const is_less = lhs.loc.start < rhs.loc.start; + const text = if (is_less) source[lhs.loc.start..rhs.loc.start] else source[rhs.loc.start..lhs.loc.start]; + + // report messages on the same line in reverse order + if (std.mem.indexOfScalar(u8, text, '\n') == null) { + return !is_less; + } + + return is_less; + } }; -fn lessThan(source: []const u8, lhs: MsgItem, rhs: MsgItem) bool { - const is_less = lhs.loc.start < rhs.loc.start; - const text = if (is_less) source[lhs.loc.start..rhs.loc.start] else source[rhs.loc.start..lhs.loc.start]; +/// iterates through MsgItem's grouped by lines +/// assumes that `messages` is sorted +const MsgItemIterator = struct { + source: []const u8, + messages: []const MsgItem, + msg_index: usize = 0, + + fn next(it: *MsgItemIterator) ?[]const MsgItem { + std.debug.assert(it.msg_index <= it.messages.len); + if (it.msg_index == it.messages.len) return null; - // report messages on the same line in reverse order - if (std.mem.indexOfScalar(u8, text, '\n') == null) { - return !is_less; + const msg = it.messages[it.msg_index]; + const line_loc = offsets.lineLocAtIndex(it.source, msg.loc.start); + + const start = it.msg_index; + const end = while (it.msg_index < it.messages.len) : (it.msg_index += 1) { + const loc = it.messages[it.msg_index].loc; + if (line_loc.start <= loc.start and loc.end <= line_loc.end) continue; + break it.msg_index; + } else it.messages.len; + + it.msg_index = end; + return it.messages[start..end]; } +}; + +// +// +// + +test ErrorBuilder { + var eb = ErrorBuilder.init(std.testing.allocator); + defer eb.deinit(); + try std.testing.expect(!eb.hasMessages()); + + try eb.addFile("example.zig", ""); + try eb.msgAtIndex("", "example.zig", 0, .info, .{}); + try std.testing.expect(eb.hasMessages()); + + eb.clearMessages(); + try std.testing.expect(!eb.hasMessages()); + eb.removeUnusedFiles(); + try std.testing.expectEqual(@as(usize, 0), eb.files.count()); +} + +test "ErrorBuilder - write" { + var eb = ErrorBuilder.init(std.testing.allocator); + defer eb.deinit(); + + try std.testing.expectFmt("", "{}", .{eb}); + + try eb.addFile("", + \\The missile knows where it is at all times. + \\It knows this because it knows where it isn't. + \\By subtracting where it is from where it isn't, or where it isn't from where it is + \\(whichever is greater), it obtains a difference, or deviation. + \\The guidance subsystem uses deviations to generate corrective commands to drive + \\the missile from a position where it is to a position where it isn't, and + \\arriving at a position where it wasn't, it now is. + \\Consequently, the position where it is, is now the position that it wasn't, and it + \\follows that the position that it was, is now the position that it isn't. + \\In the event that the position that it is in is not the position that it wasn't, + \\the system has acquired a variation, the variation being the difference between where + \\the missile is, and where it wasn't. + \\If variation is considered to be a significant factor, it too may be correcte by the GEA. + \\However, the missile must also know where it was. + \\The missile guidance computer scenario works as follows. + \\Because a variation has modified some of the information the missile has obtained, + \\it is not sure just where it is. + \\However, it is sure where it isn't, within reason, and it knows where it was. + \\It now subtracts where it should be from where it wasn't, or vice-versa, and by + \\differentiating this from the algebraic sum of where it shouldn't be, and where + \\it was, it is able to obtain the deviation and its variation, which is called error. + ); + + try std.testing.expectFmt("", "{}", .{eb}); + + { + eb.clearMessages(); + eb.unified = 0; + try eb.msgAtLoc("what about equallity?", "", .{ .start = 175, .end = 195 }, .warn, .{}); + + try std.testing.expectFmt( + \\(whichever is greater), it obtains a difference, or deviation. + \\ ^^^^^^^^^^^^^^^^^^^^ warning: what about equallity? + , "{}", .{eb}); + } + + { + eb.clearMessages(); + eb.unified = 1; + try eb.msgAtLoc("are safety checks enabled?", "", .{ .start = 94, .end = 105 }, .info, .{}); + + try std.testing.expectFmt( + \\It knows this because it knows where it isn't. + \\By subtracting where it is from where it isn't, or where it isn't from where it is + \\ ^^^^^^^^^^^ info: are safety checks enabled? + \\(whichever is greater), it obtains a difference, or deviation. + , "{}", .{eb}); + } + + { + eb.clearMessages(); + eb.unified = 1; + try eb.msgAtLoc("AAM or ASM?", "", .{ .start = 4, .end = 11 }, .info, .{}); + + try std.testing.expectFmt( + \\The missile knows where it is at all times. + \\ ^^^^^^^ info: AAM or ASM? + \\It knows this because it knows where it isn't. + , "{}", .{eb}); + } + + { + eb.clearMessages(); + eb.unified = 2; + try eb.msgAtLoc("reserved keyword!", "", .{ .start = 1432, .end = 1437 }, .err, .{}); + + try std.testing.expectFmt( + \\It now subtracts where it should be from where it wasn't, or vice-versa, and by + \\differentiating this from the algebraic sum of where it shouldn't be, and where + \\it was, it is able to obtain the deviation and its variation, which is called error. + \\ ^^^^^ error: reserved keyword! + , "{}", .{eb}); + } + + { + eb.clearMessages(); + try eb.msgAtLoc("redeclaration of work 'knows'", "", .{ .start = 69, .end = 74 }, .err, .{}); + try eb.msgAtLoc("declared here", "", .{ .start = 12, .end = 17 }, .info, .{}); + + eb.unified = 0; + try std.testing.expectFmt( + \\The missile knows where it is at all times. + \\ ^^^^^ info: declared here + \\It knows this because it knows where it isn't. + \\ ^^^^^ error: redeclaration of work 'knows' + , "{}", .{eb}); + + eb.unified = 1; + try std.testing.expectFmt( + \\The missile knows where it is at all times. + \\ ^^^^^ info: declared here + \\It knows this because it knows where it isn't. + \\ ^^^^^ error: redeclaration of work 'knows' + \\By subtracting where it is from where it isn't, or where it isn't from where it is + , "{}", .{eb}); + } + + { + eb.clearMessages(); + try eb.msgAtLoc("redeclaration of work 'knows'", "", .{ .start = 69, .end = 74 }, .err, .{}); + try eb.msgAtLoc("declared here", "", .{ .start = 12, .end = 17 }, .info, .{}); + + eb.unified = 0; + try std.testing.expectFmt( + \\The missile knows where it is at all times. + \\ ^^^^^ info: declared here + \\It knows this because it knows where it isn't. + \\ ^^^^^ error: redeclaration of work 'knows' + , "{}", .{eb}); + + eb.unified = 1; + try std.testing.expectFmt( + \\The missile knows where it is at all times. + \\ ^^^^^ info: declared here + \\It knows this because it knows where it isn't. + \\ ^^^^^ error: redeclaration of work 'knows' + \\By subtracting where it is from where it isn't, or where it isn't from where it is + , "{}", .{eb}); + } +} + +test "ErrorBuilder - write on empty file" { + var eb = ErrorBuilder.init(std.testing.allocator); + defer eb.deinit(); + + try eb.addFile("empty.zig", ""); + try eb.msgAtIndex("why is this empty?", "empty.zig", 0, .warn, .{}); + + eb.unified = null; + try std.testing.expectFmt( + \\ + \\^ warning: why is this empty? + , "{}", .{eb}); + + eb.unified = 0; + try std.testing.expectFmt( + \\ + \\^ warning: why is this empty? + , "{}", .{eb}); - return is_less; + eb.unified = 2; + try std.testing.expectFmt( + \\ + \\^ warning: why is this empty? + , "{}", .{eb}); } diff --git a/tests/utility/offsets.zig b/tests/utility/offsets.zig index b8f805bb7..f3c84587a 100644 --- a/tests/utility/offsets.zig +++ b/tests/utility/offsets.zig @@ -91,6 +91,23 @@ test "offsets - lineLocAtIndex" { try std.testing.expectEqualStrings("foo", offsets.lineSliceAtIndex("foo\n", 3)); } +test "offsets - multilineLocAtIndex" { + const text = + \\line0 + \\line1 + \\line2 + \\line3 + \\line4 + ; + try std.testing.expectEqualStrings(offsets.lineSliceAtIndex(text, 0), offsets.multilineSliceAtIndex(text, 0, 0)); + try std.testing.expectEqualStrings(offsets.lineSliceAtIndex(text, 5), offsets.multilineSliceAtIndex(text, 5, 0)); + try std.testing.expectEqualStrings(offsets.lineSliceAtIndex(text, 6), offsets.multilineSliceAtIndex(text, 6, 0)); + + try std.testing.expectEqualStrings("line1\nline2\nline3", offsets.multilineSliceAtIndex(text, 15, 1)); + try std.testing.expectEqualStrings("line0\nline1", offsets.multilineSliceAtIndex(text, 3, 1)); + try std.testing.expectEqualStrings("line3\nline4", offsets.multilineSliceAtIndex(text, 27, 1)); +} + test "offsets - lineLocUntilIndex" { try std.testing.expectEqualStrings("", offsets.lineSliceUntilIndex("", 0)); try std.testing.expectEqualStrings("", offsets.lineSliceUntilIndex("\n", 0)); From ecb2f2e1db1441bc62ce035a0c7c468ea95567de Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Fri, 10 Nov 2023 20:27:21 +0100 Subject: [PATCH 02/30] another intern pool rewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - new `StringPool` - most functions now operate on `InternPool.Index` instead of `ÌnternPool.Key` - many new helper functions --- src/ComptimeInterpreter.zig | 280 +- src/analyser/InternPool.zig | 2774 ++++++++++------- src/analyser/analyser.zig | 1 + src/analyser/completions.zig | 82 +- src/analyser/error_msg.zig | 164 + src/analyser/string_pool.zig | 194 ++ src/analysis.zig | 13 +- src/features/completions.zig | 1 - src/features/semantic_tokens.zig | 2 +- src/stage2/AstGen.zig | 2 +- .../comptime_interpreter.zig | 51 +- 11 files changed, 2260 insertions(+), 1304 deletions(-) create mode 100644 src/analyser/error_msg.zig create mode 100644 src/analyser/string_pool.zig diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index e43806ce9..328e2d323 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -13,6 +13,7 @@ const offsets = @import("offsets.zig"); const DocumentStore = @import("DocumentStore.zig"); pub const InternPool = @import("analyser/InternPool.zig"); +pub const ErrorMsg = @import("analyser/error_msg.zig").ErrorMsg; pub const Index = InternPool.Index; pub const Key = InternPool.Key; pub const ComptimeInterpreter = @This(); @@ -73,8 +74,8 @@ pub const Namespace = struct { node_idx: Ast.Node.Index, /// Will be a struct, enum, union, opaque or .none ty: InternPool.Index, - decls: std.StringArrayHashMapUnmanaged(InternPool.DeclIndex) = .{}, - usingnamespaces: std.ArrayListUnmanaged(InternPool.DeclIndex) = .{}, + decls: std.StringArrayHashMapUnmanaged(InternPool.Decl.Index) = .{}, + usingnamespaces: std.ArrayListUnmanaged(InternPool.Decl.Index) = .{}, pub const Index = InternPool.NamespaceIndex; @@ -132,7 +133,7 @@ pub fn huntItDown( namespace: Namespace.Index, decl_name: []const u8, options: InterpretOptions, -) ?InternPool.DeclIndex { +) InternPool.Decl.OptionalIndex { _ = options; var current_namespace = namespace; @@ -141,11 +142,11 @@ pub fn huntItDown( defer current_namespace = interpreter.namespaces.items(.parent)[@intFromEnum(current_namespace)]; if (decls.get(decl_name)) |decl| { - return decl; + return decl.toOptional(); } } - return null; + return .none; } // Might be useful in the future @@ -199,12 +200,13 @@ pub fn interpret( const struct_index = try interpreter.ip.createStruct(interpreter.allocator, .{ .fields = .{}, + .owner_decl = .none, // TODO .namespace = container_namespace, .layout = .Auto, // TODO .backing_int_ty = .none, // TODO .status = .none, // TODO }); - var struct_info = interpreter.ip.getStruct(struct_index); + var struct_info = interpreter.ip.getStructMut(struct_index); var buffer: [2]Ast.Node.Index = undefined; @@ -222,20 +224,21 @@ pub fn interpret( else (try (try interpreter.interpret(container_field.ast.value_expr, container_namespace, .{})).getValue()).index; // TODO check ty - const init_value_ty = interpreter.ip.indexToKey(init_value.index).typeOf(); + const init_value_ty = interpreter.ip.typeOf(init_value.index); if (init_value_ty != .type_type) { try interpreter.recordError( container_field.ast.type_expr, "expected_type", "expected type 'type', found '{}'", - .{init_value_ty.fmt(interpreter.ip.*)}, + .{init_value_ty.fmt(interpreter.ip)}, ); continue; } const field_name = tree.tokenSlice(container_field.ast.main_token); + const field_name_index = try interpreter.ip.string_pool.getOrPutString(interpreter.allocator, field_name); - try struct_info.fields.put(interpreter.allocator, field_name, .{ + try struct_info.fields.put(interpreter.allocator, field_name_index, .{ .ty = init_value.index, .default_value = default_value, .alignment = 0, // TODO, @@ -261,15 +264,16 @@ pub fn interpret( .aligned_var_decl, .simple_var_decl, => { - var decls: *std.StringArrayHashMapUnmanaged(InternPool.DeclIndex) = &interpreter.namespaces.items(.decls)[@intFromEnum(namespace)]; + var decls: *std.StringArrayHashMapUnmanaged(InternPool.Decl.Index) = &interpreter.namespaces.items(.decls)[@intFromEnum(namespace)]; const name = analysis.getDeclName(tree, node_idx).?; const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ - .name = name, + .name = try interpreter.ip.string_pool.getOrPutString(interpreter.allocator, name), .node_idx = node_idx, .index = .none, .alignment = 0, // TODO .address_space = .generic, // TODO + .src_namespace = namespace, .is_pub = true, // TODO .is_exported = false, // TODO }); @@ -295,16 +299,14 @@ pub fn interpret( if (type_value == null and init_value == null) return InterpretResult{ .nothing = {} }; if (type_value) |v| { - if (interpreter.ip.indexToKey(v.index).typeOf() != .type_type) { + if (interpreter.ip.typeOf(v.index) != .type_type) { return InterpretResult{ .nothing = {} }; } } // TODO coerce `init_value` into `type_value` - const decl = interpreter.ip.getDecl(decl_index); - decl.index = if (type_value) |v| try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = v.index }, - }) else init_value.?.index; + const decl = interpreter.ip.getDeclMut(decl_index); + decl.index = if (type_value) |v| try interpreter.ip.getUnknown(interpreter.allocator, v.index) else init_value.?.index; // TODO: Am I a dumbo shrimp? (e.g. is this tree shaking correct? works on my machine so like...) @@ -419,7 +421,7 @@ pub fn interpret( } // Logic to find identifiers in accessible scopes - if (interpreter.huntItDown(namespace, identifier, options)) |decl_index| { + if (interpreter.huntItDown(namespace, identifier, options).unwrap()) |decl_index| { const decl = interpreter.ip.getDecl(decl_index); if (decl.index == .none) return InterpretResult{ .nothing = {} }; return InterpretResult{ .value = Value{ @@ -445,61 +447,62 @@ pub fn interpret( var ir = try interpreter.interpret(data[node_idx].lhs, namespace, options); const ir_value = try ir.getValue(); - const val_index = ir_value.index; - const val = interpreter.ip.indexToKey(val_index); - std.debug.assert(val.typeOf() != .none); - const ty = interpreter.ip.indexToKey(val.typeOf()); + const val = ir_value.index; + const ty = interpreter.ip.typeOf(val); - const inner_ty = switch (ty) { - .pointer_type => |info| if (info.size == .One) interpreter.ip.indexToKey(info.elem_type) else ty, + const inner_ty = switch (interpreter.ip.indexToKey(ty)) { + .pointer_type => |pointer_info| if (pointer_info.size == .One) pointer_info.elem_type else ty, else => ty, }; - const can_have_fields: bool = switch (inner_ty) { + switch (interpreter.ip.indexToKey(inner_ty)) { .simple_type => |simple| switch (simple) { .type => blk: { - if (interpreter.huntItDown(val.getNamespace(interpreter.ip.*), field_name, options)) |decl_index| { - const decl = interpreter.ip.getDecl(decl_index); - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = decl.index, - } }; - } + if (val == .none) break :blk; - if (val == .unknown_value) { - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - .index = .unknown_unknown, - } }; + const namespace_index = interpreter.ip.getNamespace(val); + if (namespace_index != .none) { + if (interpreter.huntItDown(namespace_index, field_name, options).unwrap()) |decl_index| { + const decl = interpreter.ip.getDecl(decl_index); + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = node_idx, + .index = decl.index, + } }; + } } - switch (val) { + switch (interpreter.ip.indexToKey(val)) { + .unknown_value => { + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + .index = .unknown_unknown, + } }; + }, .error_set_type => |error_set_info| { // TODO _ = error_set_info; }, .union_type => {}, // TODO .enum_type => |enum_index| { // TODO const enum_info = interpreter.ip.getEnum(enum_index); - if (enum_info.fields.get(field_name)) |field| { - _ = field; - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - .index = .unknown_unknown, // TODO - }, - }; - } + const field_name_index = interpreter.ip.string_pool.getString(field_name) orelse break :blk; + const field = enum_info.fields.get(field_name_index) orelse break :blk; + _ = field; + return InterpretResult{ + .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + .index = .unknown_unknown, // TODO + }, + }; }, - else => break :blk false, + else => {}, } - break :blk true; }, - else => false, + else => {}, }, - .pointer_type => |pointer_info| blk: { + .pointer_type => |pointer_info| { if (pointer_info.size == .Slice) { if (std.mem.eql(u8, field_name, "ptr")) { var many_ptr_info = InternPool.Key{ .pointer_type = pointer_info }; @@ -540,9 +543,8 @@ pub fn interpret( }; } } - break :blk true; }, - .array_type => |array_info| blk: { + .array_type => |array_info| { if (std.mem.eql(u8, field_name, "len")) { return InterpretResult{ .value = Value{ .interpreter = interpreter, @@ -553,23 +555,22 @@ pub fn interpret( } }), } }; } - break :blk true; }, .optional_type => |optional_info| blk: { - if (!std.mem.eql(u8, field_name, "?")) break :blk false; + if (!std.mem.eql(u8, field_name, "?")) break :blk; - if (val_index == .type_type) { + if (val == .type_type) { try interpreter.recordError( node_idx, "null_unwrap", "tried to unwrap optional of type `{}` which was null", - .{optional_info.payload_type.fmt(interpreter.ip.*)}, + .{optional_info.payload_type.fmt(interpreter.ip)}, ); return error.InvalidOperation; } - const result = switch (val) { + const result = switch (interpreter.ip.indexToKey(val)) { .optional_value => |optional_val| optional_val.val, - .unknown_value => val_index, + .unknown_value => val, else => return error.InvalidOperation, }; return InterpretResult{ .value = Value{ @@ -580,32 +581,29 @@ pub fn interpret( }, .struct_type => |struct_index| blk: { const struct_info = interpreter.ip.getStruct(struct_index); - if (struct_info.fields.getIndex(field_name)) |field_index| { - const field = struct_info.fields.values()[field_index]; - - const result = switch (val) { - .aggregate => |aggregate| aggregate.values[field_index], - .unknown_value => try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = field.ty }, - }), - else => return error.InvalidOperation, - }; + const field_name_index = interpreter.ip.string_pool.getString(field_name) orelse break :blk; + const field_index = struct_info.fields.getIndex(field_name_index) orelse break :blk; + const field = struct_info.fields.values()[field_index]; + + const result = switch (interpreter.ip.indexToKey(val)) { + .aggregate => |aggregate| aggregate.values[field_index], + .unknown_value => try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = field.ty }, + }), + else => return error.InvalidOperation, + }; - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - .index = result, - } }; - } - break :blk true; + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + .index = result, + } }; }, - .enum_type => |enum_info| blk: { // TODO + .enum_type => |enum_info| { // TODO _ = enum_info; - break :blk true; }, - .union_type => |union_info| blk: { // TODO + .union_type => |union_info| { // TODO _ = union_info; - break :blk true; }, .int_type, .error_union_type, @@ -614,7 +612,7 @@ pub fn interpret( .tuple_type, .vector_type, .anyframe_type, - => false, + => {}, .simple_value, .int_u64_value, @@ -628,31 +626,31 @@ pub fn interpret( .float_comptime_value, => unreachable, - .bytes, .optional_value, .slice, .aggregate, .union_value, .null_value, + .error_value, .undefined_value, .unknown_value, => unreachable, - }; + } - const accessed_ty = if (inner_ty == .simple_type and inner_ty.simple_type == .type) val else inner_ty; - if (can_have_fields) { + const accessed_ty = if (inner_ty == .type_type) val else inner_ty; + if (interpreter.ip.canHaveFields(accessed_ty)) { try interpreter.recordError( node_idx, "undeclared_identifier", "`{}` has no member '{s}'", - .{ accessed_ty.fmt(interpreter.ip.*), field_name }, + .{ accessed_ty.fmt(interpreter.ip), field_name }, ); } else { try interpreter.recordError( node_idx, "invalid_field_access", "`{}` does not support field access", - .{accessed_ty.fmt(interpreter.ip.*)}, + .{accessed_ty.fmt(interpreter.ip)}, ); } return error.InvalidOperation; @@ -681,8 +679,7 @@ pub fn interpret( const ir = try interpreter.interpret(if_info.ast.cond_expr, namespace, options); const condition = (try ir.getValue()).index; - const condition_val = interpreter.ip.indexToKey(condition); - const condition_ty = condition_val.typeOf(); + const condition_ty = interpreter.ip.typeOf(condition); switch (condition_ty) { .bool_type => {}, @@ -692,12 +689,12 @@ pub fn interpret( if_info.ast.cond_expr, "invalid_if_condition", "expected `bool` but found `{}`", - .{condition_ty.fmt(interpreter.ip.*)}, + .{condition_ty.fmt(interpreter.ip)}, ); return error.InvalidOperation; }, } - if (condition_val == .unknown_value) { + if (interpreter.ip.indexToKey(condition) == .unknown_value) { return InterpretResult{ .nothing = {} }; } @@ -795,11 +792,16 @@ pub fn interpret( const to_val = try lhs.getValue(); const from_val = try rhs.getValue(); - const to_ty = interpreter.ip.indexToKey(to_val.index).typeOf(); - const from_ty = interpreter.ip.indexToKey(from_val.index).typeOf(); + const to_ty = interpreter.ip.typeOf(to_val.index); + const from_ty = interpreter.ip.typeOf(from_val.index); + _ = from_ty; + + var arena_allocator = std.heap.ArenaAllocator.init(interpreter.allocator); + defer arena_allocator.deinit(); + var err_msg: ErrorMsg = undefined; // TODO report error - _ = try interpreter.ip.cast(interpreter.allocator, to_ty, from_ty, builtin.target); + _ = try interpreter.ip.coerce(interpreter.allocator, arena_allocator.allocator(), to_ty, from_val.index, builtin.target, &err_msg); return InterpretResult{ .nothing = {} }; }, @@ -837,10 +839,10 @@ pub fn interpret( try writer.writeAll("indeterminate"); continue; }; - const val = interpreter.ip.indexToKey(ir_value.index); - const ty = val.typeOf(); + const val = ir_value.index; + const ty = interpreter.ip.typeOf(val); - try writer.print("@as({}, {})", .{ ty.fmt(interpreter.ip.*), val.fmt(interpreter.ip.*) }); + try writer.print("@as({}, {})", .{ ty.fmt(interpreter.ip), val.fmt(interpreter.ip) }); if (index != params.len - 1) try writer.writeAll(", "); } @@ -869,6 +871,7 @@ pub fn interpret( if (std.mem.eql(u8, import_str[1 .. import_str.len - 1], "root")) { const struct_index = try interpreter.ip.createStruct(interpreter.allocator, .{ .fields = .{}, + .owner_decl = .none, // TODO .namespace = .none, .layout = .Auto, .backing_int_ty = .none, @@ -912,7 +915,7 @@ pub fn interpret( for (params, types) |param, *out_type| { const value = try (try interpreter.interpret(param, namespace, options)).getValue(); - out_type.* = interpreter.ip.indexToKey(value.index).typeOf(); + out_type.* = interpreter.ip.typeOf(value.index); } const peer_type = try interpreter.ip.resolvePeerTypes(interpreter.allocator, types, builtin.target); @@ -923,7 +926,7 @@ pub fn interpret( try writer.writeAll("incompatible types: "); for (types, 0..) |ty, i| { if (i != 0) try writer.writeAll(", "); - try writer.print("`{}`", .{ty.fmt(interpreter.ip.*)}); + try writer.print("`{}`", .{ty.fmt(interpreter.ip)}); } try interpreter.recordError(node_idx, "invalid_typeof", "{s}", .{output.items}); @@ -942,25 +945,27 @@ pub fn interpret( const ir_value = try (try interpreter.interpret(params[0], namespace, options)).getValue(); const field_name = try (try interpreter.interpret(params[1], namespace, options)).getValue(); + _ = field_name; - const val = interpreter.ip.indexToKey(ir_value.index); - const ty = val.typeOf(); + const val = ir_value.index; + const ty = interpreter.ip.typeOf(val); if (ty != .type_type) return error.InvalidBuiltin; - const value_namespace = interpreter.ip.indexToKey(ty).getNamespace(interpreter.ip.*); + const value_namespace = interpreter.ip.getNamespace(ty); if (value_namespace == .none) return error.InvalidBuiltin; - const name = interpreter.ip.indexToKey(field_name.index).bytes; // TODO add checks + return InterpretResult{ .nothing = {} }; + // const name = field_name.index; // TODO - const decls = interpreter.namespaces.items(.decls)[@intFromEnum(value_namespace)]; - const has_decl = decls.contains(name); + // const decls = interpreter.namespaces.items(.decls)[@intFromEnum(value_namespace)]; + // const has_decl = decls.contains(name); - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .index = if (has_decl) .bool_true else .bool_false, - } }; + // return InterpretResult{ .value = Value{ + // .interpreter = interpreter, + // .node_idx = node_idx, + // .index = if (has_decl) .bool_true else .bool_false, + // } }; } if (std.mem.eql(u8, call_name, "@as")) { @@ -969,7 +974,7 @@ pub fn interpret( const as_type = try (try interpreter.interpret(params[0], namespace, options)).getValue(); // const value = try (try interpreter.interpret(params[1], namespace, options)).getValue(); - if (interpreter.ip.indexToKey(as_type.index).typeOf() != .type_type) { + if (interpreter.ip.typeOf(as_type.index) != .type_type) { return error.InvalidBuiltin; } @@ -991,25 +996,20 @@ pub fn interpret( .string_literal => { const str = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1]; - // const string_literal_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ - // .elem_type = try interpreter.ip.get(interpreter.allocator, Key{ .array_type = .{ - // .child = Index.u8_type, - // .len = @intCast(u64, str.len), - // .sentinel = try interpreter.ip.get(interpreter.allocator, Key{ .int_u64_value = .{ .ty = .u8_type, .int = 0 } }), - // } }), - // .sentinel = .none, - // .alignment = 0, - // .size = .One, - // .is_const = true, - // .is_volatile = false, - // .is_allowzero = false, - // .address_space = .generic, - // } }); + const string_literal_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ + .elem_type = try interpreter.ip.get(interpreter.allocator, Key{ .array_type = .{ + .child = .u8_type, + .len = @intCast(str.len), + .sentinel = .zero_u8, + } }), + .size = .One, + .is_const = true, + } }); return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .index = try interpreter.ip.get(interpreter.allocator, Key{ .bytes = str }), + .index = try interpreter.ip.getUnknown(interpreter.allocator, string_literal_type), } }; }, // TODO: Add comptime autodetection; e.g. const MyArrayList = std.ArrayList(u8) @@ -1067,11 +1067,12 @@ pub fn interpret( const decls = &interpreter.namespaces.items(.decls)[@intFromEnum(namespace)]; const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ - .name = name, + .name = try interpreter.ip.string_pool.getOrPutString(interpreter.allocator, name), .node_idx = node_idx, .index = function_type, .alignment = 0, // TODO .address_space = .generic, // TODO + .src_namespace = namespace, .is_pub = false, // TODO .is_exported = false, // TODO }); @@ -1116,7 +1117,7 @@ pub fn interpret( const ir_value = try result.getValue(); const val = ir_value.index; - const ty = interpreter.ip.indexToKey(ir_value.index).typeOf(); + const ty = interpreter.ip.typeOf(ir_value.index); if (ty == .unknown_type) { return InterpretResult{ .value = .{ @@ -1133,7 +1134,7 @@ pub fn interpret( node_idx, "invalid_deref", "expected type `bool` but got `{}`", - .{ty.fmt(interpreter.ip.*)}, + .{ty.fmt(interpreter.ip)}, ); return error.InvalidOperation; } @@ -1152,17 +1153,11 @@ pub fn interpret( const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); const ir_value = try result.getValue(); - const ty = interpreter.ip.indexToKey(ir_value.index).typeOf(); + const ty = interpreter.ip.typeOf(ir_value.index); const pointer_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ .elem_type = ty, - .sentinel = .none, - .alignment = 0, .size = .One, - .is_const = false, - .is_volatile = false, - .is_allowzero = false, - .address_space = .generic, } }); return InterpretResult{ .value = .{ @@ -1177,7 +1172,7 @@ pub fn interpret( const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); const ir_value = (try result.getValue()); - const ty = interpreter.ip.indexToKey(ir_value.index).typeOf(); + const ty = interpreter.ip.typeOf(ir_value.index); if (ty == .unknown_type) { return InterpretResult{ .value = .{ @@ -1249,13 +1244,13 @@ pub fn call( while (ast.nextFnParam(&arg_it)) |param| { if (arg_index >= arguments.len) return error.MissingArguments; const tex = try (try interpreter.interpret(param.type_expr, fn_namespace, options)).getValue(); - const tex_ty = interpreter.ip.indexToKey(tex.index).typeOf(); + const tex_ty = interpreter.ip.typeOf(tex.index); if (tex_ty != .type_type) { try interpreter.recordError( param.type_expr, "expected_type", "expected type 'type', found '{}'", - .{tex_ty.fmt(interpreter.ip.*)}, + .{tex_ty.fmt(interpreter.ip)}, ); return error.InvalidCast; } @@ -1265,11 +1260,12 @@ pub fn call( const decls = &interpreter.namespaces.items(.decls)[@intFromEnum(fn_namespace)]; const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ - .name = name, + .name = try interpreter.ip.string_pool.getOrPutString(interpreter.allocator, name), .node_idx = name_token, .index = arguments[arg_index].index, .alignment = 0, // TODO .address_space = .generic, // TODO + .src_namespace = namespace, .is_pub = true, // TODO .is_exported = false, // TODO }); diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index b935f3c69..a5a94bd72 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -3,6 +3,7 @@ map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, items: std.MultiArrayList(Item) = .{}, extra: std.ArrayListUnmanaged(u8) = .{}, +string_pool: StringPool = .{}, decls: std.SegmentedList(InternPool.Decl, 0) = .{}, structs: std.SegmentedList(InternPool.Struct, 0) = .{}, @@ -17,14 +18,12 @@ const assert = std.debug.assert; const expect = std.testing.expect; const expectFmt = std.testing.expectFmt; +pub const StringPool = @import("string_pool.zig").StringPool(.{}); +pub const SPString = StringPool.String; const encoding = @import("encoding.zig"); +const ErrorMsg = @import("error_msg.zig").ErrorMsg; -pub const Int = packed struct { - signedness: std.builtin.Signedness, - bits: u16, -}; - -pub const Pointer = packed struct { +pub const Pointer = struct { elem_type: Index, sentinel: Index = .none, size: std.builtin.Type.Pointer.Size, @@ -37,7 +36,7 @@ pub const Pointer = packed struct { address_space: std.builtin.AddressSpace = .generic, }; -pub const Array = packed struct { +pub const Array = struct { len: u64, child: Index, sentinel: Index = .none, @@ -56,13 +55,14 @@ pub const FieldStatus = enum { pub const StructIndex = enum(u32) { _ }; pub const Struct = struct { - fields: std.StringArrayHashMapUnmanaged(Field), + fields: std.AutoArrayHashMapUnmanaged(SPString, Field), + owner_decl: Decl.OptionalIndex, namespace: NamespaceIndex, layout: std.builtin.Type.ContainerLayout = .Auto, backing_int_ty: Index, status: FieldStatus, - pub const Field = packed struct { + pub const Field = struct { ty: Index, default_value: Index = .none, alignment: u16 = 0, @@ -70,25 +70,26 @@ pub const Struct = struct { }; }; -pub const Optional = packed struct { +pub const Optional = struct { payload_type: Index, }; -pub const ErrorUnion = packed struct { +pub const ErrorUnion = struct { + // .none if inferred error set error_set_type: Index, payload_type: Index, }; pub const ErrorSet = struct { - /// every element is guaranteed to be .bytes - names: []const Index, + owner_decl: Decl.OptionalIndex, + names: []const SPString, }; pub const EnumIndex = enum(u32) { _ }; pub const Enum = struct { tag_type: Index, - fields: std.StringArrayHashMapUnmanaged(void), + fields: std.AutoArrayHashMapUnmanaged(SPString, void), values: std.AutoArrayHashMapUnmanaged(Index, void), namespace: NamespaceIndex, tag_type_inferred: bool, @@ -113,12 +114,12 @@ pub const UnionIndex = enum(u32) { _ }; pub const Union = struct { tag_type: Index, - fields: std.StringArrayHashMapUnmanaged(Field), + fields: std.AutoArrayHashMapUnmanaged(SPString, Field), namespace: NamespaceIndex, layout: std.builtin.Type.ContainerLayout = .Auto, status: FieldStatus, - pub const Field = packed struct { + pub const Field = struct { ty: Index, alignment: u16, }; @@ -130,21 +131,21 @@ pub const Tuple = struct { values: []const Index, }; -pub const Vector = packed struct { +pub const Vector = struct { len: u32, child: Index, }; -pub const AnyFrame = packed struct { +pub const AnyFrame = struct { child: Index, }; -const U64Value = packed struct { +const U64Value = struct { ty: Index, int: u64, }; -const I64Value = packed struct { +const I64Value = struct { ty: Index, int: i64, }; @@ -154,14 +155,12 @@ pub const BigInt = struct { int: std.math.big.int.Const, }; -pub const Bytes = []const u8; - -pub const OptionalValue = packed struct { +pub const OptionalValue = struct { ty: Index, val: Index, }; -pub const Slice = packed struct { +pub const Slice = struct { ty: Index, ptr: Index, len: Index, @@ -172,35 +171,62 @@ pub const Aggregate = struct { values: []const Index, }; -pub const UnionValue = packed struct { +pub const UnionValue = struct { ty: Index, field_index: u32, val: Index, }; -pub const NullValue = packed struct { +pub const ErrorValue = struct { ty: Index, + error_tag_name: SPString, }; -pub const UndefinedValue = packed struct { +pub const NullValue = struct { ty: Index, }; -pub const UnknownValue = packed struct { +pub const UndefinedValue = struct { ty: Index, }; -pub const DeclIndex = enum(u32) { _ }; +pub const UnknownValue = struct { + /// asserts that this is not .type_type because that is a the same as .unknown_type + ty: Index, +}; pub const Decl = struct { - name: []const u8, + name: SPString, node_idx: u32, /// this stores both the type and the value - index: Index, + index: InternPool.Index, alignment: u16, address_space: std.builtin.AddressSpace, + src_namespace: InternPool.NamespaceIndex, is_pub: bool, is_exported: bool, + + pub const Index = enum(u32) { + _, + + pub fn toOptional(i: Decl.Index) OptionalIndex { + return @as(OptionalIndex, @enumFromInt(@intFromEnum(i))); + } + }; + + pub const OptionalIndex = enum(u32) { + none = std.math.maxInt(u32), + _, + + pub fn init(oi: ?Decl.Index) OptionalIndex { + return if (oi) |index| index.toOptional() else .none; + } + + pub fn unwrap(oi: OptionalIndex) ?Decl.Index { + if (oi == .none) return null; + return @as(Decl.Index, @enumFromInt(@intFromEnum(oi))); + } + }; }; const BigIntInternal = struct { @@ -212,18 +238,15 @@ pub const Key = union(enum) { simple_type: SimpleType, simple_value: SimpleValue, - int_type: Int, + int_type: std.builtin.Type.Int, pointer_type: Pointer, array_type: Array, - /// TODO consider *Struct instead of StructIndex struct_type: StructIndex, optional_type: Optional, error_union_type: ErrorUnion, error_set_type: ErrorSet, - /// TODO consider *Enum instead of EnumIndex enum_type: EnumIndex, function_type: Function, - /// TODO consider *Union instead of UnionIndex union_type: UnionIndex, tuple_type: Tuple, vector_type: Vector, @@ -239,16 +262,15 @@ pub const Key = union(enum) { float_128_value: f128, float_comptime_value: f128, - bytes: Bytes, optional_value: OptionalValue, slice: Slice, aggregate: Aggregate, union_value: UnionValue, + error_value: ErrorValue, null_value: NullValue, undefined_value: UndefinedValue, unknown_value: UnknownValue, - // error // error union pub fn eql(a: Key, b: Key) bool { @@ -293,765 +315,16 @@ pub const Key = union(enum) { .float_128_value => .float_f128, .float_comptime_value => .float_comptime, - .bytes => .bytes, .optional_value => .optional_value, .slice => .slice, .aggregate => .aggregate, .union_value => .union_value, + .error_value => .error_value, .null_value => .null_value, .undefined_value => .undefined_value, .unknown_value => .unknown_value, }; } - - pub fn zigTypeTag(key: Key) std.builtin.TypeId { - return switch (key) { - .simple_type => |simple| switch (simple) { - .f16, - .f32, - .f64, - .f80, - .f128, - .c_longdouble, - => .Float, - - .usize, - .isize, - .c_char, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - => .Int, - - .comptime_int => .ComptimeInt, - .comptime_float => .ComptimeFloat, - - .anyopaque => .Opaque, - .bool => .Bool, - .void => .Void, - .type => .Type, - .anyerror => .ErrorSet, - .noreturn => .NoReturn, - .anyframe_type => .AnyFrame, - .empty_struct_type => .Struct, - .null_type => .Null, - .undefined_type => .Undefined, - .enum_literal_type => .EnumLiteral, - - .atomic_order => .Enum, - .atomic_rmw_op => .Enum, - .calling_convention => .Enum, - .address_space => .Enum, - .float_mode => .Enum, - .reduce_op => .Enum, - .modifier => .Enum, - .prefetch_options => .Struct, - .export_options => .Struct, - .extern_options => .Struct, - .type_info => .Union, - - .unknown => unreachable, - .generic_poison => unreachable, - }, - - .int_type => .Int, - .pointer_type => .Pointer, - .array_type => .Array, - .struct_type => .Struct, - .optional_type => .Optional, - .error_union_type => .ErrorUnion, - .error_set_type => .ErrorSet, - .enum_type => .Enum, - .function_type => .Fn, - .union_type => .Union, - .tuple_type => .Struct, - .vector_type => .Vector, - .anyframe_type => .AnyFrame, - - .simple_value, - .int_u64_value, - .int_i64_value, - .int_big_value, - .float_16_value, - .float_32_value, - .float_64_value, - .float_80_value, - .float_128_value, - .float_comptime_value, - => unreachable, - - .bytes, - .optional_value, - .slice, - .aggregate, - .union_value, - .null_value, - .undefined_value, - .unknown_value, - => unreachable, - }; - } - - pub fn typeOf(key: Key) Index { - return switch (key) { - .simple_type => .type_type, - .simple_value => |simple| switch (simple) { - .undefined_value => .undefined_type, - .void_value => .void_type, - .unreachable_value => .noreturn_type, - .null_value => .null_type, - .bool_true => .bool_type, - .bool_false => .bool_type, - .the_only_possible_value => unreachable, - .generic_poison => .generic_poison_type, - }, - - .int_type, - .pointer_type, - .array_type, - .struct_type, - .optional_type, - .error_union_type, - .error_set_type, - .enum_type, - .function_type, - .union_type, - .tuple_type, - .vector_type, - .anyframe_type, - => .type_type, - - .int_u64_value => |int| int.ty, - .int_i64_value => |int| int.ty, - .int_big_value => |int| int.ty, - .float_16_value => .f16_type, - .float_32_value => .f32_type, - .float_64_value => .f64_type, - .float_80_value => .f80_type, - .float_128_value => .f128_type, - .float_comptime_value => .comptime_float_type, - - .bytes => .unknown_type, // TODO - .optional_value => |optional_info| optional_info.ty, - .slice => |slice_info| slice_info.ty, - .aggregate => |aggregate_info| aggregate_info.ty, - .union_value => |union_info| union_info.ty, - .null_value => |null_info| null_info.ty, - .undefined_value => |undefined_info| undefined_info.ty, - .unknown_value => |unknown_info| unknown_info.ty, - }; - } - - /// Asserts the type is an integer, enum, error set, packed struct, or vector of one of them. - pub fn intInfo(ty: Key, target: std.Target, ip: InternPool) Int { - var key: Key = ty; - while (true) switch (key) { - .simple_type => |simple| switch (simple) { - .usize => return .{ .signedness = .signed, .bits = target.ptrBitWidth() }, - .isize => return .{ .signedness = .unsigned, .bits = target.ptrBitWidth() }, - - .c_char => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.char) }, - .c_short => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.short) }, - .c_ushort => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ushort) }, - .c_int => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.int) }, - .c_uint => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.uint) }, - .c_long => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.long) }, - .c_ulong => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ulong) }, - .c_longlong => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.longlong) }, - .c_ulonglong => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ulonglong) }, - .c_longdouble => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.longdouble) }, - - // TODO revisit this when error sets support custom int types (comment taken from zig codebase) - .anyerror => return .{ .signedness = .unsigned, .bits = 16 }, - - else => unreachable, - }, - .int_type => |int_info| return int_info, - .enum_type => |enum_index| { - const enum_info = ip.getEnum(enum_index); - key = ip.indexToKey(enum_info.tag_type); - }, - .struct_type => |struct_index| { - const struct_info = ip.getStruct(struct_index); - assert(struct_info.layout == .Packed); - key = ip.indexToKey(struct_info.backing_int_ty); - }, - // TODO revisit this when error sets support custom int types (comment taken from zig codebase) - .error_set_type => return .{ .signedness = .unsigned, .bits = 16 }, - .vector_type => |vector_info| { - assert(vector_info.len == 1); - key = ip.indexToKey(vector_info.child); - }, - else => unreachable, - }; - } - - /// Asserts the type is a fixed-size float or comptime_float. - /// Returns 128 for comptime_float types. - pub fn floatBits(ty: Key, target: std.Target) u16 { - return switch (ty.simple_type) { - .f16 => 16, - .f32 => 32, - .f64 => 64, - .f80 => 80, - .f128, .comptime_float => 128, - .c_longdouble => target.c_type_bit_size(.longdouble), - - else => unreachable, - }; - } - - pub fn isSinglePointer(ty: Key) bool { - return switch (ty) { - .pointer_type => |pointer_info| pointer_info.size == .One, - else => false, - }; - } - - pub fn isCPtr(ty: Key) bool { - return switch (ty) { - .pointer_type => |pointer_info| pointer_info.size == .C, - else => false, - }; - } - - pub fn isConstPtr(ty: Key) bool { - return switch (ty) { - .pointer_type => |pointer_info| pointer_info.is_const, - else => false, - }; - } - - /// For pointer-like optionals, returns true, otherwise returns the allowzero property - /// of pointers. - pub fn ptrAllowsZero(ty: Key, ip: *const InternPool) bool { - if (ty.pointer_type.is_allowzero) return true; - return ty.isPtrLikeOptional(ip); - } - - /// Returns true if the type is optional and would be lowered to a single pointer - /// address value, using 0 for null. Note that this returns true for C pointers. - pub fn isPtrLikeOptional(ty: Key, ip: *const InternPool) bool { - switch (ty) { - .optional_type => |optional_info| { - const child_ty = optional_info.payload_type; - const child_key = ip.indexToKey(child_ty); - if (child_key != .pointer_type) return false; - const info = child_key.pointer_type; - switch (info.size) { - .Slice, .C => return false, - .Many, .One => return !info.is_allowzero, - } - }, - .pointer_type => |pointer_info| return pointer_info.size == .C, - else => return false, - } - } - - pub fn isPtrAtRuntime(ty: Key, ip: *const InternPool) bool { - return switch (ty) { - .pointer_type => |pointer_info| pointer_info.size != .Slice, - .optional_type => |optional_info| { - const child_type = ip.indexToKey(optional_info.payload_type); - switch (child_type) { - .pointer_type => |pointer_info| switch (pointer_info.size) { - .Slice, .C => return false, - .Many, .One => return !pointer_info.is_allowzero, - }, - else => return false, - } - }, - else => false, - }; - } - - pub fn elemType2(ty: Key) Index { - return switch (ty) { - .simple_type => |simple| switch (simple) { - .anyframe_type => Index.void_type, - else => unreachable, - }, - .pointer_type => |pointer_info| pointer_info.elem_type, - .array_type => |array_info| array_info.child, - .optional_type => |optional_info| optional_info.payload_type, - .vector_type => |vector_info| vector_info.child, - .anyframe_type => |anyframe_info| anyframe_info.child, - else => unreachable, - }; - } - - /// Asserts the type is an array, pointer or vector. - pub fn sentinel(ty: Key) Index { - return switch (ty) { - .pointer_type => |pointer_info| pointer_info.sentinel, - .array_type => |array_info| array_info.sentinel, - .vector_type => Index.none, - else => unreachable, - }; - } - - pub fn getNamespace(ty: Key, ip: InternPool) NamespaceIndex { - return switch (ty) { - .struct_type => |struct_index| ip.getStruct(struct_index).namespace, - .enum_type => |enum_index| ip.getEnum(enum_index).namespace, - .union_type => |union_index| ip.getUnion(union_index).namespace, - else => .none, - }; - } - - pub fn onePossibleValue(ty: Key, ip: InternPool) Index { - return switch (ty) { - .simple_type => |simple| switch (simple) { - .f16, - .f32, - .f64, - .f80, - .f128, - .usize, - .isize, - .c_char, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .anyopaque, - .bool, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .anyframe_type, - .enum_literal_type, - => Index.none, - - .empty_struct_type => Index.empty_aggregate, - .void => Index.void_value, - .noreturn => Index.unreachable_value, - .null_type => Index.null_value, - .undefined_type => Index.undefined_value, - - .atomic_order, - .atomic_rmw_op, - .calling_convention, - .address_space, - .float_mode, - .reduce_op, - .modifier, - .prefetch_options, - .export_options, - .extern_options, - .type_info, - => Index.none, - - .unknown => Index.unknown_unknown, - .generic_poison => unreachable, - }, - .int_type => |int_info| { - if (int_info.bits == 0) { - switch (int_info.signedness) { - .unsigned => return Index.zero_comptime_int, - .signed => return Index.zero_comptime_int, // do we need a signed zero? - } - } - return Index.none; - }, - .pointer_type => Index.none, - .array_type => |array_info| { - if (array_info.len == 0) { - return Index.empty_aggregate; - } else if (ip.indexToKey(array_info.child).onePossibleValue(ip) != Index.none) { - return Index.the_only_possible_value; - } - return Index.none; - }, - .struct_type => |struct_index| { - const struct_info = ip.getStruct(struct_index); - var field_it = struct_info.fields.iterator(); - while (field_it.next()) |entry| { - if (entry.value_ptr.is_comptime) continue; - if (ip.indexToKey(entry.value_ptr.ty).onePossibleValue(ip) != Index.none) continue; - return Index.none; - } - return Index.empty_aggregate; - }, - .optional_type => |optional_info| { - if (optional_info.payload_type == Index.noreturn_type) { - return Index.null_value; - } - return Index.none; - }, - .error_union_type => Index.none, - .error_set_type => Index.none, - .enum_type => |enum_index| { - const enum_info = ip.getEnum(enum_index); - return switch (enum_info.fields.count()) { - 0 => Index.unreachable_value, - 1 => enum_info.values.keys()[0], - else => Index.none, - }; - }, - .function_type => Index.none, - .union_type => panicOrElse("TODO", Index.none), - .tuple_type => panicOrElse("TODO", Index.none), - .vector_type => |vector_info| { - if (vector_info.len == 0) { - return panicOrElse("TODO return empty array value", Index.none); - } - return ip.indexToKey(vector_info.child).onePossibleValue(ip); - }, - .anyframe_type => Index.none, - - .simple_value, - .int_u64_value, - .int_i64_value, - .int_big_value, - .float_16_value, - .float_32_value, - .float_64_value, - .float_80_value, - .float_128_value, - .float_comptime_value, - => unreachable, - - .bytes, - .optional_value, - .slice, - .aggregate, - .union_value, - .null_value, - .undefined_value, - .unknown_value, - => unreachable, - }; - } - - pub fn isNull(val: Key) bool { - return switch (val) { - .simple_value => |simple| switch (simple) { - .null_value => true, - .bool_true => false, - .bool_false => true, - .the_only_possible_value => true, - else => unreachable, - }, - .int_u64_value => |int_value| int_value.int == 0, - .int_i64_value => |int_value| int_value.int == 0, - .int_big_value => |int_value| int_value.int.orderAgainstScalar(0).compare(.eq), - - .null_value => true, - .optional_value => false, - .unknown_value => unreachable, - - else => unreachable, - }; - } - - /// If the value fits in a u64, return it, otherwise null. - /// Asserts not undefined. - pub fn getUnsignedInt(val: Key) !?u64 { - return switch (val) { - .simple_value => |simple| switch (simple) { - .null_value => 0, - .bool_true => 1, - .bool_false => 0, - .the_only_possible_value => 0, - else => null, - }, - .int_u64_value => |int_value| int_value.int, - .int_i64_value => |int_value| @intCast(int_value.int), - .int_big_value => |int_value| int_value.int.to(u64) catch null, - .null_value => 0, - else => null, - }; - } - - pub const FormatContext = struct { - key: Key, - options: FormatOptions = .{}, - ip: InternPool, - }; - - // TODO add options for controling how types show be formatted - pub const FormatOptions = struct {}; - - fn format( - ctx: FormatContext, - comptime fmt_str: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) @TypeOf(writer).Error!void { - _ = options; - if (fmt_str.len != 0) std.fmt.invalidFmtError(fmt_str, Key); - try print(ctx.key, ctx.ip, writer); - } - - pub fn print(key: Key, ip: InternPool, writer: anytype) @TypeOf(writer).Error!void { - var k = key; - while (try printInternal(k, ip, writer)) |index| { - k = ip.indexToKey(index); - } - } - - fn printInternal(ty: Key, ip: InternPool, writer: anytype) @TypeOf(writer).Error!?Index { - switch (ty) { - .simple_type => |simple| switch (simple) { - .f16, - .f32, - .f64, - .f80, - .f128, - .usize, - .isize, - .c_char, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .anyopaque, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .anyframe_type, - => try writer.writeAll(@tagName(simple)), - - .null_type => try writer.writeAll("@TypeOf(null)"), - .undefined_type => try writer.writeAll("@TypeOf(undefined)"), - .empty_struct_type => try writer.writeAll("@TypeOf(.{})"), - .enum_literal_type => try writer.writeAll("@TypeOf(.enum_literal)"), - - .atomic_order => try writer.writeAll("std.builtin.AtomicOrder"), - .atomic_rmw_op => try writer.writeAll("std.builtin.AtomicRmwOp"), - .calling_convention => try writer.writeAll("std.builtin.CallingConvention"), - .address_space => try writer.writeAll("std.builtin.AddressSpace"), - .float_mode => try writer.writeAll("std.builtin.FloatMode"), - .reduce_op => try writer.writeAll("std.builtin.ReduceOp"), - .modifier => try writer.writeAll("std.builtin.CallModifier"), - .prefetch_options => try writer.writeAll("std.builtin.PrefetchOptions"), - .export_options => try writer.writeAll("std.builtin.ExportOptions"), - .extern_options => try writer.writeAll("std.builtin.ExternOptions"), - .type_info => try writer.writeAll("std.builtin.Type"), - .unknown => try writer.writeAll("(unknown type)"), - .generic_poison => unreachable, - }, - .int_type => |int_info| switch (int_info.signedness) { - .signed => try writer.print("i{}", .{int_info.bits}), - .unsigned => try writer.print("u{}", .{int_info.bits}), - }, - .pointer_type => |pointer_info| { - if (pointer_info.sentinel != Index.none) { - switch (pointer_info.size) { - .One, .C => unreachable, - .Many => try writer.print("[*:{}]", .{pointer_info.sentinel.fmt(ip)}), - .Slice => try writer.print("[:{}]", .{pointer_info.sentinel.fmt(ip)}), - } - } else switch (pointer_info.size) { - .One => try writer.writeAll("*"), - .Many => try writer.writeAll("[*]"), - .C => try writer.writeAll("[*c]"), - .Slice => try writer.writeAll("[]"), - } - - if (pointer_info.alignment != 0) { - try writer.print("align({d}", .{pointer_info.alignment}); - - if (pointer_info.bit_offset != 0 or pointer_info.host_size != 0) { - try writer.print(":{d}:{d}", .{ pointer_info.bit_offset, pointer_info.host_size }); - } - - try writer.writeAll(") "); - } - - if (pointer_info.address_space != .generic) { - try writer.print("addrspace(.{s}) ", .{@tagName(pointer_info.address_space)}); - } - - if (pointer_info.is_const) try writer.writeAll("const "); - if (pointer_info.is_volatile) try writer.writeAll("volatile "); - if (pointer_info.is_allowzero and pointer_info.size != .C) try writer.writeAll("allowzero "); - - return pointer_info.elem_type; - }, - .array_type => |array_info| { - try writer.print("[{d}", .{array_info.len}); - if (array_info.sentinel != Index.none) { - try writer.print(":{}", .{array_info.sentinel.fmt(ip)}); - } - try writer.writeByte(']'); - - return array_info.child; - }, - .struct_type => return panicOrElse("TODO", null), - .optional_type => |optional_info| { - try writer.writeByte('?'); - return optional_info.payload_type; - }, - .error_union_type => |error_union_info| { - try print(ip.indexToKey(error_union_info.error_set_type), ip, writer); - try writer.writeByte('!'); - return error_union_info.payload_type; - }, - .error_set_type => |error_set_info| { - const names = error_set_info.names; - try writer.writeAll("error{"); - for (names, 0..) |name, i| { - if (i != 0) try writer.writeByte(','); - try writer.writeAll(ip.indexToKey(name).bytes); - } - try writer.writeByte('}'); - }, - .enum_type => return panicOrElse("TODO", null), - .function_type => |function_info| { - try writer.writeAll("fn("); - - for (function_info.args, 0..) |arg_ty, i| { - if (i != 0) try writer.writeAll(", "); - - if (i < 32) { - if (function_info.args_is_comptime.isSet(i)) { - try writer.writeAll("comptime "); - } - if (function_info.args_is_noalias.isSet(i)) { - try writer.writeAll("noalias "); - } - } - - try print(ip.indexToKey(arg_ty), ip, writer); - } - - if (function_info.is_var_args) { - if (function_info.args.len != 0) { - try writer.writeAll(", "); - } - try writer.writeAll("..."); - } - try writer.writeAll(") "); - - if (function_info.alignment != 0) { - try writer.print("align({d}) ", .{function_info.alignment}); - } - if (function_info.calling_convention != .Unspecified) { - try writer.print("callconv(.{s}) ", .{@tagName(function_info.calling_convention)}); - } - - return function_info.return_type; - }, - .union_type => return panicOrElse("TODO", null), - .tuple_type => |tuple_info| { - try writer.writeAll("tuple{"); - for (tuple_info.types, 0..) |field_ty, i| { - if (i != 0) try writer.writeAll(", "); - const val = tuple_info.values[i]; - if (val != Index.none) { - try writer.writeAll("comptime "); - } - try print(ip.indexToKey(field_ty), ip, writer); - if (val != Index.none) { - try writer.print(" = {}", .{val.fmt(ip)}); - } - } - try writer.writeByte('}'); - }, - .vector_type => |vector_info| { - try writer.print("@Vector({d},{})", .{ - vector_info.len, - vector_info.child.fmt(ip), - }); - }, - .anyframe_type => |anyframe_info| { - try writer.writeAll("anyframe->"); - return anyframe_info.child; - }, - - .simple_value => |simple| switch (simple) { - .undefined_value => try writer.writeAll("undefined"), - .void_value => try writer.writeAll("{}"), - .unreachable_value => try writer.writeAll("unreachable"), - .null_value => try writer.writeAll("null"), - .bool_true => try writer.writeAll("true"), - .bool_false => try writer.writeAll("false"), - .the_only_possible_value => try writer.writeAll("(the only possible value)"), - .generic_poison => try writer.writeAll("(generic poison)"), - }, - .int_u64_value => |i| try std.fmt.formatIntValue(i.int, "", .{}, writer), - .int_i64_value => |i| try std.fmt.formatIntValue(i.int, "", .{}, writer), - .int_big_value => |i| try i.int.format("", .{}, writer), - .float_16_value => |float| try writer.print("{d}", .{float}), - .float_32_value => |float| try writer.print("{d}", .{float}), - .float_64_value => |float| try writer.print("{d}", .{float}), - .float_80_value => |float| try writer.print("{d}", .{@as(f64, @floatCast(float))}), - .float_128_value, - .float_comptime_value, - => |float| try writer.print("{d}", .{@as(f64, @floatCast(float))}), - - .bytes => |bytes| try writer.print("\"{}\"", .{std.zig.fmtEscapes(bytes)}), - .optional_value => |optional| { - return optional.val; - }, - .slice => |slice_value| { - _ = slice_value; - try writer.writeAll(".{"); - try writer.writeAll(" TODO "); // TODO - try writer.writeByte('}'); - }, - .aggregate => |aggregate| { - if (aggregate.values.len == 0) { - try writer.writeAll(".{}"); - return null; - } - const struct_info = ip.getStruct(ip.indexToKey(aggregate.ty).struct_type); - assert(aggregate.values.len == struct_info.fields.count()); - - try writer.writeAll(".{"); - var i: u32 = 0; - while (i < aggregate.values.len) : (i += 1) { - if (i != 0) try writer.writeAll(", "); - - const field_name = struct_info.fields.keys()[i]; - try writer.print(".{s} = {}", .{ field_name, aggregate.values[i].fmt(ip) }); - } - try writer.writeByte('}'); - }, - .union_value => |union_value| { - const union_info = ip.getUnion(ip.indexToKey(union_value.ty).union_type); - - const name = union_info.fields.keys()[union_value.field_index]; - try writer.print(".{{ .{} = {} }}", .{ - std.zig.fmtId(name), - union_value.val.fmt(ip), - }); - }, - .null_value => try writer.print("null", .{}), - .undefined_value => try writer.print("undefined", .{}), - .unknown_value => try writer.print("(unknown value)", .{}), - } - return null; - } - - pub fn fmt(key: Key, ip: InternPool) std.fmt.Formatter(format) { - return .{ .data = .{ - .key = key, - .ip = ip, - } }; - } }; pub const Item = struct { @@ -1138,12 +411,16 @@ pub const Index = enum(u32) { undefined_value, /// `0` (comptime_int) zero_comptime_int, + /// `0` (u1) + zero_u1, /// `0` (u8) zero_u8, /// `0` (usize) zero_usize, /// `1` (comptime_int) one_comptime_int, + /// `1` (u1) + one_u1, /// `1` (u8) one_u8, /// `1` (usize) @@ -1168,39 +445,16 @@ pub const Index = enum(u32) { none = std.math.maxInt(u32), _, - pub fn fmt(index: Index, ip: InternPool) std.fmt.Formatter(Key.format) { - return .{ .data = .{ - .key = ip.indexToKey(index), - .ip = ip, - } }; + pub inline fn fmt(index: Index, ip: *InternPool) std.fmt.Formatter(format) { + return fmtOptions(index, ip, .{}); } - pub fn fmtDebug(index: Index, ip: InternPool) std.fmt.Formatter(formatDebug) { - return .{ .data = .{ - .index = index, - .ip = ip, - } }; + pub inline fn fmtDebug(index: Index, ip: *InternPool) std.fmt.Formatter(format) { + return fmtOptions(index, ip, .{ .debug = true }); } - const FormatContext = struct { - index: Index, - ip: InternPool, - }; - - fn formatDebug( - ctx: FormatContext, - comptime fmt_str: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) @TypeOf(writer).Error!void { - if (ctx.index == .none) { - return writer.writeAll(".none"); - } else { - return Key.format(.{ - .key = ctx.ip.indexToKey(ctx.index), - .ip = ctx.ip, - }, fmt_str, options, writer); - } + pub fn fmtOptions(index: Index, ip: *InternPool, options: FormatOptions) std.fmt.Formatter(format) { + return .{ .data = .{ .index = index, .ip = ip, .options = options } }; } }; @@ -1299,9 +553,6 @@ pub const Tag = enum(u8) { /// data is payload to f128. float_comptime, - /// A byte sequence value. - /// data is payload to data begin and length. - bytes, /// A optional value that is not null. /// data is index to OptionalValue. optional_value, @@ -1314,6 +565,9 @@ pub const Tag = enum(u8) { /// A union value. /// data is index to UnionValue. union_value, + /// A error value. + /// data is index to ErrorValue. + error_value, /// A null value. /// data is index to type which may be unknown. null_value, @@ -1390,6 +644,7 @@ comptime { pub fn init(gpa: Allocator) Allocator.Error!InternPool { var ip: InternPool = .{}; + errdefer ip.deinit(gpa); const items = [_]struct { index: Index, key: Key }{ .{ .index = .u1_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 1 } } }, @@ -1464,9 +719,11 @@ pub fn init(gpa: Allocator) Allocator.Error!InternPool { .{ .index = .undefined_value, .key = .{ .simple_value = .undefined_value } }, .{ .index = .zero_comptime_int, .key = .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 0 } } }, + .{ .index = .zero_u1, .key = .{ .int_u64_value = .{ .ty = .u1_type, .int = 0 } } }, .{ .index = .zero_u8, .key = .{ .int_u64_value = .{ .ty = .u8_type, .int = 0 } } }, .{ .index = .zero_usize, .key = .{ .int_u64_value = .{ .ty = .usize_type, .int = 0 } } }, .{ .index = .one_comptime_int, .key = .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 1 } } }, + .{ .index = .one_u1, .key = .{ .int_u64_value = .{ .ty = .u1_type, .int = 1 } } }, .{ .index = .one_u8, .key = .{ .int_u64_value = .{ .ty = .u8_type, .int = 1 } } }, .{ .index = .one_usize, .key = .{ .int_u64_value = .{ .ty = .usize_type, .int = 1 } } }, .{ .index = .void_value, .key = .{ .simple_value = .void_value } }, @@ -1480,13 +737,14 @@ pub fn init(gpa: Allocator) Allocator.Error!InternPool { .{ .index = .unknown_unknown, .key = .{ .unknown_value = .{ .ty = .unknown_type } } }, }; - const extra_count = 6 * @sizeOf(Pointer) + @sizeOf(ErrorUnion) + 4 * @sizeOf(Function) + 6 * @sizeOf(InternPool.U64Value); + const extra_count = 6 * @sizeOf(Pointer) + @sizeOf(ErrorUnion) + 4 * @sizeOf(Function) + 8 * @sizeOf(InternPool.U64Value) + @sizeOf(InternPool.Aggregate); try ip.map.ensureTotalCapacity(gpa, items.len); try ip.items.ensureTotalCapacity(gpa, items.len); try ip.extra.ensureTotalCapacity(gpa, extra_count); - for (items) |item| { + for (items, 0..) |item, i| { + assert(@intFromEnum(item.index) == i); if (builtin.is_test or builtin.mode == .Debug) { var failing_allocator = std.testing.FailingAllocator.init(undefined, .{ .fail_index = 0, @@ -1505,6 +763,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { ip.map.deinit(gpa); ip.items.deinit(gpa); ip.extra.deinit(gpa); + ip.string_pool.deinit(gpa); var struct_it = ip.structs.iterator(0); while (struct_it.next()) |item| { @@ -1525,7 +784,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { ip.unions.deinit(gpa); } -pub fn indexToKey(ip: InternPool, index: Index) Key { +pub fn indexToKey(ip: *const InternPool, index: Index) Key { assert(index != .none); const item = ip.items.get(@intFromEnum(index)); const data = item.data; @@ -1576,11 +835,11 @@ pub fn indexToKey(ip: InternPool, index: Index) Key { .float_f128 => .{ .float_128_value = ip.extraData(f128, data) }, .float_comptime => .{ .float_comptime_value = ip.extraData(f128, data) }, - .bytes => .{ .bytes = ip.extraData([]const u8, data) }, .optional_value => .{ .optional_value = ip.extraData(OptionalValue, data) }, .slice => .{ .slice = ip.extraData(Slice, data) }, .aggregate => .{ .aggregate = ip.extraData(Aggregate, data) }, .union_value => .{ .union_value = ip.extraData(UnionValue, data) }, + .error_value => .{ .error_value = ip.extraData(ErrorValue, data) }, .null_value => .{ .null_value = .{ .ty = @as(Index, @enumFromInt(data)) } }, .undefined_value => .{ .undefined_value = .{ .ty = @as(Index, @enumFromInt(data)) } }, .unknown_value => .{ .unknown_value = .{ .ty = @as(Index, @enumFromInt(data)) } }, @@ -1615,55 +874,66 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { .float_32_value => |float_val| @as(u32, @bitCast(float_val)), .null_value => |null_val| @intFromEnum(null_val.ty), .undefined_value => |undefined_val| @intFromEnum(undefined_val.ty), - .unknown_value => |unknown_val| @intFromEnum(unknown_val.ty), - inline else => |data| try ip.addExtra(gpa, data), // TODO sad stage1 noises :( + .unknown_value => |unknown_val| blk: { + assert(unknown_val.ty != .type_type); // .unknown_type instead + break :blk @intFromEnum(unknown_val.ty); + }, + inline else => |data| try ip.addExtra(gpa, data), }; try ip.items.append(gpa, .{ .tag = tag, .data = data, }); - return @as(Index, @enumFromInt(ip.items.len - 1)); + return @enumFromInt(ip.items.len - 1); } -pub fn contains(ip: InternPool, key: Key) ?Index { +pub fn contains(ip: *const InternPool, key: Key) ?Index { const adapter: KeyAdapter = .{ .ip = &ip }; const index = ip.map.getIndexAdapted(key, adapter) orelse return null; - return @as(Index, @enumFromInt(index)); + return @enumFromInt(index); } -pub fn getDecl(ip: InternPool, index: InternPool.DeclIndex) *InternPool.Decl { - var decls = ip.decls; - return decls.at(@intFromEnum(index)); +pub fn getDecl(ip: *const InternPool, index: InternPool.Decl.Index) *const InternPool.Decl { + return ip.decls.at(@intFromEnum(index)); } -pub fn getStruct(ip: InternPool, index: InternPool.StructIndex) *InternPool.Struct { - var structs = ip.structs; - return structs.at(@intFromEnum(index)); +pub fn getDeclMut(ip: *InternPool, index: InternPool.Decl.Index) *InternPool.Decl { + return ip.decls.at(@intFromEnum(index)); } -pub fn getEnum(ip: InternPool, index: InternPool.EnumIndex) *InternPool.Enum { - var enums = ip.enums; - return enums.at(@intFromEnum(index)); +pub fn getStruct(ip: *const InternPool, index: InternPool.StructIndex) *const InternPool.Struct { + return ip.structs.at(@intFromEnum(index)); } -pub fn getUnion(ip: InternPool, index: InternPool.UnionIndex) *InternPool.Union { - var unions = ip.unions; - return unions.at(@intFromEnum(index)); +pub fn getStructMut(ip: *InternPool, index: InternPool.StructIndex) *InternPool.Struct { + return ip.structs.at(@intFromEnum(index)); +} +pub fn getEnum(ip: *const InternPool, index: InternPool.EnumIndex) *const InternPool.Enum { + return ip.enums.at(@intFromEnum(index)); +} +pub fn getEnumMut(ip: *InternPool, index: InternPool.EnumIndex) *InternPool.Enum { + return ip.enums.at(@intFromEnum(index)); +} +pub fn getUnion(ip: *const InternPool, index: InternPool.UnionIndex) *const InternPool.Union { + return ip.unions.at(@intFromEnum(index)); +} +pub fn getUnionMut(ip: *InternPool, index: InternPool.UnionIndex) *InternPool.Union { + return ip.unions.at(@intFromEnum(index)); } -pub fn createDecl(ip: *InternPool, gpa: Allocator, decl: InternPool.Decl) Allocator.Error!InternPool.DeclIndex { +pub fn createDecl(ip: *InternPool, gpa: Allocator, decl: InternPool.Decl) Allocator.Error!InternPool.Decl.Index { try ip.decls.append(gpa, decl); - return @as(InternPool.DeclIndex, @enumFromInt(ip.decls.count() - 1)); + return @enumFromInt(ip.decls.count() - 1); } pub fn createStruct(ip: *InternPool, gpa: Allocator, struct_info: InternPool.Struct) Allocator.Error!InternPool.StructIndex { try ip.structs.append(gpa, struct_info); - return @as(InternPool.StructIndex, @enumFromInt(ip.structs.count() - 1)); + return @enumFromInt(ip.structs.count() - 1); } pub fn createEnum(ip: *InternPool, gpa: Allocator, enum_info: InternPool.Enum) Allocator.Error!InternPool.EnumIndex { try ip.enums.append(gpa, enum_info); - return @as(InternPool.EnumIndex, @enumFromInt(ip.enums.count() - 1)); + return @enumFromInt(ip.enums.count() - 1); } pub fn createUnion(ip: *InternPool, gpa: Allocator, union_info: InternPool.Union) Allocator.Error!InternPool.UnionIndex { try ip.unions.append(gpa, union_info); - return @as(InternPool.UnionIndex, @enumFromInt(ip.unions.count() - 1)); + return @enumFromInt(ip.unions.count() - 1); } fn addExtra(ip: *InternPool, gpa: Allocator, extra: anytype) Allocator.Error!u32 { @@ -1823,11 +1093,249 @@ fn deepHash(hasher: anytype, key: anytype) void { // UTILITY // --------------------------------------------- -pub fn cast(ip: *InternPool, gpa: Allocator, destination_ty: Index, source_ty: Index, target: std.Target) Allocator.Error!Index { - return resolvePeerTypes(ip, gpa, &.{ destination_ty, source_ty }, target); +// pub const CoercionResult = union(enum) { +// ok: Index, +// err: ErrorMsg, +// }; + +/// @as(dest_ty, inst); +pub fn coerce( + ip: *InternPool, + gpa: Allocator, + arena: Allocator, + dest_ty: Index, + inst: Index, + target: std.Target, + /// TODO make this a return value instead of out pointer + /// see `CoercionResult` + err_msg: *ErrorMsg, +) Allocator.Error!Index { + assert(ip.isType(dest_ty)); + if (dest_ty == .unknown_type) return .unknown_unknown; + switch (ip.typeOf(dest_ty)) { + .unknown_type => return .unknown_unknown, + .type_type => {}, + else => unreachable, + } + + const inst_ty = ip.typeOf(inst); + if (inst_ty == dest_ty) return inst; + if (inst_ty == .undefined_type) return try ip.getUndefined(gpa, dest_ty); + if (inst_ty == .unknown_type) return try ip.getUnknown(gpa, dest_ty); + + var in_memory_result = try ip.coerceInMemoryAllowed(gpa, arena, dest_ty, inst_ty, false, builtin.target); + if (in_memory_result == .ok) return try ip.getUnknown(gpa, dest_ty); + + switch (ip.zigTypeTag(dest_ty)) { + .Optional => optional: { + // null to ?T + if (inst_ty == .null_type) { + return try ip.getNull(gpa, dest_ty); + } + const child_type = ip.indexToKey(dest_ty).optional_type.payload_type; + + // TODO cast from ?*T and ?[*]T to ?*anyopaque + // but don't do it if the source type is a double pointer + if (child_type == .anyopaque_type) { + return try ip.getUnknown(gpa, dest_ty); // TODO + } + + // T to ?T + const intermediate = try ip.coerce(gpa, arena, child_type, inst, target, err_msg); + if (intermediate == .none) break :optional; + + return try ip.get(gpa, .{ .optional_value = .{ + .ty = dest_ty, + .val = intermediate, + } }); + }, + .Pointer => pointer: { + const dest_info = ip.indexToKey(dest_ty).pointer_type; + + // Function body to function pointer. + if (ip.zigTypeTag(inst_ty) == .Fn) { + return try ip.getUnknown(gpa, dest_ty); + } + + const inst_ty_key = ip.indexToKey(inst_ty); + // *T to *[1]T + if (dest_info.size == .One and ip.isSinglePointer(inst_ty)) single_item: { + // TODO if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer; + const ptr_elem_ty = ip.indexToKey(inst_ty).pointer_type.elem_type; + + const array_ty = ip.indexToKey(dest_info.elem_type); + if (array_ty != .array_type) break :single_item; + const array_elem_ty = array_ty.array_type.child; + if (try ip.coerceInMemoryAllowed(gpa, arena, array_elem_ty, ptr_elem_ty, dest_info.is_const, target) != .ok) { + break :single_item; + } + return try ip.getUnknown(gpa, dest_ty); + // return ip.coerceCompatiblePtrs(gpa, arena, dest_ty, inst); + } + + // Coercions where the source is a single pointer to an array. + src_array_ptr: { + if (!ip.isSinglePointer(inst_ty)) break :src_array_ptr; // TODO + // TODO if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer; + + const array_ty = ip.indexToKey(inst_ty_key.pointer_type.elem_type); + if (array_ty != .array_type) break :src_array_ptr; + const array_elem_type = array_ty.array_type.child; + + const elem_res = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, array_elem_type, dest_info.is_const, target); + if (elem_res != .ok) { + in_memory_result = .{ .ptr_child = .{ + .child = try elem_res.dupe(arena), + .actual = array_elem_type, + .wanted = dest_info.elem_type, + } }; + break :src_array_ptr; + } + + if (dest_info.sentinel != .none and + dest_info.sentinel != array_ty.array_type.sentinel) + { + in_memory_result = .{ .ptr_sentinel = .{ + .actual = array_ty.array_type.sentinel, + .wanted = dest_info.sentinel, + .ty = dest_info.elem_type, + } }; + break :src_array_ptr; + } + + return try ip.getUnknown(gpa, dest_ty); + // switch (dest_info.size) { + // // *[N]T to []T + // .Slice => return ip.coerceArrayPtrToSlice(gpa, arena, dest_ty, inst), + // // *[N]T to [*c]T + // .C => return ip.coerceCompatiblePtrs(gpa, arena, dest_ty, inst), + // // *[N]T to [*]T + // .Many => return ip.coerceCompatiblePtrs(gpa, arena, dest_ty, inst), + // .One => {}, + // } + } + + // coercion from C pointer + if (ip.isCPointer(inst_ty)) src_c_ptr: { + + // TODO if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :src_c_ptr; + const src_elem_ty = ip.indexToKey(inst_ty).pointer_type.elem_type; + if (try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, src_elem_ty, dest_info.is_const, target) != .ok) { + break :src_c_ptr; + } + return try ip.getUnknown(gpa, dest_ty); + // return ip.coerceCompatiblePtrs(gpa, arena, dest_ty, inst); + } + + // cast from *T and [*]T to *anyopaque + // but don't do it if the source type is a double pointer + if (dest_info.elem_type == .anyopaque_type and inst_ty_key == .pointer_type) { + // TODO if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer; + const elem_ty = ip.indexToKey(inst_ty).pointer_type.elem_type; + const is_pointer = ip.zigTypeTag(elem_ty) == .Pointer; + if (is_pointer or ip.isPtrLikeOptional(elem_ty)) { + in_memory_result = .{ .double_ptr_to_anyopaque = .{ + .actual = inst_ty, + .wanted = dest_ty, + } }; + break :pointer; + } + return try ip.getUnknown(gpa, dest_ty); + // return ip.coerceCompatiblePtrs(gpa, arena, dest_ty, inst); + } + + return try ip.getUnknown(gpa, dest_ty); + }, + .Int, .ComptimeInt => switch (ip.zigTypeTag(inst_ty)) { + .Float, .ComptimeFloat => return try ip.getUnknown(gpa, dest_ty), + .Int, .ComptimeInt => { + if (try ip.intFitsInType(inst, dest_ty, target)) { + return try ip.coerceInt(gpa, dest_ty, inst); + } else { + err_msg.* = .{ .integer_out_of_range = .{ + .dest_ty = dest_ty, + .actual = inst, + } }; + return .none; + } + }, + else => {}, + }, + .Float, .ComptimeFloat => return try ip.getUnknown(gpa, dest_ty), + .Enum => return try ip.getUnknown(gpa, dest_ty), + .ErrorUnion => return try ip.getUnknown(gpa, dest_ty), + .Union => return try ip.getUnknown(gpa, dest_ty), + .Array => switch (ip.zigTypeTag(inst_ty)) { + .Vector => return try ip.getUnknown(gpa, dest_ty), + .Struct => { + if (inst_ty == Index.empty_struct_type) { + const len = ip.indexToKey(dest_ty).array_type.len; + if (len != 0) { + err_msg.* = .{ .wrong_array_elem_count = .{ + .expected = @intCast(len), + .actual = 0, + } }; + return .none; + } + // TODO + return try ip.getUnknown(gpa, dest_ty); + } + return try ip.getUnknown(gpa, dest_ty); + }, + else => {}, + }, + .Vector => return try ip.getUnknown(gpa, dest_ty), + .Struct => return try ip.getUnknown(gpa, dest_ty), + else => {}, + } + + return .none; +} + +fn intFitsInType( + ip: *InternPool, + val: Index, + ty: Index, + target: std.Target, +) Allocator.Error!bool { + if (ty == .comptime_int_type) return true; + const info = ip.intInfo(ty, target); + + switch (ip.indexToKey(val)) { + .undefined_value, .unknown_value => return true, + inline .int_i64_value, .int_u64_value => |value| { + var buffer: [std.math.big.int.calcTwosCompLimbCount(64)]std.math.big.Limb = undefined; + var big_int = std.math.big.int.Mutable.init(&buffer, value.int); + return big_int.toConst().fitsInTwosComp(info.signedness, info.bits); + }, + .int_big_value => |int| return int.int.fitsInTwosComp(info.signedness, info.bits), + else => unreachable, + } +} + +fn coerceInt( + ip: *InternPool, + gpa: Allocator, + dest_ty: Index, + val: Index, +) Allocator.Error!Index { + switch (ip.indexToKey(val)) { + .int_i64_value => |int| return try ip.get(gpa, .{ .int_i64_value = .{ .int = int.int, .ty = dest_ty } }), + .int_u64_value => |int| return try ip.get(gpa, .{ .int_u64_value = .{ .int = int.int, .ty = dest_ty } }), + .int_big_value => |int| return try ip.get(gpa, .{ .int_big_value = .{ .int = int.int, .ty = dest_ty } }), + .undefined_value => |info| return try ip.getUndefined(gpa, info.ty), + .unknown_value => |info| return try ip.getUnknown(gpa, info.ty), + else => unreachable, + } } pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, target: std.Target) Allocator.Error!Index { + if (std.debug.runtime_safety) { + for (types) |ty| { + assert(ip.isType(ty)); + } + } + switch (types.len) { 0 => return Index.noreturn_type, 1 => return types[0], @@ -1847,8 +1355,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t var any_are_null = false; var seen_const = false; var convert_to_slice = false; - var chosen_i: usize = 0; - for (types[1..], 1..) |candidate, candidate_i| { + for (types[1..]) |candidate| { if (candidate == chosen) continue; const candidate_key: Key = ip.indexToKey(candidate); @@ -1861,7 +1368,6 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t } if ((try ip.coerceInMemoryAllowed(gpa, arena, candidate, chosen, true, target)) == .ok) { chosen = candidate; - chosen_i = candidate_i; continue; } @@ -1870,15 +1376,13 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t .f16, .f32, .f64, .f80, .f128 => switch (chosen_key) { .simple_type => |chosen_simple| switch (chosen_simple) { .f16, .f32, .f64, .f80, .f128 => { - if (chosen_key.floatBits(target) < candidate_key.floatBits(target)) { + if (ip.floatBits(chosen, target) < ip.floatBits(candidate, target)) { chosen = candidate; - chosen_i = candidate_i; } continue; }, .comptime_int, .comptime_float => { chosen = candidate; - chosen_i = candidate_i; continue; }, else => {}, @@ -1913,26 +1417,23 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t .c_ulonglong, .c_longdouble, => { - const chosen_bits = chosen_key.intInfo(target, ip.*).bits; - const candidate_bits = candidate_key.intInfo(target, ip.*).bits; + const chosen_bits = ip.intInfo(chosen, target).bits; + const candidate_bits = ip.intInfo(candidate, target).bits; if (chosen_bits < candidate_bits) { chosen = candidate; - chosen_i = candidate_i; } continue; }, .comptime_int => { chosen = candidate; - chosen_i = candidate_i; continue; }, else => {}, }, .int_type => |chosen_info| { - if (chosen_info.bits < candidate_key.intInfo(target, ip.*).bits) { + if (chosen_info.bits < ip.intInfo(candidate, target).bits) { chosen = candidate; - chosen_i = candidate_i; } continue; }, @@ -1975,7 +1476,6 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t .f16, .f32, .f64, .f80, .f128 => continue, .comptime_int => { chosen = candidate; - chosen_i = candidate_i; continue; }, .comptime_float => unreachable, @@ -2004,18 +1504,16 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t .c_ulonglong, .c_longdouble, => { - const chosen_bits = chosen_key.intInfo(target, ip.*).bits; - const candidate_bits = candidate_key.intInfo(target, ip.*).bits; + const chosen_bits = ip.intInfo(chosen, target).bits; + const candidate_bits = ip.intInfo(candidate, target).bits; if (chosen_bits < candidate_bits) { chosen = candidate; - chosen_i = candidate_i; } continue; }, .comptime_int => { chosen = candidate; - chosen_i = candidate_i; continue; }, else => {}, @@ -2023,7 +1521,6 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t .int_type => |chosen_info| { if (chosen_info.bits < candidate_info.bits) { chosen = candidate; - chosen_i = candidate_i; } continue; }, @@ -2035,7 +1532,6 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t .comptime_int => { if (candidate_info.size == .C) { chosen = candidate; - chosen_i = candidate_i; continue; } }, @@ -2056,7 +1552,6 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` convert_to_slice = false; chosen = candidate; - chosen_i = candidate_i; continue; } if (candidate_info.size == .One and @@ -2089,7 +1584,6 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t if (cand_ok) { convert_to_slice = true; chosen = candidate; - chosen_i = candidate_i; continue; } @@ -2111,7 +1605,6 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t if (cand_ok) { if (!chosen_ok or chosen_info.size != .C) { chosen = candidate; - chosen_i = candidate_i; } continue; } else { @@ -2127,7 +1620,6 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t .int_type => { if (candidate_info.size == .C) { chosen = candidate; - chosen_i = candidate_i; continue; } }, @@ -2163,16 +1655,13 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t } } }, - .function_type => |chosen_info| { - if (candidate_info.is_const) { - const candidate_elem_key = ip.indexToKey(candidate_info.elem_type); - if (candidate_elem_key == .function_type and - .ok == try ip.coerceInMemoryAllowedFns(gpa, arena, chosen_info, candidate_elem_key.function_type, target)) - { - chosen = candidate; - chosen_i = candidate_i; - continue; - } + .function_type => { + if (candidate_info.is_const and + ip.zigTypeTag(candidate_info.elem_type) == .Fn and + .ok == try ip.coerceInMemoryAllowedFns(gpa, arena, chosen, candidate_info.elem_type, target)) + { + chosen = candidate; + continue; } }, else => {}, @@ -2183,21 +1672,19 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t }, .optional_type => |candidate_info| { if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen, candidate_info.payload_type, true, target)) == .ok) { - seen_const = seen_const or ip.indexToKey(candidate_info.payload_type).isConstPtr(); + seen_const = seen_const or ip.isConstPointer(candidate_info.payload_type); any_are_null = true; continue; } - seen_const = seen_const or chosen_key.isConstPtr(); + seen_const = seen_const or ip.isConstPointer(chosen); any_are_null = false; chosen = candidate; - chosen_i = candidate_i; continue; }, .vector_type => switch (chosen_key) { .array_type => { chosen = candidate; - chosen_i = candidate_i; continue; }, else => {}, @@ -2211,13 +1698,11 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t .undefined_type, => { chosen = candidate; - chosen_i = candidate_i; continue; }, .null_type => { any_are_null = true; chosen = candidate; - chosen_i = candidate_i; continue; }, else => {}, @@ -2229,7 +1714,6 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t if ((try ip.coerceInMemoryAllowed(gpa, arena, candidate, chosen_info.payload_type, true, target)) == .ok) { any_are_null = true; chosen = candidate; - chosen_i = candidate_i; continue; } }, @@ -2245,16 +1729,14 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t } if (chosen == .none) return chosen; - const chosen_key = ip.indexToKey(chosen); if (convert_to_slice) { // turn *[N]T => []T - const chosen_elem_key = ip.indexToKey(chosen_key.pointer_type.elem_type); - var info = chosen_key.pointer_type; - info.sentinel = chosen_elem_key.sentinel(); + var info = ip.indexToKey(chosen).pointer_type; + info.sentinel = ip.sentinel(info.elem_type); info.size = .Slice; - info.is_const = seen_const or chosen_elem_key.isConstPtr(); - info.elem_type = chosen_elem_key.elemType2(); + info.is_const = seen_const or ip.isConstPointer(info.elem_type); + info.elem_type = ip.elemType(info.elem_type); const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty; @@ -2267,7 +1749,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t if (seen_const) { // turn []T => []const T - switch (chosen_key) { + switch (ip.indexToKey(chosen)) { .error_union_type => |error_union_info| { var info: Pointer = ip.indexToKey(error_union_info.payload_type).pointer_type; info.is_const = true; @@ -2297,7 +1779,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t } if (any_are_null) { - const opt_ty = switch (chosen_key) { + const opt_ty = switch (ip.indexToKey(chosen)) { .simple_type => |simple| switch (simple) { .null_type => chosen, else => try ip.get(gpa, .{ .optional_type = .{ .payload_type = chosen } }), @@ -2328,7 +1810,7 @@ const InMemoryCoercionResult = union(enum) { optional_shape: Pair, optional_child: PairAndChild, from_anyerror, - missing_error: []const Index, + missing_error: []const SPString, /// true if wanted is var args fn_var_args: bool, /// true if wanted is generic @@ -2420,7 +1902,7 @@ const InMemoryCoercionResult = union(enum) { wanted_offset: u16, }; - fn dupe(child: *const InMemoryCoercionResult, arena: Allocator) !*InMemoryCoercionResult { + fn dupe(child: *const InMemoryCoercionResult, arena: Allocator) Allocator.Error!*InMemoryCoercionResult { const res = try arena.create(InMemoryCoercionResult); res.* = child.*; return res; @@ -2443,15 +1925,16 @@ fn coerceInMemoryAllowed( target: std.Target, ) Allocator.Error!InMemoryCoercionResult { if (dest_ty == src_ty) return .ok; - if (dest_ty == .unknown_type or src_ty == .unknown_type) return .ok; + if (ip.isUnknown(dest_ty) or ip.isUnknown(src_ty)) return .ok; + + assert(ip.isType(dest_ty)); + assert(ip.isType(src_ty)); const dest_key = ip.indexToKey(dest_ty); const src_key = ip.indexToKey(src_ty); - assert(dest_key.typeOf() == .type_type); - assert(src_key.typeOf() == .type_type); - const dest_tag = dest_key.zigTypeTag(); - const src_tag = src_key.zigTypeTag(); + const dest_tag = ip.zigTypeTag(dest_ty); + const src_tag = ip.zigTypeTag(src_ty); if (dest_tag != src_tag) { return InMemoryCoercionResult{ .no_match = .{ @@ -2462,8 +1945,8 @@ fn coerceInMemoryAllowed( switch (dest_tag) { .Int => { - const dest_info = dest_key.intInfo(target, ip.*); - const src_info = src_key.intInfo(target, ip.*); + const dest_info = ip.intInfo(dest_ty, target); + const src_info = ip.intInfo(src_ty, target); if (dest_info.signedness == src_info.signedness and dest_info.bits == src_info.bits) return .ok; @@ -2482,8 +1965,8 @@ fn coerceInMemoryAllowed( return .ok; }, .Float => { - const dest_bits = dest_key.floatBits(target); - const src_bits = src_key.floatBits(target); + const dest_bits = ip.floatBits(dest_ty, target); + const src_bits = ip.floatBits(src_ty, target); if (dest_bits == src_bits) return .ok; return InMemoryCoercionResult{ .no_match = .{ .actual = dest_ty, @@ -2491,16 +1974,14 @@ fn coerceInMemoryAllowed( } }; }, .Pointer => { - return try ip.coerceInMemoryAllowedPtrs(gpa, arena, dest_ty, src_ty, dest_key, src_key, dest_is_const, target); + return try ip.coerceInMemoryAllowedPtrs(gpa, arena, dest_ty, src_ty, dest_is_const, target); }, .Optional => { // Pointer-like Optionals - const maybe_dest_ptr_ty = try ip.optionalPtrTy(dest_key); - const maybe_src_ptr_ty = try ip.optionalPtrTy(src_key); + const maybe_dest_ptr_ty = ip.optionalPtrTy(dest_ty); + const maybe_src_ptr_ty = ip.optionalPtrTy(src_ty); if (maybe_dest_ptr_ty != .none and maybe_src_ptr_ty != .none) { - const dest_ptr_info = ip.indexToKey(maybe_dest_ptr_ty); - const src_ptr_info = ip.indexToKey(maybe_src_ptr_ty); - return try ip.coerceInMemoryAllowedPtrs(gpa, arena, dest_ty, src_ty, dest_ptr_info, src_ptr_info, dest_is_const, target); + return try ip.coerceInMemoryAllowedPtrs(gpa, arena, dest_ty, src_ty, dest_is_const, target); } if (maybe_dest_ptr_ty != maybe_src_ptr_ty) { @@ -2525,7 +2006,7 @@ fn coerceInMemoryAllowed( return .ok; }, .Fn => { - return try ip.coerceInMemoryAllowedFns(gpa, arena, dest_key.function_type, src_key.function_type, target); + return try ip.coerceInMemoryAllowedFns(gpa, arena, dest_ty, src_ty, target); }, .ErrorUnion => { const dest_payload = dest_key.error_union_type.payload_type; @@ -2540,7 +2021,8 @@ fn coerceInMemoryAllowed( } const dest_set = dest_key.error_union_type.error_set_type; const src_set = src_key.error_union_type.error_set_type; - return try ip.coerceInMemoryAllowed(gpa, arena, dest_set, src_set, dest_is_const, target); + if (dest_set == .none or src_set == .none) return .ok; + return try ip.coerceInMemoryAllowedErrorSets(gpa, arena, dest_set, src_set); }, .ErrorSet => { return try ip.coerceInMemoryAllowedErrorSets(gpa, arena, dest_ty, src_ty); @@ -2618,22 +2100,17 @@ fn coerceInMemoryAllowedErrorSets( src_ty: Index, ) !InMemoryCoercionResult { if (dest_ty == src_ty) return .ok; - - const dest_key = ip.indexToKey(dest_ty); - assert(dest_key.zigTypeTag() == .ErrorSet); - if (dest_ty == .anyerror_type) return .ok; - - const src_key = ip.indexToKey(src_ty); - assert(src_key.zigTypeTag() == .ErrorSet); - if (src_ty == .anyerror_type) return .from_anyerror; - var missing_error_buf = std.ArrayListUnmanaged(Index){}; + const dest_set = ip.indexToKey(dest_ty).error_set_type; + const src_set = ip.indexToKey(src_ty).error_set_type; + + var missing_error_buf = std.ArrayListUnmanaged(SPString){}; defer missing_error_buf.deinit(gpa); - for (src_key.error_set_type.names) |name| { - if (std.mem.indexOfScalar(Index, dest_key.error_set_type.names, name) == null) { + for (src_set.names) |name| { + if (std.mem.indexOfScalar(SPString, dest_set.names, name) == null) { try missing_error_buf.append(gpa, name); } } @@ -2641,7 +2118,7 @@ fn coerceInMemoryAllowedErrorSets( if (missing_error_buf.items.len == 0) return .ok; return InMemoryCoercionResult{ - .missing_error = try arena.dupe(Index, missing_error_buf.items), + .missing_error = try arena.dupe(SPString, missing_error_buf.items), }; } @@ -2649,10 +2126,13 @@ fn coerceInMemoryAllowedFns( ip: *InternPool, gpa: Allocator, arena: Allocator, - dest_info: Function, - src_info: Function, + dest_ty: Index, + src_ty: Index, target: std.Target, ) Allocator.Error!InMemoryCoercionResult { + const dest_info = ip.indexToKey(dest_ty).function_type; + const src_info = ip.indexToKey(src_ty).function_type; + if (dest_info.is_var_args != src_info.is_var_args) { return InMemoryCoercionResult{ .fn_var_args = dest_info.is_var_args }; } @@ -2731,13 +2211,11 @@ fn coerceInMemoryAllowedPtrs( arena: Allocator, dest_ty: Index, src_ty: Index, - dest_ptr_info: Key, - src_ptr_info: Key, dest_is_const: bool, target: std.Target, ) Allocator.Error!InMemoryCoercionResult { - const dest_info = dest_ptr_info.pointer_type; - const src_info = src_ptr_info.pointer_type; + const dest_info = ip.indexToKey(dest_ty).pointer_type; + const src_info = ip.indexToKey(src_ty).pointer_type; const ok_ptr_size = src_info.size == dest_info.size or src_info.size == .C or dest_info.size == .C; @@ -2777,8 +2255,8 @@ fn coerceInMemoryAllowedPtrs( } }; } - const dest_allow_zero = dest_ptr_info.ptrAllowsZero(ip); - const src_allow_zero = src_ptr_info.ptrAllowsZero(ip); + const dest_allow_zero = ip.ptrAllowsZero(dest_ty); + const src_allow_zero = ip.ptrAllowsZero(src_ty); const ok_allows_zero = (dest_allow_zero and (src_allow_zero or dest_is_const)) or (!dest_allow_zero and !src_allow_zero); if (!ok_allows_zero) { @@ -2842,29 +2320,21 @@ fn coerceInMemoryAllowedPtrs( return .ok; } -fn optionalPtrTy( - ip: InternPool, - ty: Key, -) !Index { - switch (ty) { - .optional_type => |optional_info| { - const child_type = optional_info.payload_type; - const child_key = ip.indexToKey(child_type); - - if (child_key != .pointer_type) return Index.none; - const child_ptr_key = child_key.pointer_type; - - switch (child_ptr_key.size) { +fn optionalPtrTy(ip: *const InternPool, ty: Index) Index { + switch (ip.indexToKey(ty)) { + .optional_type => |optional_info| switch (ip.indexToKey(optional_info.payload_type)) { + .pointer_type => |pointer_info| switch (pointer_info.size) { .Slice, .C => return Index.none, .Many, .One => { - if (child_ptr_key.is_allowzero) return Index.none; + if (pointer_info.is_allowzero) return Index.none; // optionals of zero sized types behave like bools, not pointers - if (child_key.onePossibleValue(ip) != Index.none) return Index.none; + if (ip.onePossibleValue(optional_info.payload_type) != Index.none) return Index.none; - return child_type; + return optional_info.payload_type; }, - } + }, + else => return .none, }, else => unreachable, } @@ -2878,6 +2348,1122 @@ inline fn panicOrElse(message: []const u8, value: anytype) @TypeOf(value) { return value; } +// --------------------------------------------- +// HELPER FUNCTIONS +// --------------------------------------------- + +pub fn zigTypeTag(ip: *const InternPool, index: Index) std.builtin.TypeId { + return switch (ip.items.items(.tag)[@intFromEnum(index)]) { + .simple_type => switch (@as(SimpleType, @enumFromInt(ip.items.items(.data)[@intFromEnum(index)]))) { + .f16, + .f32, + .f64, + .f80, + .f128, + .c_longdouble, + => .Float, + + .usize, + .isize, + .c_char, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + => .Int, + + .comptime_int => .ComptimeInt, + .comptime_float => .ComptimeFloat, + + .anyopaque => .Opaque, + .bool => .Bool, + .void => .Void, + .type => .Type, + .anyerror => .ErrorSet, + .noreturn => .NoReturn, + .anyframe_type => .AnyFrame, + .empty_struct_type => .Struct, + .null_type => .Null, + .undefined_type => .Undefined, + .enum_literal_type => .EnumLiteral, + + .atomic_order => .Enum, + .atomic_rmw_op => .Enum, + .calling_convention => .Enum, + .address_space => .Enum, + .float_mode => .Enum, + .reduce_op => .Enum, + .modifier => .Enum, + .prefetch_options => .Struct, + .export_options => .Struct, + .extern_options => .Struct, + .type_info => .Union, + + .unknown => unreachable, + .generic_poison => unreachable, + }, + .type_int_signed, .type_int_unsigned => .Int, + .type_pointer => .Pointer, + .type_array => .Array, + .type_struct => .Struct, + .type_optional => .Optional, + .type_error_union => .ErrorUnion, + .type_error_set => .ErrorSet, + .type_enum => .Enum, + .type_function => .Fn, + .type_union => .Union, + .type_tuple => .Struct, + .type_vector => .Vector, + .type_anyframe => .AnyFrame, + + .simple_value, + .int_u64, + .int_i64, + .int_big_positive, + .int_big_negative, + .float_f16, + .float_f32, + .float_f64, + .float_f80, + .float_f128, + .float_comptime, + .optional_value, + .aggregate, + .slice, + .union_value, + .null_value, + .error_value, + .undefined_value, + .unknown_value, + => unreachable, + }; +} + +pub fn typeOf(ip: *const InternPool, index: Index) Index { + const data = ip.items.items(.data)[@intFromEnum(index)]; + return switch (ip.items.items(.tag)[@intFromEnum(index)]) { + .simple_value => switch (@as(SimpleValue, @enumFromInt(data))) { + .undefined_value => .undefined_type, + .void_value => .void_type, + .unreachable_value => .noreturn_type, + .null_value => .null_type, + .bool_true => .bool_type, + .bool_false => .bool_type, + .the_only_possible_value => unreachable, + .generic_poison => .generic_poison_type, + }, + .simple_type, + .type_int_signed, + .type_int_unsigned, + .type_pointer, + .type_array, + .type_struct, + .type_optional, + .type_error_union, + .type_error_set, + .type_enum, + .type_function, + .type_union, + .type_tuple, + .type_vector, + .type_anyframe, + => .type_type, + + .float_f16 => .f16_type, + .float_f32 => .f32_type, + .float_f64 => .f64_type, + .float_f80 => .f80_type, + .float_f128 => .f128_type, + .float_comptime => .comptime_float_type, + + .int_u64 => ip.extraData(U64Value, data).ty, + .int_i64 => ip.extraData(I64Value, data).ty, + .int_big_positive, .int_big_negative => ip.extraData(BigIntInternal, data).ty, + .optional_value => ip.extraData(OptionalValue, data).ty, + .aggregate => ip.extraData(Aggregate, data).ty, + .slice => ip.extraData(Slice, data).ty, + .union_value => ip.extraData(UnionValue, data).ty, + .error_value => ip.extraData(ErrorValue, data).ty, + + .null_value, + .undefined_value, + .unknown_value, + => @enumFromInt(ip.items.items(.data)[@intFromEnum(index)]), + }; +} + +pub fn isType(ip: *const InternPool, ty: Index) bool { + return switch (ip.items.items(.tag)[@intFromEnum(ty)]) { + .simple_type, + .type_int_signed, + .type_int_unsigned, + .type_pointer, + .type_array, + .type_struct, + .type_optional, + .type_error_union, + .type_error_set, + .type_enum, + .type_function, + .type_union, + .type_tuple, + .type_vector, + .type_anyframe, + => true, + + .simple_value, + .float_f16, + .float_f32, + .float_f64, + .float_f80, + .float_f128, + .float_comptime, + .int_u64, + .int_i64, + .int_big_positive, + .int_big_negative, + .optional_value, + .aggregate, + .slice, + .union_value, + .error_value, + .null_value, + .undefined_value, + => false, + .unknown_value => .unknown_type == @as(Index, @enumFromInt(ip.items.items(.data)[@intFromEnum(ty)])), + }; +} + +pub fn isUnknown(ip: *const InternPool, index: Index) bool { + switch (index) { + .unknown_type => return true, + .unknown_unknown => return true, + else => switch (ip.items.items(.tag)[@intFromEnum(index)]) { + .unknown_value => return true, + else => return false, + }, + } +} + +pub fn isUnknownDeep(ip: *const InternPool, gpa: std.mem.Allocator, index: Index) Allocator.Error!bool { + var set = std.AutoHashMap(Index, void).init(gpa); + defer set.deinit(); + return try ip.isUnknownDeepInternal(index, &set); +} + +fn isUnknownDeepInternal(ip: *const InternPool, index: Index, set: *std.AutoHashMap(Index, void)) Allocator.Error!bool { + const gop = try set.getOrPut(index); + if (gop.found_existing) return false; + return switch (ip.indexToKey(index)) { + .simple_type => |simple| switch (simple) { + .unknown => true, + else => false, + }, + .simple_value => false, + + .int_type => false, + .pointer_type => |pointer_info| { + if (try ip.isUnknownDeepInternal(pointer_info.elem_type, set)) return true; + if (pointer_info.sentinel != .none and try ip.isUnknownDeepInternal(pointer_info.sentinel, set)) return true; + return false; + }, + .array_type => |array_info| { + if (try ip.isUnknownDeepInternal(array_info.child, set)) return true; + if (array_info.sentinel != .none and try ip.isUnknownDeepInternal(array_info.sentinel, set)) return true; + return false; + }, + .struct_type => |struct_index| { + const struct_info = ip.getStruct(struct_index); + for (struct_info.fields.values()) |field| { + if (try ip.isUnknownDeepInternal(field.ty, set)) return true; + if (field.default_value != .none and try ip.isUnknownDeepInternal(field.default_value, set)) return true; + } + // TODO namespace + return false; + }, + .optional_type => |optional_info| try ip.isUnknownDeepInternal(optional_info.payload_type, set), + .error_union_type => |error_union_info| try ip.isUnknownDeepInternal(error_union_info.payload_type, set), + .error_set_type => false, + .enum_type => |enum_index| { + const enum_info = ip.getEnum(enum_index); + for (enum_info.values.keys()) |val| { + if (try ip.isUnknownDeepInternal(val, set)) return true; + } + // TODO namespace + return false; + }, + .function_type => |function_info| { + for (function_info.args) |arg_ty| { + if (try ip.isUnknownDeepInternal(arg_ty, set)) return true; + } + if (try ip.isUnknownDeepInternal(function_info.return_type, set)) return true; + return false; + }, + .union_type => |union_index| { + const union_info = ip.getUnion(union_index); + for (union_info.fields.values()) |field| { + if (try ip.isUnknownDeepInternal(field.ty, set)) return true; + } + // TODO namespace + return false; + }, + .tuple_type => |tuple_info| { + for (tuple_info.types, tuple_info.values) |ty, val| { + if (try ip.isUnknownDeepInternal(ty, set)) return true; + if (try ip.isUnknownDeepInternal(val, set)) return true; + } + return false; + }, + .vector_type => |vector_info| try ip.isUnknownDeepInternal(vector_info.child, set), + .anyframe_type => |anyframe_info| try ip.isUnknownDeepInternal(anyframe_info.child, set), + + .int_u64_value, + .int_i64_value, + .int_big_value, + .float_16_value, + .float_32_value, + .float_64_value, + .float_80_value, + .float_128_value, + .float_comptime_value, + => false, + + .optional_value, + .slice, + .aggregate, + .union_value, + .error_value, + .null_value, + .undefined_value, + => try ip.isUnknownDeepInternal(ip.typeOf(index), set), + .unknown_value => true, + }; +} + +/// Asserts the type is an integer, enum, error set, packed struct, or vector of one of them. +pub fn intInfo(ip: *const InternPool, ty: Index, target: std.Target) std.builtin.Type.Int { + var index = ty; + while (true) switch (index) { + .u1_type => return .{ .signedness = .unsigned, .bits = 1 }, + .u8_type => return .{ .signedness = .unsigned, .bits = 8 }, + .i8_type => return .{ .signedness = .signed, .bits = 8 }, + .u16_type => return .{ .signedness = .unsigned, .bits = 16 }, + .i16_type => return .{ .signedness = .signed, .bits = 16 }, + .u29_type => return .{ .signedness = .unsigned, .bits = 29 }, + .u32_type => return .{ .signedness = .unsigned, .bits = 32 }, + .i32_type => return .{ .signedness = .signed, .bits = 32 }, + .u64_type => return .{ .signedness = .unsigned, .bits = 64 }, + .i64_type => return .{ .signedness = .signed, .bits = 64 }, + .u128_type => return .{ .signedness = .unsigned, .bits = 128 }, + .i128_type => return .{ .signedness = .signed, .bits = 128 }, + + .usize_type => return .{ .signedness = .unsigned, .bits = target.ptrBitWidth() }, + .isize_type => return .{ .signedness = .signed, .bits = target.ptrBitWidth() }, + + .c_char_type => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.char) }, + .c_short_type => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.short) }, + .c_ushort_type => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ushort) }, + .c_int_type => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.int) }, + .c_uint_type => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.uint) }, + .c_long_type => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.long) }, + .c_ulong_type => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ulong) }, + .c_longlong_type => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.longlong) }, + .c_ulonglong_type => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ulonglong) }, + .c_longdouble_type => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.longdouble) }, + + // TODO revisit this when error sets support custom int types (comment taken from zig codebase) + .anyerror_type => return .{ .signedness = .unsigned, .bits = 16 }, + + else => switch (ip.indexToKey(index)) { + .int_type => |int_info| return int_info, + .enum_type => |enum_index| { + const enum_info = ip.getEnum(enum_index); + index = enum_info.tag_type; + }, + .struct_type => |struct_index| { + const struct_info = ip.getStruct(struct_index); + assert(struct_info.layout == .Packed); + index = struct_info.backing_int_ty; + }, + // TODO revisit this when error sets support custom int types (comment taken from zig codebase) + .error_set_type => return .{ .signedness = .unsigned, .bits = 16 }, + .vector_type => |vector_info| { + assert(vector_info.len == 1); + index = vector_info.child; + }, + else => unreachable, + }, + }; +} + +/// Asserts the type is a fixed-size float or comptime_float. +/// Returns 128 for comptime_float types. +pub fn floatBits(ip: *const InternPool, ty: Index, target: std.Target) u16 { + _ = ip; + return switch (ty) { + .f16_type => 16, + .f32_type => 32, + .f64_type => 64, + .f80_type => 80, + .f128_type, .comptime_float_type => 128, + .c_longdouble_type => target.c_type_bit_size(.longdouble), + + else => unreachable, + }; +} + +pub fn isFloat(ip: *const InternPool, ty: Index) bool { + _ = ip; + return switch (ty) { + .c_longdouble_type, + .f16_type, + .f32_type, + .f64_type, + .f80_type, + .f128_type, + => true, + else => false, + }; +} + +pub fn isSinglePointer(ip: *const InternPool, ty: Index) bool { + return switch (ip.indexToKey(ty)) { + .pointer_type => |pointer_info| pointer_info.size == .One, + else => false, + }; +} + +pub fn isManyPointer(ip: *const InternPool, ty: Index) bool { + return switch (ip.indexToKey(ty)) { + .pointer_type => |pointer_info| pointer_info.size == .Many, + else => false, + }; +} + +pub fn isSlicePointer(ip: *const InternPool, ty: Index) bool { + return switch (ip.indexToKey(ty)) { + .pointer_type => |pointer_info| pointer_info.size == .Slice, + else => false, + }; +} + +pub fn isCPointer(ip: *const InternPool, ty: Index) bool { + return switch (ip.indexToKey(ty)) { + .pointer_type => |pointer_info| pointer_info.size == .C, + else => false, + }; +} + +pub fn isConstPointer(ip: *const InternPool, ty: Index) bool { + return switch (ip.indexToKey(ty)) { + .pointer_type => |pointer_info| pointer_info.is_const, + else => false, + }; +} + +/// For pointer-like optionals, returns true, otherwise returns the allowzero property +/// of pointers. +pub fn ptrAllowsZero(ip: *const InternPool, ty: Index) bool { + if (ip.indexToKey(ty).pointer_type.is_allowzero) return true; + return ip.isPtrLikeOptional(ty); +} + +/// Returns true if the type is optional and would be lowered to a single pointer +/// address value, using 0 for null. Note that this returns true for C pointers. +pub fn isPtrLikeOptional(ip: *const InternPool, ty: Index) bool { + return switch (ip.indexToKey(ty)) { + .optional_type => |optional_info| switch (ip.indexToKey(optional_info.payload_type)) { + .pointer_type => |pointer_info| switch (pointer_info.size) { + .Slice, .C => false, + .Many, .One => !pointer_info.is_allowzero, + }, + else => false, + }, + .pointer_type => |pointer_info| pointer_info.size == .C, + else => false, + }; +} + +pub fn isPtrAtRuntime(ip: *const InternPool, ty: Index) bool { + return switch (ip.indexToKey(ty)) { + .pointer_type => |pointer_info| pointer_info.size != .Slice, + .optional_type => |optional_info| switch (ip.indexToKey(optional_info.payload_type)) { + .pointer_type => |pointer_info| switch (pointer_info.size) { + .Slice, .C => false, + .Many, .One => !pointer_info.is_allowzero, + }, + else => false, + }, + else => false, + }; +} + +/// For *T, returns T. +/// For [*]T, returns T. +/// For []T, returns T. +/// For [*c]T, returns T. +/// For [N]T, returns T. +/// For ?T, returns T. +/// For @vector(T, _), returns T. +/// For anyframe->T, returns T. +pub fn childType(ip: *const InternPool, ty: Index) Index { + return switch (ip.indexToKey(ty)) { + .pointer_type => |pointer_info| pointer_info.elem_type, + .array_type => |array_info| array_info.child, + .optional_type => |optional_info| optional_info.payload_type, + .vector_type => |vector_info| vector_info.child, + .anyframe_type => |anyframe_info| anyframe_info.child, + else => unreachable, + }; +} + +/// For *[N]T, returns T. +/// For ?*T, returns T. +/// For ?*[N]T, returns T. +/// For ?[*]T, returns T. +/// For *T, returns T. +/// For [*]T, returns T. +/// For []T, returns T. +/// For [*c]T, returns T. +/// For [N]T, returns T. +/// For ?T, returns T. +/// For @vector(T, _), returns T. +/// For anyframe->T, returns T. +pub fn elemType(ip: *const InternPool, ty: Index) Index { + return switch (ip.indexToKey(ty)) { + .pointer_type => |pointer_info| switch (pointer_info.size) { + .One => ip.childType(pointer_info.elem_type), + .Many, .C, .Slice => pointer_info.elem_type, + }, + .optional_type => |optional_info| ip.childType(optional_info.payload_type), + .array_type => |array_info| array_info.child, + .vector_type => |vector_info| vector_info.child, + .anyframe_type => |anyframe_info| anyframe_info.child, + else => unreachable, + }; +} + +pub fn errorSetMerge(ip: *InternPool, gpa: std.mem.Allocator, a_ty: Index, b_ty: Index) Allocator.Error!Index { + // Anything merged with anyerror is anyerror. + if (a_ty == .anyerror_type or b_ty == .anyerror_type) { + return .anyerror_type; + } + + if (a_ty == b_ty) return a_ty; + + const a_names = ip.indexToKey(a_ty).error_set_type.names; + const b_names = ip.indexToKey(b_ty).error_set_type.names; + + var set = std.AutoArrayHashMapUnmanaged(SPString, void){}; + defer set.deinit(gpa); + + try set.ensureTotalCapacity(gpa, a_names.len + b_names.len); + + for (a_names) |name| set.putAssumeCapacityNoClobber(name, {}); + for (b_names) |name| set.putAssumeCapacity(name, {}); + + return try ip.get(gpa, .{ + .error_set_type = .{ .owner_decl = .none, .names = set.keys() }, + }); +} + +/// Asserts the type is an array, pointer or vector. +pub fn sentinel(ip: *const InternPool, ty: Index) Index { + return switch (ip.indexToKey(ty)) { + .pointer_type => |pointer_info| pointer_info.sentinel, + .array_type => |array_info| array_info.sentinel, + .vector_type => .none, + else => unreachable, + }; +} + +pub fn getNamespace(ip: *const InternPool, ty: Index) NamespaceIndex { + return switch (ip.indexToKey(ty)) { + .struct_type => |struct_index| ip.getStruct(struct_index).namespace, + .enum_type => |enum_index| ip.getEnum(enum_index).namespace, + .union_type => |union_index| ip.getUnion(union_index).namespace, + else => .none, + }; +} + +pub fn onePossibleValue(ip: *const InternPool, ty: Index) Index { + return switch (ip.indexToKey(ty)) { + .simple_type => |simple| switch (simple) { + .f16, + .f32, + .f64, + .f80, + .f128, + .usize, + .isize, + .c_char, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .anyopaque, + .bool, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .anyframe_type, + .enum_literal_type, + => Index.none, + + .empty_struct_type => Index.empty_aggregate, + .void => Index.void_value, + .noreturn => Index.unreachable_value, + .null_type => Index.null_value, + .undefined_type => Index.undefined_value, + + .atomic_order, + .atomic_rmw_op, + .calling_convention, + .address_space, + .float_mode, + .reduce_op, + .modifier, + .prefetch_options, + .export_options, + .extern_options, + .type_info, + => Index.none, + + .unknown => unreachable, + .generic_poison => unreachable, + }, + .int_type => |int_info| { + if (int_info.bits == 0) { + switch (int_info.signedness) { + .unsigned => return Index.zero_comptime_int, + .signed => return Index.zero_comptime_int, // do we need a signed zero? + } + } + return Index.none; + }, + .pointer_type => Index.none, + .array_type => |array_info| { + if (array_info.len == 0) return Index.empty_aggregate; + const maybe_one_possible_value = ip.onePossibleValue(array_info.child); + if (maybe_one_possible_value != .none) return maybe_one_possible_value; + + return Index.none; + }, + .struct_type => |struct_index| { + const struct_info = ip.getStruct(struct_index); + var field_it = struct_info.fields.iterator(); + while (field_it.next()) |entry| { + if (entry.value_ptr.is_comptime) continue; + if (ip.onePossibleValue(entry.value_ptr.ty) != Index.none) continue; + return Index.none; + } + return Index.empty_aggregate; + }, + .optional_type => |optional_info| { + if (optional_info.payload_type == Index.noreturn_type) { + return Index.null_value; + } + return Index.none; + }, + .error_union_type => Index.none, + .error_set_type => Index.none, + .enum_type => |enum_index| { + const enum_info = ip.getEnum(enum_index); + return switch (enum_info.fields.count()) { + 0 => Index.unreachable_value, + 1 => enum_info.values.keys()[0], + else => Index.none, + }; + }, + .function_type => Index.none, + .union_type => panicOrElse("TODO", Index.none), + .tuple_type => panicOrElse("TODO", Index.none), + .vector_type => |vector_info| { + if (vector_info.len == 0) { + return panicOrElse("TODO return empty array value", Index.the_only_possible_value); + } + return ip.onePossibleValue(vector_info.child); + }, + .anyframe_type => Index.none, + + .simple_value, + .int_u64_value, + .int_i64_value, + .int_big_value, + .float_16_value, + .float_32_value, + .float_64_value, + .float_80_value, + .float_128_value, + .float_comptime_value, + => unreachable, + + .optional_value, + .slice, + .aggregate, + .union_value, + .error_value, + .null_value, + .undefined_value, + .unknown_value, + => unreachable, + }; +} + +pub fn canHaveFields(ip: *const InternPool, ty: Index) bool { + return switch (ip.indexToKey(ty)) { + .simple_type => |simple| switch (simple) { + .type => true, // TODO + .unknown => true, + else => false, + }, + .array_type, + .struct_type, + .enum_type, + .union_type, + => true, + + .pointer_type, + .optional_type, + .int_type, + .error_union_type, + .error_set_type, + .function_type, + .tuple_type, + .vector_type, + .anyframe_type, + => false, + + .simple_value, + .int_u64_value, + .int_i64_value, + .int_big_value, + .float_16_value, + .float_32_value, + .float_64_value, + .float_80_value, + .float_128_value, + .float_comptime_value, + => unreachable, + + .optional_value, + .slice, + .aggregate, + .union_value, + .error_value, + .null_value, + .undefined_value, + .unknown_value, + => unreachable, + }; +} + +pub fn isIndexable(ip: *const InternPool, ty: Index) bool { + return switch (ip.indexToKey(ty)) { + .array_type, .vector_type => true, + .pointer_type => |pointer_info| switch (pointer_info.size) { + .Slice, .Many, .C => true, + .One => ip.indexToKey(pointer_info.elem_type) == .array_type, + }, + .tuple_type => true, + else => false, + }; +} + +pub fn isNull(ip: *const InternPool, val: Index) bool { + return switch (ip.indexToKey(val)) { + .simple_value => |simple| switch (simple) { + .null_value => true, + else => false, + }, + .null_value => true, + .optional_value => false, + else => false, + }; +} + +pub fn isZero(ip: *const InternPool, val: Index) bool { + return switch (ip.indexToKey(val)) { + .simple_value => |simple| switch (simple) { + .null_value => true, + .bool_true => false, + .bool_false => true, + .the_only_possible_value => true, + else => false, + }, + .int_u64_value => |int_value| int_value.int == 0, + .int_i64_value => |int_value| int_value.int == 0, + .int_big_value => |int_value| int_value.int.eqlZero(), + + .null_value => true, + .optional_value => false, + + else => false, + }; +} + +/// If the value fits in the given integer, return it, otherwise null. +pub fn toInt(ip: *const InternPool, val: Index, comptime T: type) !?T { + return switch (ip.indexToKey(val)) { + .simple_value => |simple| switch (simple) { + .null_value => 0, + .bool_true => 1, + .bool_false => 0, + .the_only_possible_value => 0, + else => null, + }, + .int_u64_value => |int_value| @intCast(int_value.int), + .int_i64_value => |int_value| @intCast(int_value.int), + .int_big_value => |int_value| int_value.int.to(T) catch null, + .null_value => 0, + else => null, + }; +} + +pub fn getNull(ip: *InternPool, gpa: Allocator, ty: Index) Allocator.Error!Index { + if (ty == .none) return Index.null_value; + assert(ip.isType(ty)); + return try ip.get(gpa, .{ .null_value = .{ .ty = ty } }); +} + +pub fn getUndefined(ip: *InternPool, gpa: Allocator, ty: Index) Allocator.Error!Index { + assert(ip.isType(ty)); + return try ip.get(gpa, .{ .undefined_value = .{ .ty = ty } }); +} + +pub fn getUnknown(ip: *InternPool, gpa: Allocator, ty: Index) Allocator.Error!Index { + assert(ip.isType(ty)); + if (ty == .type_type) return Index.unknown_type; + if (ty == .unknown_type) return Index.unknown_unknown; + return try ip.get(gpa, .{ .unknown_value = .{ .ty = ty } }); +} + +// --------------------------------------------- +// Print +// --------------------------------------------- + +const FormatContext = struct { + index: Index, + options: FormatOptions = .{}, + ip: *InternPool, +}; + +// TODO add options for controling how types show be formatted +pub const FormatOptions = struct { + debug: bool = false, +}; + +fn format( + ctx: FormatContext, + comptime fmt_str: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + if (fmt_str.len != 0) std.fmt.invalidFmtError(fmt_str, ctx.index); + if (ctx.options.debug and ctx.index == .none) { + return writer.writeAll(".none"); + } else { + try ctx.ip.print(ctx.index, writer, ctx.options); + } +} + +pub fn print(ip: *InternPool, index: Index, writer: anytype, options: FormatOptions) @TypeOf(writer).Error!void { + var tv = index; + const ty = ip.typeOf(tv); + while (true) { + if (options.debug and ty != .type_type) try writer.print("@as({},", .{ip.typeOf(tv).fmt(ip)}); + var child_options = options; + child_options.debug = false; + tv = try ip.printInternal(tv, writer, child_options) orelse break; + } + if (options.debug and ty != .type_type) try writer.writeByte(')'); +} + +fn printInternal(ip: *InternPool, ty: Index, writer: anytype, options: FormatOptions) @TypeOf(writer).Error!?Index { + switch (ip.indexToKey(ty)) { + .simple_type => |simple| switch (simple) { + .f16, + .f32, + .f64, + .f80, + .f128, + .usize, + .isize, + .c_char, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .anyopaque, + .bool, + .void, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .noreturn, + .anyframe_type, + => try writer.writeAll(@tagName(simple)), + + .null_type => try writer.writeAll("@TypeOf(null)"), + .undefined_type => try writer.writeAll("@TypeOf(undefined)"), + .empty_struct_type => try writer.writeAll("@TypeOf(.{})"), + .enum_literal_type => try writer.writeAll("@TypeOf(.enum_literal)"), + + .atomic_order => try writer.writeAll("std.builtin.AtomicOrder"), + .atomic_rmw_op => try writer.writeAll("std.builtin.AtomicRmwOp"), + .calling_convention => try writer.writeAll("std.builtin.CallingConvention"), + .address_space => try writer.writeAll("std.builtin.AddressSpace"), + .float_mode => try writer.writeAll("std.builtin.FloatMode"), + .reduce_op => try writer.writeAll("std.builtin.ReduceOp"), + .modifier => try writer.writeAll("std.builtin.CallModifier"), + .prefetch_options => try writer.writeAll("std.builtin.PrefetchOptions"), + .export_options => try writer.writeAll("std.builtin.ExportOptions"), + .extern_options => try writer.writeAll("std.builtin.ExternOptions"), + .type_info => try writer.writeAll("std.builtin.Type"), + .unknown => try writer.writeAll("(unknown type)"), + .generic_poison => try writer.writeAll("(generic poison)"), + }, + .int_type => |int_info| switch (int_info.signedness) { + .signed => try writer.print("i{}", .{int_info.bits}), + .unsigned => try writer.print("u{}", .{int_info.bits}), + }, + .pointer_type => |pointer_info| { + if (pointer_info.sentinel != Index.none) { + switch (pointer_info.size) { + .One, .C => unreachable, + .Many => try writer.print("[*:{}]", .{pointer_info.sentinel.fmt(ip)}), + .Slice => try writer.print("[:{}]", .{pointer_info.sentinel.fmt(ip)}), + } + } else switch (pointer_info.size) { + .One => try writer.writeAll("*"), + .Many => try writer.writeAll("[*]"), + .C => try writer.writeAll("[*c]"), + .Slice => try writer.writeAll("[]"), + } + + if (pointer_info.alignment != 0) { + try writer.print("align({d}", .{pointer_info.alignment}); + + if (pointer_info.bit_offset != 0 or pointer_info.host_size != 0) { + try writer.print(":{d}:{d}", .{ pointer_info.bit_offset, pointer_info.host_size }); + } + + try writer.writeAll(") "); + } + + if (pointer_info.address_space != .generic) { + try writer.print("addrspace(.{s}) ", .{@tagName(pointer_info.address_space)}); + } + + if (pointer_info.is_const) try writer.writeAll("const "); + if (pointer_info.is_volatile) try writer.writeAll("volatile "); + if (pointer_info.is_allowzero and pointer_info.size != .C) try writer.writeAll("allowzero "); + + return pointer_info.elem_type; + }, + .array_type => |array_info| { + try writer.print("[{d}", .{array_info.len}); + if (array_info.sentinel != Index.none) { + try writer.writeByte(':'); + try ip.print(array_info.sentinel, writer, options); + } + try writer.writeByte(']'); + + return array_info.child; + }, + .struct_type => |struct_index| { + const optional_decl_index = ip.getStruct(struct_index).owner_decl; + const decl_index = optional_decl_index.unwrap() orelse return panicOrElse("TODO", null); + const decl = ip.getDecl(decl_index); + try writer.print("{}", .{ip.fmtId(decl.name)}); + }, + .optional_type => |optional_info| { + try writer.writeByte('?'); + return optional_info.payload_type; + }, + .error_union_type => |error_union_info| { + if (error_union_info.error_set_type != .none) { + try ip.print(error_union_info.error_set_type, writer, options); + } + try writer.writeByte('!'); + return error_union_info.payload_type; + }, + .error_set_type => |error_set_info| { + if (error_set_info.owner_decl.unwrap()) |decl_index| { + const decl = ip.getDecl(decl_index); + try writer.print("{}", .{ip.fmtId(decl.name)}); + return null; + } + const names = error_set_info.names; + try writer.writeAll("error{"); + for (names, 0..) |name, i| { + if (i != 0) try writer.writeByte(','); + try writer.print("{}", .{ip.fmtId(name)}); + } + try writer.writeByte('}'); + }, + .enum_type => return panicOrElse("TODO", null), + .function_type => |function_info| { + try writer.writeAll("fn("); + + for (function_info.args, 0..) |arg_ty, i| { + if (i != 0) try writer.writeAll(", "); + + if (i < 32) { + if (function_info.args_is_comptime.isSet(i)) { + try writer.writeAll("comptime "); + } + if (function_info.args_is_noalias.isSet(i)) { + try writer.writeAll("noalias "); + } + } + + try ip.print(arg_ty, writer, options); + } + + if (function_info.is_var_args) { + if (function_info.args.len != 0) { + try writer.writeAll(", "); + } + try writer.writeAll("..."); + } + try writer.writeAll(") "); + + if (function_info.alignment != 0) { + try writer.print("align({d}) ", .{function_info.alignment}); + } + if (function_info.calling_convention != .Unspecified) { + try writer.print("callconv(.{s}) ", .{@tagName(function_info.calling_convention)}); + } + + return function_info.return_type; + }, + .union_type => return panicOrElse("TODO", null), + .tuple_type => |tuple_info| { + try writer.writeAll("tuple{"); + for (tuple_info.types, tuple_info.values, 0..) |field_ty, field_val, i| { + if (i != 0) try writer.writeAll(", "); + if (field_val != Index.none) { + try writer.writeAll("comptime "); + } + try ip.print(field_ty, writer, options); + if (field_val != Index.none) { + try writer.writeAll(" = "); + try ip.print(field_val, writer, options); + } + } + try writer.writeByte('}'); + }, + .vector_type => |vector_info| { + try writer.print("@Vector({d},{})", .{ + vector_info.len, + vector_info.child.fmtOptions(ip, options), + }); + }, + .anyframe_type => |anyframe_info| { + try writer.writeAll("anyframe->"); + return anyframe_info.child; + }, + + .simple_value => |simple| switch (simple) { + .undefined_value => try writer.writeAll("undefined"), + .void_value => try writer.writeAll("{}"), + .unreachable_value => try writer.writeAll("unreachable"), + .null_value => try writer.writeAll("null"), + .bool_true => try writer.writeAll("true"), + .bool_false => try writer.writeAll("false"), + .the_only_possible_value => try writer.writeAll("(the only possible value)"), + .generic_poison => try writer.writeAll("(generic poison)"), + }, + .int_u64_value => |i| try writer.print("{d}", .{i.int}), + .int_i64_value => |i| try writer.print("{d}", .{i.int}), + .int_big_value => |i| try writer.print("{d}", .{i.int}), + .float_16_value => |float| try writer.print("{d}", .{float}), + .float_32_value => |float| try writer.print("{d}", .{float}), + .float_64_value => |float| try writer.print("{d}", .{float}), + .float_80_value => |float| try writer.print("{d}", .{@as(f64, @floatCast(float))}), + .float_128_value, + .float_comptime_value, + => |float| try writer.print("{d}", .{@as(f64, @floatCast(float))}), + + .optional_value => |optional| return optional.val, + .slice => |slice_value| { + _ = slice_value; + try writer.writeAll(".{"); + try writer.writeAll(" TODO "); // TODO + try writer.writeByte('}'); + }, + .aggregate => |aggregate| { + if (aggregate.values.len == 0) { + try writer.writeAll(".{}"); + return null; + } + const struct_info = ip.getStruct(ip.indexToKey(aggregate.ty).struct_type); + + try writer.writeAll(".{"); + for (struct_info.fields.keys(), aggregate.values, 0..) |field_name, field, i| { + if (i != 0) try writer.writeAll(", "); + + try writer.print(".{} = {}", .{ + ip.fmtId(field_name), + field.fmtOptions(ip, options), + }); + } + try writer.writeByte('}'); + }, + .union_value => |union_value| { + const union_info = ip.getUnion(ip.indexToKey(union_value.ty).union_type); + const name = union_info.fields.keys()[union_value.field_index]; + + try writer.print(".{{ .{} = {} }}", .{ + ip.fmtId(name), + union_value.val.fmtOptions(ip, options), + }); + }, + .error_value => |error_value| try writer.print("error.{}", .{ip.fmtId(error_value.error_tag_name)}), + .null_value => try writer.print("null", .{}), + .undefined_value => try writer.print("undefined", .{}), + .unknown_value => try writer.print("(unknown value)", .{}), + } + return null; +} + +fn formatId( + ctx: struct { + ip: *InternPool, + string: SPString, + }, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + _ = options; + if (fmt.len != 0) std.fmt.invalidFmtError(fmt, InternPool.Struct.Field); + ctx.ip.string_pool.mutex.lock(); + defer ctx.ip.string_pool.mutex.unlock(); + try writer.print("{}", .{std.zig.fmtId(ctx.ip.string_pool.stringToSliceUnsafe(ctx.string))}); +} + +pub fn fmtId(ip: *InternPool, field: SPString) std.fmt.Formatter(formatId) { + return .{ .data = .{ .ip = ip, .string = field } }; +} + // --------------------------------------------- // TESTS // --------------------------------------------- @@ -2899,16 +3485,16 @@ test "simple types" { const bool_true = try ip.get(gpa, .{ .simple_value = .bool_true }); const bool_false = try ip.get(gpa, .{ .simple_value = .bool_false }); - try expectFmt("@TypeOf(null)", "{}", .{null_type.fmt(ip)}); - try expectFmt("@TypeOf(undefined)", "{}", .{undefined_type.fmt(ip)}); - try expectFmt("@TypeOf(.enum_literal)", "{}", .{enum_literal_type.fmt(ip)}); + try expectFmt("@TypeOf(null)", "{}", .{null_type.fmt(&ip)}); + try expectFmt("@TypeOf(undefined)", "{}", .{undefined_type.fmt(&ip)}); + try expectFmt("@TypeOf(.enum_literal)", "{}", .{enum_literal_type.fmt(&ip)}); - try expectFmt("undefined", "{}", .{undefined_value.fmt(ip)}); - try expectFmt("{}", "{}", .{void_value.fmt(ip)}); - try expectFmt("unreachable", "{}", .{unreachable_value.fmt(ip)}); - try expectFmt("null", "{}", .{null_value.fmt(ip)}); - try expectFmt("true", "{}", .{bool_true.fmt(ip)}); - try expectFmt("false", "{}", .{bool_false.fmt(ip)}); + try expectFmt("undefined", "{}", .{undefined_value.fmt(&ip)}); + try expectFmt("{}", "{}", .{void_value.fmt(&ip)}); + try expectFmt("unreachable", "{}", .{unreachable_value.fmt(&ip)}); + try expectFmt("null", "{}", .{null_value.fmt(&ip)}); + try expectFmt("true", "{}", .{bool_true.fmt(&ip)}); + try expectFmt("false", "{}", .{bool_false.fmt(&ip)}); } test "int type" { @@ -2928,9 +3514,9 @@ test "int type" { try expect(i16_type != another_i32_type); try expect(i16_type != u7_type); - try expectFmt("i32", "{}", .{i32_type.fmt(ip)}); - try expectFmt("i16", "{}", .{i16_type.fmt(ip)}); - try expectFmt("u7", "{}", .{u7_type.fmt(ip)}); + try expectFmt("i32", "{}", .{i32_type.fmt(&ip)}); + try expectFmt("i16", "{}", .{i16_type.fmt(&ip)}); + try expectFmt("u7", "{}", .{u7_type.fmt(&ip)}); } test "int value" { @@ -2956,14 +3542,14 @@ test "int value" { try expect(u64_max_value != i64_max_value); try expect(i64_max_value != i64_min_value); - try expectFmt("0", "{}", .{unsigned_zero_value.fmt(ip)}); - try expectFmt("1", "{}", .{unsigned_one_value.fmt(ip)}); - try expectFmt("0", "{}", .{signed_zero_value.fmt(ip)}); - try expectFmt("1", "{}", .{signed_one_value.fmt(ip)}); + try expectFmt("0", "{}", .{unsigned_zero_value.fmt(&ip)}); + try expectFmt("1", "{}", .{unsigned_one_value.fmt(&ip)}); + try expectFmt("0", "{}", .{signed_zero_value.fmt(&ip)}); + try expectFmt("1", "{}", .{signed_one_value.fmt(&ip)}); - try expectFmt("18446744073709551615", "{}", .{u64_max_value.fmt(ip)}); - try expectFmt("9223372036854775807", "{}", .{i64_max_value.fmt(ip)}); - try expectFmt("-9223372036854775808", "{}", .{i64_min_value.fmt(ip)}); + try expectFmt("18446744073709551615", "{}", .{u64_max_value.fmt(&ip)}); + try expectFmt("9223372036854775807", "{}", .{i64_max_value.fmt(&ip)}); + try expectFmt("-9223372036854775808", "{}", .{i64_min_value.fmt(&ip)}); } test "big int value" { @@ -2988,8 +3574,8 @@ test "big int value" { .int = result.toConst().negate(), } }); - try expectFmt("340282366920938463463374607431768211456", "{}", .{positive_big_int_value.fmt(ip)}); - try expectFmt("-340282366920938463463374607431768211456", "{}", .{negative_big_int_value.fmt(ip)}); + try expectFmt("340282366920938463463374607431768211456", "{}", .{positive_big_int_value.fmt(&ip)}); + try expectFmt("-340282366920938463463374607431768211456", "{}", .{negative_big_int_value.fmt(&ip)}); } test "float type" { @@ -3015,11 +3601,11 @@ test "float type" { try expect(f32_type == another_f32_type); try expect(f64_type == another_f64_type); - try expectFmt("f16", "{}", .{f16_type.fmt(ip)}); - try expectFmt("f32", "{}", .{f32_type.fmt(ip)}); - try expectFmt("f64", "{}", .{f64_type.fmt(ip)}); - try expectFmt("f80", "{}", .{f80_type.fmt(ip)}); - try expectFmt("f128", "{}", .{f128_type.fmt(ip)}); + try expectFmt("f16", "{}", .{f16_type.fmt(&ip)}); + try expectFmt("f32", "{}", .{f32_type.fmt(&ip)}); + try expectFmt("f64", "{}", .{f64_type.fmt(&ip)}); + try expectFmt("f80", "{}", .{f80_type.fmt(&ip)}); + try expectFmt("f128", "{}", .{f128_type.fmt(&ip)}); } test "float value" { @@ -3064,20 +3650,20 @@ test "float value" { try expect(ip.indexToKey(f32_zero_value).eql(ip.indexToKey(f32_zero_value))); try expect(!ip.indexToKey(f32_zero_value).eql(ip.indexToKey(f32_nzero_value))); - try expectFmt("0.25", "{}", .{f16_value.fmt(ip)}); - try expectFmt("0.5", "{}", .{f32_value.fmt(ip)}); - try expectFmt("1", "{}", .{f64_value.fmt(ip)}); - try expectFmt("2", "{}", .{f80_value.fmt(ip)}); - try expectFmt("2.75", "{}", .{f128_value.fmt(ip)}); + try expectFmt("0.25", "{}", .{f16_value.fmt(&ip)}); + try expectFmt("0.5", "{}", .{f32_value.fmt(&ip)}); + try expectFmt("1", "{}", .{f64_value.fmt(&ip)}); + try expectFmt("2", "{}", .{f80_value.fmt(&ip)}); + try expectFmt("2.75", "{}", .{f128_value.fmt(&ip)}); - try expectFmt("nan", "{}", .{f32_snan_value.fmt(ip)}); - try expectFmt("nan", "{}", .{f32_qnan_value.fmt(ip)}); + try expectFmt("nan", "{}", .{f32_snan_value.fmt(&ip)}); + try expectFmt("nan", "{}", .{f32_qnan_value.fmt(&ip)}); - try expectFmt("inf", "{}", .{f32_inf_value.fmt(ip)}); - try expectFmt("-inf", "{}", .{f32_ninf_value.fmt(ip)}); + try expectFmt("inf", "{}", .{f32_inf_value.fmt(&ip)}); + try expectFmt("-inf", "{}", .{f32_ninf_value.fmt(&ip)}); - try expectFmt("0", "{}", .{f32_zero_value.fmt(ip)}); - try expectFmt("-0", "{}", .{f32_nzero_value.fmt(ip)}); + try expectFmt("0", "{}", .{f32_zero_value.fmt(&ip)}); + try expectFmt("-0", "{}", .{f32_nzero_value.fmt(&ip)}); } test "pointer type" { @@ -3147,17 +3733,17 @@ test "pointer type" { try expect(@"[*:0]u32" != @"[:0]u32"); try expect(@"[:0]u32" != @"[*c]u32"); - try expectFmt("*i32", "{}", .{@"*i32".fmt(ip)}); - try expectFmt("*u32", "{}", .{@"*u32".fmt(ip)}); - try expectFmt("*const volatile u32", "{}", .{@"*const volatile u32".fmt(ip)}); - try expectFmt("*align(4:2:3) u32", "{}", .{@"*align(4:2:3) u32".fmt(ip)}); - try expectFmt("*addrspace(.shared) const u32", "{}", .{@"*addrspace(.shared) const u32".fmt(ip)}); - - try expectFmt("[*]u32", "{}", .{@"[*]u32".fmt(ip)}); - try expectFmt("[*:0]u32", "{}", .{@"[*:0]u32".fmt(ip)}); - try expectFmt("[]u32", "{}", .{@"[]u32".fmt(ip)}); - try expectFmt("[:0]u32", "{}", .{@"[:0]u32".fmt(ip)}); - try expectFmt("[*c]u32", "{}", .{@"[*c]u32".fmt(ip)}); + try expectFmt("*i32", "{}", .{@"*i32".fmt(&ip)}); + try expectFmt("*u32", "{}", .{@"*u32".fmt(&ip)}); + try expectFmt("*const volatile u32", "{}", .{@"*const volatile u32".fmt(&ip)}); + try expectFmt("*align(4:2:3) u32", "{}", .{@"*align(4:2:3) u32".fmt(&ip)}); + try expectFmt("*addrspace(.shared) const u32", "{}", .{@"*addrspace(.shared) const u32".fmt(&ip)}); + + try expectFmt("[*]u32", "{}", .{@"[*]u32".fmt(&ip)}); + try expectFmt("[*:0]u32", "{}", .{@"[*:0]u32".fmt(&ip)}); + try expectFmt("[]u32", "{}", .{@"[]u32".fmt(&ip)}); + try expectFmt("[:0]u32", "{}", .{@"[:0]u32".fmt(&ip)}); + try expectFmt("[*c]u32", "{}", .{@"[*c]u32".fmt(&ip)}); } test "optional type" { @@ -3171,8 +3757,8 @@ test "optional type" { try expect(i32_optional_type != u32_optional_type); - try expectFmt("?i32", "{}", .{i32_optional_type.fmt(ip)}); - try expectFmt("?u32", "{}", .{u32_optional_type.fmt(ip)}); + try expectFmt("?i32", "{}", .{i32_optional_type.fmt(&ip)}); + try expectFmt("?u32", "{}", .{u32_optional_type.fmt(&ip)}); } test "optional value" { @@ -3186,7 +3772,7 @@ test "optional value" { const u64_42_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .u64_type, .int = 42 } }); const optional_42_value = try ip.get(gpa, .{ .optional_value = .{ .ty = u32_optional_type, .val = u64_42_value } }); - try expectFmt("42", "{}", .{optional_42_value.fmt(ip)}); + try expectFmt("42", "{}", .{optional_42_value.fmt(&ip)}); } test "error set type" { @@ -3195,26 +3781,31 @@ test "error set type" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); - const foo_name = try ip.get(gpa, .{ .bytes = "foo" }); - const bar_name = try ip.get(gpa, .{ .bytes = "bar" }); - const baz_name = try ip.get(gpa, .{ .bytes = "baz" }); + const foo_name = try ip.string_pool.getOrPutString(gpa, "foo"); + const bar_name = try ip.string_pool.getOrPutString(gpa, "bar"); + const baz_name = try ip.string_pool.getOrPutString(gpa, "baz"); - const empty_error_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{} } }); + const empty_error_set = try ip.get(gpa, .{ .error_set_type = .{ + .owner_decl = .none, + .names = &.{}, + } }); const foo_bar_baz_set = try ip.get(gpa, .{ .error_set_type = .{ + .owner_decl = .none, .names = &.{ foo_name, bar_name, baz_name }, } }); const foo_bar_set = try ip.get(gpa, .{ .error_set_type = .{ + .owner_decl = .none, .names = &.{ foo_name, bar_name }, } }); try expect(empty_error_set != foo_bar_baz_set); try expect(foo_bar_baz_set != foo_bar_set); - try expectFmt("error{}", "{}", .{empty_error_set.fmt(ip)}); - try expectFmt("error{foo,bar,baz}", "{}", .{foo_bar_baz_set.fmt(ip)}); - try expectFmt("error{foo,bar}", "{}", .{foo_bar_set.fmt(ip)}); + try expectFmt("error{}", "{}", .{empty_error_set.fmt(&ip)}); + try expectFmt("error{foo,bar,baz}", "{}", .{foo_bar_baz_set.fmt(&ip)}); + try expectFmt("error{foo,bar}", "{}", .{foo_bar_set.fmt(&ip)}); } test "error union type" { @@ -3223,7 +3814,10 @@ test "error union type" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); - const empty_error_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{} } }); + const empty_error_set = try ip.get(gpa, .{ .error_set_type = .{ + .owner_decl = .none, + .names = &.{}, + } }); const bool_type = try ip.get(gpa, .{ .simple_type = .bool }); const @"error{}!bool" = try ip.get(gpa, .{ .error_union_type = .{ @@ -3231,7 +3825,7 @@ test "error union type" { .payload_type = bool_type, } }); - try expectFmt("error{}!bool", "{}", .{@"error{}!bool".fmt(ip)}); + try expectFmt("error{}!bool", "{}", .{@"error{}!bool".fmt(&ip)}); } test "array type" { @@ -3252,8 +3846,8 @@ test "array type" { try expect(i32_3_array_type != u32_0_0_array_type); - try expectFmt("[3]i32", "{}", .{i32_3_array_type.fmt(ip)}); - try expectFmt("[3:0]u32", "{}", .{u32_0_0_array_type.fmt(ip)}); + try expectFmt("[3]i32", "{}", .{i32_3_array_type.fmt(&ip)}); + try expectFmt("[3:0]u32", "{}", .{u32_0_0_array_type.fmt(&ip)}); } test "struct value" { @@ -3262,24 +3856,28 @@ test "struct value" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); + const foo_name_index = try ip.string_pool.getOrPutString(gpa, "foo"); + const bar_name_index = try ip.string_pool.getOrPutString(gpa, "bar"); + const struct_index = try ip.createStruct(gpa, .{ .fields = .{}, + .owner_decl = .none, .namespace = .none, .layout = .Auto, .backing_int_ty = .none, .status = .none, }); const struct_type = try ip.get(gpa, .{ .struct_type = struct_index }); - const struct_info = ip.getStruct(struct_index); - try struct_info.fields.put(gpa, "foo", .{ .ty = .usize_type }); - try struct_info.fields.put(gpa, "bar", .{ .ty = .bool_type }); + const struct_info = ip.getStructMut(struct_index); + try struct_info.fields.put(gpa, foo_name_index, .{ .ty = .usize_type }); + try struct_info.fields.put(gpa, bar_name_index, .{ .ty = .bool_type }); const aggregate_value = try ip.get(gpa, .{ .aggregate = .{ .ty = struct_type, .values = &.{ .one_usize, .bool_true }, } }); - try expectFmt(".{.foo = 1, .bar = true}", "{}", .{aggregate_value.fmt(ip)}); + try expectFmt(".{.foo = 1, .bar = true}", "{}", .{aggregate_value.fmt(&ip)}); } test "function type" { @@ -3318,10 +3916,10 @@ test "function type" { .calling_convention = .C, } }); - try expectFmt("fn(i32) bool", "{}", .{@"fn(i32) bool".fmt(ip)}); - try expectFmt("fn(comptime type, noalias i32) type", "{}", .{@"fn(comptime type, noalias i32) type".fmt(ip)}); - try expectFmt("fn(i32, ...) type", "{}", .{@"fn(i32, ...) type".fmt(ip)}); - try expectFmt("fn() align(4) callconv(.C) type", "{}", .{@"fn() align(4) callconv(.C) type".fmt(ip)}); + try expectFmt("fn(i32) bool", "{}", .{@"fn(i32) bool".fmt(&ip)}); + try expectFmt("fn(comptime type, noalias i32) type", "{}", .{@"fn(comptime type, noalias i32) type".fmt(&ip)}); + try expectFmt("fn(i32, ...) type", "{}", .{@"fn(i32, ...) type".fmt(&ip)}); + try expectFmt("fn() align(4) callconv(.C) type", "{}", .{@"fn() align(4) callconv(.C) type".fmt(&ip)}); } test "union value" { @@ -3330,6 +3928,9 @@ test "union value" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); + const int_name_index = try ip.string_pool.getOrPutString(gpa, "int"); + const float_name_index = try ip.string_pool.getOrPutString(gpa, "float"); + const f16_value = try ip.get(gpa, .{ .float_16_value = 0.25 }); const union_index = try ip.createUnion(gpa, .{ @@ -3340,9 +3941,9 @@ test "union value" { .status = .none, }); const union_type = try ip.get(gpa, .{ .union_type = union_index }); - const union_info = ip.getUnion(union_index); - try union_info.fields.put(gpa, "int", .{ .ty = .usize_type, .alignment = 0 }); - try union_info.fields.put(gpa, "float", .{ .ty = .f16_type, .alignment = 0 }); + const union_info = ip.getUnionMut(union_index); + try union_info.fields.put(gpa, int_name_index, .{ .ty = .usize_type, .alignment = 0 }); + try union_info.fields.put(gpa, float_name_index, .{ .ty = .f16_type, .alignment = 0 }); const union_value1 = try ip.get(gpa, .{ .union_value = .{ .ty = union_type, @@ -3355,8 +3956,8 @@ test "union value" { .val = f16_value, } }); - try expectFmt(".{ .int = 1 }", "{}", .{union_value1.fmt(ip)}); - try expectFmt(".{ .float = 0.25 }", "{}", .{union_value2.fmt(ip)}); + try expectFmt(".{ .int = 1 }", "{}", .{union_value1.fmt(&ip)}); + try expectFmt(".{ .float = 0.25 }", "{}", .{union_value2.fmt(&ip)}); } test "anyframe type" { @@ -3370,8 +3971,8 @@ test "anyframe type" { try expect(@"anyframe->i32" != @"anyframe->bool"); - try expectFmt("anyframe->i32", "{}", .{@"anyframe->i32".fmt(ip)}); - try expectFmt("anyframe->bool", "{}", .{@"anyframe->bool".fmt(ip)}); + try expectFmt("anyframe->i32", "{}", .{@"anyframe->i32".fmt(&ip)}); + try expectFmt("anyframe->bool", "{}", .{@"anyframe->bool".fmt(&ip)}); } test "vector type" { @@ -3391,40 +3992,8 @@ test "vector type" { try expect(@"@Vector(2,u32)" != @"@Vector(2,bool)"); - try expectFmt("@Vector(2,u32)", "{}", .{@"@Vector(2,u32)".fmt(ip)}); - try expectFmt("@Vector(2,bool)", "{}", .{@"@Vector(2,bool)".fmt(ip)}); -} - -test "bytes value" { - const gpa = std.testing.allocator; - - var ip = try InternPool.init(gpa); - defer ip.deinit(gpa); - - var str1: [43]u8 = "https://www.youtube.com/watch?v=dQw4w9WgXcQ".*; - const bytes_value1 = try ip.get(gpa, .{ .bytes = &str1 }); - @memset(&str1, 0); - - var str2: [43]u8 = "https://www.youtube.com/watch?v=dQw4w9WgXcQ".*; - const bytes_value2 = try ip.get(gpa, .{ .bytes = &str2 }); - @memset(&str2, 0); - - var str3: [26]u8 = "https://www.duckduckgo.com".*; - const bytes_value3 = try ip.get(gpa, .{ .bytes = &str3 }); - @memset(&str3, 0); - - try expect(bytes_value1 == bytes_value2); - try expect(bytes_value2 != bytes_value3); - - try expect(@intFromPtr(&str1) != @intFromPtr(ip.indexToKey(bytes_value1).bytes.ptr)); - try expect(@intFromPtr(&str2) != @intFromPtr(ip.indexToKey(bytes_value2).bytes.ptr)); - try expect(@intFromPtr(&str3) != @intFromPtr(ip.indexToKey(bytes_value3).bytes.ptr)); - - try std.testing.expectEqual(ip.indexToKey(bytes_value1).bytes.ptr, ip.indexToKey(bytes_value2).bytes.ptr); - - try std.testing.expectEqualStrings("https://www.youtube.com/watch?v=dQw4w9WgXcQ", ip.indexToKey(bytes_value1).bytes); - try std.testing.expectEqualStrings("https://www.youtube.com/watch?v=dQw4w9WgXcQ", ip.indexToKey(bytes_value2).bytes); - try std.testing.expectEqualStrings("https://www.duckduckgo.com", ip.indexToKey(bytes_value3).bytes); + try expectFmt("@Vector(2,u32)", "{}", .{@"@Vector(2,u32)".fmt(&ip)}); + try expectFmt("@Vector(2,bool)", "{}", .{@"@Vector(2,bool)".fmt(&ip)}); } test "coerceInMemoryAllowed integers and floats" { @@ -3461,14 +4030,26 @@ test "coerceInMemoryAllowed error set" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); - const foo_name = try ip.get(gpa, .{ .bytes = "foo" }); - const bar_name = try ip.get(gpa, .{ .bytes = "bar" }); - const baz_name = try ip.get(gpa, .{ .bytes = "baz" }); + const foo_name = try ip.string_pool.getOrPutString(gpa, "foo"); + const bar_name = try ip.string_pool.getOrPutString(gpa, "bar"); + const baz_name = try ip.string_pool.getOrPutString(gpa, "baz"); - const foo_bar_baz_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{ baz_name, bar_name, foo_name } } }); - const foo_bar_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{ foo_name, bar_name } } }); - const foo_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{foo_name} } }); - const empty_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{} } }); + const foo_bar_baz_set = try ip.get(gpa, .{ .error_set_type = .{ + .owner_decl = .none, + .names = &.{ baz_name, bar_name, foo_name }, + } }); + const foo_bar_set = try ip.get(gpa, .{ .error_set_type = .{ + .owner_decl = .none, + .names = &.{ foo_name, bar_name }, + } }); + const foo_set = try ip.get(gpa, .{ .error_set_type = .{ + .owner_decl = .none, + .names = &.{foo_name}, + } }); + const empty_set = try ip.get(gpa, .{ .error_set_type = .{ + .owner_decl = .none, + .names = &.{}, + } }); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, foo_bar_baz_set, true, builtin.target) == .ok); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, foo_bar_set, true, builtin.target) == .ok); @@ -3503,10 +4084,9 @@ test "resolvePeerTypes" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); - try expect(.noreturn_type == try ip.resolvePeerTypes(std.testing.allocator, &.{}, builtin.target)); - try expect(.type_type == try ip.resolvePeerTypes(std.testing.allocator, &.{.type_type}, builtin.target)); + try expect(.noreturn_type == try ip.resolvePeerTypes(gpa, &.{}, builtin.target)); + try expect(.type_type == try ip.resolvePeerTypes(gpa, &.{.type_type}, builtin.target)); - try ip.testResolvePeerTypes(.none, .none, .none); try ip.testResolvePeerTypes(.bool_type, .bool_type, .bool_type); try ip.testResolvePeerTypes(.bool_type, .noreturn_type, .bool_type); try ip.testResolvePeerTypes(.bool_type, .undefined_type, .bool_type); @@ -3735,18 +4315,52 @@ fn testResolvePeerTypes(ip: *InternPool, a: Index, b: Index, expected: Index) !v fn testResolvePeerTypesInOrder(ip: *InternPool, lhs: Index, rhs: Index, expected: Index) !void { const actual = try resolvePeerTypes(ip, std.testing.allocator, &.{ lhs, rhs }, builtin.target); - try expectEqualTypes(ip.*, expected, actual); + if (expected == actual) return; + std.debug.print("expected `{}`, found `{}`\n", .{ expected.fmtDebug(ip), actual.fmtDebug(ip) }); + return error.TestExpectedEqual; } -fn expectEqualTypes(ip: InternPool, expected: Index, actual: Index) !void { +test "coerce int" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"as(comptime_int, 1)" = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 1 } }); + const @"as(u1, 1)" = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .u1_type, .int = 1 } }); + const @"as(comptime_int, -1)" = try ip.get(gpa, .{ .int_i64_value = .{ .ty = .comptime_int_type, .int = -1 } }); + const @"as(i64, 32000)" = try ip.get(gpa, .{ .int_i64_value = .{ .ty = .i64_type, .int = 32000 } }); + + try ip.testCoerce(.u1_type, @"as(comptime_int, 1)", @"as(u1, 1)"); + try ip.testCoerce(.u1_type, @"as(comptime_int, -1)", .none); + try ip.testCoerce(.i8_type, @"as(i64, 32000)", .none); +} + +fn testCoerce(ip: *InternPool, dest_ty: Index, inst: Index, expected: Index) !void { + assert(ip.isType(dest_ty)); + + const gpa = std.testing.allocator; + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + var err_msg: ErrorMsg = undefined; + const actual = try ip.coerce(gpa, arena, dest_ty, inst, builtin.target, &err_msg); if (expected == actual) return; - const allocator = std.testing.allocator; - const expected_type = if (expected == .none) @tagName(Index.none) else try std.fmt.allocPrint(allocator, "{}", .{expected.fmt(ip)}); - defer if (expected != .none) allocator.free(expected_type); - const actual_type = if (actual == .none) @tagName(Index.none) else try std.fmt.allocPrint(allocator, "{}", .{actual.fmt(ip)}); - defer if (actual != .none) allocator.free(actual_type); + std.debug.print( + \\expression: @as({}, {}) + \\expected: {} + , .{ + dest_ty.fmtDebug(ip), + inst.fmtDebug(ip), + expected.fmtDebug(ip), + }); + if (actual == .none) { + std.debug.print("got error: '{}'", .{err_msg.fmt(ip)}); + } else { + std.debug.print("actual: '{}'", .{actual.fmtDebug(ip)}); + } - std.debug.print("expected `{s}`, found `{s}`\n", .{ expected_type, actual_type }); return error.TestExpectedEqual; } diff --git a/src/analyser/analyser.zig b/src/analyser/analyser.zig index 94af823c8..9603aee36 100644 --- a/src/analyser/analyser.zig +++ b/src/analyser/analyser.zig @@ -1,5 +1,6 @@ pub const completions = @import("completions.zig"); pub const InternPool = @import("InternPool.zig"); +pub const StringPool = @import("string_pool.zig").StringPool; pub const encoding = @import("encoding.zig"); comptime { diff --git a/src/analyser/completions.zig b/src/analyser/completions.zig index ec7ab99bf..eff170d73 100644 --- a/src/analyser/completions.zig +++ b/src/analyser/completions.zig @@ -11,46 +11,45 @@ pub fn dotCompletions( completions: *std.ArrayListUnmanaged(types.CompletionItem), ip: *InternPool, index: InternPool.Index, - is_type_val: bool, node: ?Ast.Node.Index, ) error{OutOfMemory}!void { std.debug.assert(index != .none); _ = node; - const index_key = ip.indexToKey(index); - const val: InternPool.Key = if (is_type_val) index_key else .{ .unknown_value = .{ .ty = index } }; - const ty: InternPool.Key = if (is_type_val) ip.indexToKey(index_key.typeOf()) else index_key; + const val: InternPool.Index = index; + const ty: InternPool.Index = ip.typeOf(index); - const inner_ty = switch (ty) { - .pointer_type => |info| if (info.size == .One) ip.indexToKey(info.elem_type) else ty, + const inner_ty = switch (ip.indexToKey(ty)) { + .pointer_type => |pointer_info| if (pointer_info.size == .One) pointer_info.elem_type else ty, else => ty, }; - switch (inner_ty) { + switch (ip.indexToKey(inner_ty)) { .simple_type => |simple| switch (simple) { .type => { - const namespace = val.getNamespace(ip.*); + if (val == .none) return; + + const namespace = ip.getNamespace(val); if (namespace != .none) { // TODO lookup in namespace } - switch (val) { + + switch (ip.indexToKey(val)) { .error_set_type => |error_set_info| { for (error_set_info.names) |name| { - const error_name = ip.indexToKey(name).bytes; try completions.append(arena, .{ - .label = error_name, + .label = try std.fmt.allocPrint(arena, "{}", .{name.fmt(&ip.string_pool)}), .kind = .Constant, - .detail = try std.fmt.allocPrint(arena, "error.{s}", .{std.zig.fmtId(error_name)}), + .detail = try std.fmt.allocPrint(arena, "error.{}", .{ip.fmtId(name)}), }); } }, .union_type => {}, // TODO .enum_type => |enum_index| { const enum_info = ip.getEnum(enum_index); - var field_it = enum_info.fields.iterator(); - while (field_it.next()) |entry| { + for (enum_info.fields.keys()) |name| { try completions.append(arena, .{ - .label = entry.key_ptr.*, + .label = try std.fmt.allocPrint(arena, "{}", .{name.fmt(&ip.string_pool)}), .kind = .EnumMember, // include field.val? }); @@ -63,13 +62,11 @@ pub fn dotCompletions( }, .pointer_type => |pointer_info| { if (pointer_info.size == .Slice) { - var many_ptr_info = InternPool.Key{ .pointer_type = pointer_info }; - many_ptr_info.pointer_type.size = .Many; - try completions.append(arena, .{ .label = "ptr", .kind = .Field, - .detail = try std.fmt.allocPrint(arena, "ptr: {}", .{many_ptr_info.fmt(ip.*)}), + // TODO this discards pointer attributes + .detail = try std.fmt.allocPrint(arena, "ptr: [*]{}", .{pointer_info.elem_type.fmt(ip)}), }); try completions.append(arena, .{ .label = "len", @@ -94,16 +91,13 @@ pub fn dotCompletions( .struct_type => |struct_index| { const struct_info = ip.getStruct(struct_index); try completions.ensureUnusedCapacity(arena, struct_info.fields.count()); - var field_it = struct_info.fields.iterator(); - while (field_it.next()) |entry| { - const label = entry.key_ptr.*; - const field = entry.value_ptr.*; + for (struct_info.fields.keys(), struct_info.fields.values()) |name, field| { completions.appendAssumeCapacity(.{ - .label = label, + .label = try std.fmt.allocPrint(arena, "{}", .{ip.fmtId(name)}), .kind = .Field, - .detail = try std.fmt.allocPrint(arena, "{s}: {}", .{ - label, - fmtFieldDetail(field, ip), + .detail = try std.fmt.allocPrint(arena, "{}: {}", .{ + name.fmt(&ip.string_pool), + fmtFieldDetail(ip, field), }), }); } @@ -112,32 +106,29 @@ pub fn dotCompletions( try completions.append(arena, .{ .label = "?", .kind = .Operator, - .detail = try std.fmt.allocPrint(arena, "{}", .{optional_info.payload_type.fmt(ip.*)}), + .detail = try std.fmt.allocPrint(arena, "{}", .{optional_info.payload_type.fmt(ip)}), }); }, .enum_type => |enum_index| { const enum_info = ip.getEnum(enum_index); - for (enum_info.fields.keys(), enum_info.values.keys()) |field_name, field_value| { + for (enum_info.fields.keys(), enum_info.values.keys()) |name, field_value| { try completions.append(arena, .{ - .label = field_name, + .label = try std.fmt.allocPrint(arena, "{}", .{ip.fmtId(name)}), .kind = .Field, - .detail = try std.fmt.allocPrint(arena, "{}", .{field_value.fmt(ip.*)}), + .detail = try std.fmt.allocPrint(arena, "{}", .{field_value.fmt(ip)}), }); } }, .union_type => |union_index| { const union_info = ip.getUnion(union_index); - var field_it = union_info.fields.iterator(); - while (field_it.next()) |entry| { - const label = entry.key_ptr.*; - const field = entry.value_ptr.*; + for (union_info.fields.keys(), union_info.fields.values()) |name, field| { try completions.append(arena, .{ - .label = label, + .label = try std.fmt.allocPrint(arena, "{}", .{ip.fmtId(name)}), .kind = .Field, .detail = if (field.alignment != 0) - try std.fmt.allocPrint(arena, "{s}: align({d}) {}", .{ label, field.alignment, field.ty.fmt(ip.*) }) + try std.fmt.allocPrint(arena, "{}: align({d}) {}", .{ ip.fmtId(name), field.alignment, field.ty.fmt(ip) }) else - try std.fmt.allocPrint(arena, "{s}: {}", .{ label, field.ty.fmt(ip.*) }), + try std.fmt.allocPrint(arena, "{}: {}", .{ ip.fmtId(name), field.ty.fmt(ip) }), }); } }, @@ -146,7 +137,7 @@ pub fn dotCompletions( try completions.append(arena, .{ .label = try std.fmt.allocPrint(arena, "{d}", .{i}), .kind = .Field, - .detail = try std.fmt.allocPrint(arena, "{d}: {}", .{ i, tuple_ty.fmt(ip.*) }), + .detail = try std.fmt.allocPrint(arena, "{d}: {}", .{ i, tuple_ty.fmt(ip) }), }); } }, @@ -170,11 +161,11 @@ pub fn dotCompletions( .float_comptime_value, => {}, - .bytes, .optional_value, .slice, .aggregate, .union_value, + .error_value, .null_value, .undefined_value, .unknown_value, @@ -205,15 +196,12 @@ fn formatFieldDetail( if (field.alignment != 0) { try writer.print("align({d}) ", .{field.alignment}); } - try writer.print("{}", .{field.ty.fmt(ctx.ip.*)}); + try writer.print("{}", .{field.ty.fmt(ctx.ip)}); if (field.default_value != .none) { - try writer.print(" = {},", .{field.default_value.fmt(ctx.ip.*)}); + try writer.print(" = {},", .{field.default_value.fmt(ctx.ip)}); } } -pub fn fmtFieldDetail(field: InternPool.Struct.Field, ip: *InternPool) std.fmt.Formatter(formatFieldDetail) { - return .{ .data = .{ - .ip = ip, - .item = field, - } }; +pub fn fmtFieldDetail(ip: *InternPool, field: InternPool.Struct.Field) std.fmt.Formatter(formatFieldDetail) { + return .{ .data = .{ .ip = ip, .item = field } }; } diff --git a/src/analyser/error_msg.zig b/src/analyser/error_msg.zig new file mode 100644 index 000000000..486ff0628 --- /dev/null +++ b/src/analyser/error_msg.zig @@ -0,0 +1,164 @@ +const std = @import("std"); +const types = @import("../lsp.zig"); +const offsets = @import("../offsets.zig"); + +const InternPool = @import("InternPool.zig"); +const Index = InternPool.Index; +const Key = InternPool.Key; + +pub const ErrorMsg = union(enum) { + /// zig: expected type '{}', found '{}' + expected_type: struct { + expected: Index, + actual: Index, + }, + /// zig: expected optional type, found '{}' + /// zig: expected error set type, found '{}' + /// zig: expected pointer, found '{}' + expected_tag_type: struct { + expected_tag: std.builtin.TypeId, + actual: Index, + }, + /// zig: comparison of '{}' with null + compare_eq_with_null: struct { + non_null_type: Index, + }, + /// zig: tried to unwrap optional of type `{}` which was '{}' + invalid_optional_unwrap: struct { + operand: Index, + }, + /// zig: type '{}' cannot represent integer value '{}' + integer_out_of_range: struct { + dest_ty: Index, + actual: Index, + }, + /// zig: expected {d} array elements; found 0 + wrong_array_elem_count: struct { + expected: u32, + actual: u32, + }, + /// zig: type '{}' does not support indexing + /// zig: operand must be an array, slice, tuple, or vector + expected_indexable_type: struct { + actual: Index, + }, + /// zig: duplicate struct field: '{}' + duplicate_struct_field: struct { + name: InternPool.StringPool.String, + }, + /// zig: `{}` has no member '{s}' + /// zig: `{}` does not support field access + unknown_field: struct { + accessed: Index, + field_name: []const u8, + }, + + const FormatContext = struct { + error_msg: ErrorMsg, + ip: *InternPool, + }; + + pub fn fmt(self: ErrorMsg, ip: *InternPool) std.fmt.Formatter(format) { + return .{ .data = .{ .error_msg = self, .ip = ip } }; + } + + pub fn format( + ctx: FormatContext, + comptime fmt_str: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + _ = options; + const ip = ctx.ip; + if (fmt_str.len != 0) std.fmt.invalidFmtError(fmt_str, ctx.error_msg); + return switch (ctx.error_msg) { + .expected_type => |info| std.fmt.format( + writer, + "expected type '{}', found '{}'", + .{ info.expected.fmt(ip), ip.typeOf(info.actual).fmt(ip) }, + ), + .expected_tag_type => |info| blk: { + const expected_tag_str = switch (info.expected_tag) { + .Type => "type", + .Void => "void", + .Bool => "bool", + .NoReturn => "noreturn", + .Int => "integer", + .Float => "float", + .Pointer => "pointer", + .Array => "array", + .Struct => "struct", + .ComptimeFloat => "comptime_float", + .ComptimeInt => "comptime_int", + .Undefined => "undefined", + .Null => "null", + .Optional => "optional", + .ErrorUnion => "error union", + .ErrorSet => "error set", + .Enum => "enum", + .Union => "union", + .Fn => "function", + .Opaque => "opaque", + .Frame => "frame", + .AnyFrame => "anyframe", + .Vector => "vector", + .EnumLiteral => "enum literal", + }; + break :blk std.fmt.format( + writer, + "expected {s} type, found '{}'", + .{ expected_tag_str, info.actual.fmt(ip) }, + ); + }, + .compare_eq_with_null => |info| std.fmt.format( + writer, + "comparison of '{}' with null", + .{info.non_null_type.fmt(ip)}, + ), + .invalid_optional_unwrap => |info| blk: { + const operand_ty = ip.typeOf(info.operand); + const payload_ty = ip.indexToKey(operand_ty).optional_type.payload_type; + break :blk std.fmt.format( + writer, + "tried to unwrap optional of type `{}` which was {}", + .{ payload_ty.fmt(ip), info.operand.fmt(ip) }, + ); + }, + .integer_out_of_range => |info| std.fmt.format( + writer, + "type '{}' cannot represent integer value '{}'", + .{ info.dest_ty.fmt(ip), info.actual.fmt(ip) }, + ), + .wrong_array_elem_count => |info| std.fmt.format( + writer, + "expected {d} array elements; found {d}", + .{ info.expected, info.actual }, + ), + .expected_indexable_type => |info| std.fmt.format( + writer, + "type '{}' does not support indexing", + .{info.actual.fmt(ip)}, + ), + .duplicate_struct_field => |info| std.fmt.format( + writer, + "duplicate struct field: '{}'", + .{info.name.fmt(&ip.string_pool)}, + ), + .unknown_field => |info| blk: { + const accessed_ty = ip.typeOf(info.accessed); + break :blk if (ip.canHaveFields(accessed_ty)) + std.fmt.format( + writer, + "'{}' has no member '{s}'", + .{ accessed_ty.fmt(ip), info.field_name }, + ) + else + std.fmt.format( + writer, + "'{}' does not support field access", + .{accessed_ty.fmt(ip)}, + ); + }, + }; + } +}; diff --git a/src/analyser/string_pool.zig b/src/analyser/string_pool.zig new file mode 100644 index 000000000..3f78dfcab --- /dev/null +++ b/src/analyser/string_pool.zig @@ -0,0 +1,194 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +pub const Config = struct { + /// Whether the StringPool may be used simultaneously from multiple threads. + thread_safe: bool = !builtin.single_threaded, + + /// What type of mutex you'd like to use, for thread safety. + /// when specified, the mutex type must have the same shape as `std.Thread.Mutex` and + /// `DummyMutex`, and have no required fields. Specifying this field causes + /// the `thread_safe` field to be ignored. + /// + /// when null (default): + /// * the mutex type defaults to `std.Thread.Mutex` when thread_safe is enabled. + /// * the mutex type defaults to `DummyMutex` otherwise. + MutexType: ?type = null, +}; + +pub fn StringPool(comptime config: Config) type { + return struct { + const Pool = @This(); + + pub const String = enum(u32) { + empty = 0, + _, + + pub fn toOptional(self: String) OptionalString { + return @enumFromInt(@intFromEnum(self)); + } + + pub fn fmt(self: String, pool: *Pool) std.fmt.Formatter(print) { + return .{ .data = .{ .string = self, .pool = pool } }; + } + }; + + pub const OptionalString = enum(u32) { + empty = 0, + none = std.math.maxInt(u32), + _, + + pub fn unwrap(self: OptionalString) ?String { + if (self == .none) return null; + return @enumFromInt(@intFromEnum(self)); + } + }; + + /// asserts that `str` contains no null bytes + pub fn getString(pool: *Pool, str: []const u8) ?String { + assert(std.mem.indexOfScalar(u8, str, 0) == null); + pool.mutex.lock(); + defer pool.mutex.unlock(); + const index = pool.map.getKeyAdapted(str, std.hash_map.StringIndexAdapter{ .bytes = &pool.bytes }) orelse return null; + return @enumFromInt(index); + } + + /// asserts that `str` contains no null bytes + /// returns `error.OutOfMemory` if adding this new string would increase the amount of allocated bytes above std.math.maxInt(u32) + pub fn getOrPutString(pool: *Pool, allocator: Allocator, str: []const u8) error{OutOfMemory}!String { + assert(std.mem.indexOfScalar(u8, str, 0) == null); + + const start_index = std.math.cast(u32, pool.bytes.items.len) orelse return error.OutOfMemory; + + pool.mutex.lock(); + defer pool.mutex.unlock(); + + pool.bytes.ensureUnusedCapacity(allocator, str.len + 1) catch { + // If allocation fails, try to do the lookup anyway. + const index = pool.map.getKeyAdapted(str, std.hash_map.StringIndexAdapter{ .bytes = &pool.bytes }) orelse return error.OutOfMemory; + return @enumFromInt(index); + }; + + const gop = try pool.map.getOrPutContextAdapted( + allocator, + str, + std.hash_map.StringIndexAdapter{ .bytes = &pool.bytes }, + std.hash_map.StringIndexContext{ .bytes = &pool.bytes }, + ); + + if (!gop.found_existing) { + pool.bytes.appendSliceAssumeCapacity(str); + pool.bytes.appendAssumeCapacity(0); + gop.key_ptr.* = start_index; + } + return @enumFromInt(gop.key_ptr.*); + } + + pub fn hashString(pool: *Pool, hasher: anytype, index: String) void { + pool.mutex.lock(); + defer pool.mutex.unlock(); + const str = pool.stringToSliceUnsafe(index); + hasher.update(str); + } + + pub fn stringToSliceAlloc(pool: *Pool, allocator: Allocator, index: String) Allocator.Error![]const u8 { + pool.mutex.lock(); + defer pool.mutex.unlock(); + const string_bytes: [*:0]u8 = @ptrCast(pool.bytes.items.ptr); + const start = @intFromEnum(index); + return try allocator.dupe(u8, std.mem.sliceTo(string_bytes + start, 0)); + } + + pub fn stringToSliceAllocZ(pool: *Pool, allocator: Allocator, index: String) Allocator.Error![:0]const u8 { + pool.mutex.lock(); + defer pool.mutex.unlock(); + const string_bytes: [*:0]u8 = @ptrCast(pool.bytes.items.ptr); + const start = @intFromEnum(index); + return try allocator.dupeZ(u8, std.mem.sliceTo(string_bytes + start, 0)); + } + + /// returns the underlying slice from an interned string + /// equal strings are guaranteed to share the same storage + pub fn stringToSliceUnsafe(pool: *Pool, index: String) [:0]const u8 { + const string_bytes: [*:0]u8 = @ptrCast(pool.bytes.items.ptr); + const start = @intFromEnum(index); + return std.mem.sliceTo(string_bytes + start, 0); + } + + mutex: @TypeOf(mutex_init) = mutex_init, + bytes: std.ArrayListUnmanaged(u8) = .{}, + map: std.HashMapUnmanaged(u32, void, std.hash_map.StringIndexContext, std.hash_map.default_max_load_percentage) = .{}, + + pub fn deinit(pool: *Pool, allocator: Allocator) void { + pool.bytes.deinit(allocator); + pool.map.deinit(allocator); + pool.* = undefined; + } + + const mutex_init = if (config.MutexType) |T| + T{} + else if (config.thread_safe) + std.Thread.Mutex{} + else + DummyMutex{}; + + const DummyMutex = struct { + pub fn lock(_: *@This()) void {} + pub fn unlock(_: *@This()) void {} + }; + + const FormatContext = struct { + string: String, + pool: *Pool, + }; + + fn print(ctx: FormatContext, comptime fmt_str: []const u8, _: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { + if (fmt_str.len != 0) std.fmt.invalidFmtError(fmt_str, ctx.string); + ctx.pool.mutex.lock(); + defer ctx.pool.mutex.unlock(); + try writer.writeAll(ctx.pool.stringToSliceUnsafe(ctx.string)); + } + }; +} + +test StringPool { + const gpa = std.testing.allocator; + var pool = StringPool(.{}){}; + defer pool.deinit(gpa); + + const str = "All Your Codebase Are Belong To Us"; + const index = try pool.getOrPutString(gpa, str); + try std.testing.expectEqualStrings(str, pool.stringToSliceUnsafe(index)); + try std.testing.expectFmt(str, "{}", .{index.fmt(&pool)}); +} + +test "StringPool - check interning" { + const gpa = std.testing.allocator; + var pool = StringPool(.{}){}; + defer pool.deinit(gpa); + + const str = "All Your Codebase Are Belong To Us"; + const index1 = try pool.getOrPutString(gpa, str); + const index2 = try pool.getOrPutString(gpa, str); + const index3 = pool.getString(str).?; + const storage1 = pool.stringToSliceUnsafe(index1); + const storage2 = pool.stringToSliceUnsafe(index2); + + try std.testing.expectEqual(index1, index2); + try std.testing.expectEqual(index2, index3); + try std.testing.expectEqualStrings(str, storage1); + try std.testing.expectEqualStrings(str, storage2); + try std.testing.expectEqual(storage1.ptr, storage2.ptr); + try std.testing.expectEqual(storage1.len, storage2.len); +} + +test "StringPool - empty string" { + if (true) return error.SkipZigTest; // TODO + const gpa = std.testing.allocator; + var pool = StringPool(.{}){}; + defer pool.deinit(gpa); + + try std.testing.expectEqualStrings("", pool.stringToSliceUnsafe(.empty)); +} diff --git a/src/analysis.zig b/src/analysis.zig index c698894f6..ad070486f 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1130,7 +1130,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e if (!is_escaped_identifier) { if (std.mem.eql(u8, name, "_")) return null; if (resolvePrimitiveType(name)) |primitive| { - const is_type = analyser.ip.indexToKey(primitive).typeOf() == .type_type; + const is_type = analyser.ip.typeOf(primitive) == .type_type; return TypeWithHandle{ .type = .{ .data = .{ .ip_index = .{ .index = primitive } }, .is_type_val = is_type }, .handle = handle, @@ -1248,7 +1248,6 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e } return null; }; - const is_type_val = interpreter.ip.indexToKey(value.index).typeOf() == .type_type; return TypeWithHandle{ .type = .{ @@ -1256,7 +1255,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e .node = value.node_idx, .index = value.index, } }, - .is_type_val = is_type_val, + .is_type_val = analyser.ip.typeOf(value.index) == .type_type, }, .handle = node_handle.handle, }; @@ -1866,6 +1865,7 @@ pub const Type = struct { /// const foo = u32; // is_type_val == true /// const bar = @as(u32, ...); // is_type_val == false /// ``` + /// if `data == .ip_index` then this field is equivalent to `typeOf(index) == .type_type` is_type_val: bool, pub fn hash32(self: Type) u32 { @@ -4117,8 +4117,7 @@ pub fn referencedTypes( .ip_index => |payload| { const allocator = collector.referenced_types.allocator; const ip = analyser.ip; - const index = if (resolved_type.type.is_type_val) ip.indexToKey(payload.index).typeOf() else payload.index; - resolved_type_str.* = try std.fmt.allocPrint(allocator, "{}", .{index.fmt(ip.*)}); + resolved_type_str.* = try std.fmt.allocPrint(allocator, "{}", .{payload.index.fmt(ip)}); }, else => {}, } @@ -4366,14 +4365,14 @@ fn addReferencedTypes( const is_escaped_identifier = tree.source[tree.tokens.items(.start)[name_token]] == '@'; if (is_escaped_identifier) return null; const primitive = Analyser.resolvePrimitiveType(name) orelse return null; - return try std.fmt.allocPrint(allocator, "{}", .{primitive.fmt(analyser.ip.*)}); + return try std.fmt.allocPrint(allocator, "{}", .{primitive.fmt(analyser.ip)}); }, else => {}, // TODO: Implement more "other" type expressions; better safe than sorry }, .ip_index => |payload| { - return try std.fmt.allocPrint(allocator, "{}", .{payload.index.fmt(analyser.ip.*)}); + return try std.fmt.allocPrint(allocator, "{}", .{payload.index.fmt(analyser.ip)}); }, .either => {}, // TODO diff --git a/src/features/completions.zig b/src/features/completions.zig index 10375cad5..86e8968f2 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -90,7 +90,6 @@ fn typeToCompletion( list, analyser.ip, payload.index, - type_handle.type.is_type_val, payload.node, ), .either => |bruh| { diff --git a/src/features/semantic_tokens.zig b/src/features/semantic_tokens.zig index 099dd4978..2574c8a7a 100644 --- a/src/features/semantic_tokens.zig +++ b/src/features/semantic_tokens.zig @@ -1001,7 +1001,7 @@ fn writeIdentifier(builder: *Builder, name_token: Ast.Node.Index) error{OutOfMem if (!is_escaped_identifier) { if (std.mem.eql(u8, name, "_")) return; if (Analyser.resolvePrimitiveType(name)) |primitive| { - const is_type = builder.analyser.ip.indexToKey(primitive).typeOf() == .type_type; + const is_type = builder.analyser.ip.typeOf(primitive) == .type_type; return try writeToken(builder, name_token, if (is_type) .type else .keywordLiteral); } } diff --git a/src/stage2/AstGen.zig b/src/stage2/AstGen.zig index 87aa4fc3d..908358892 100644 --- a/src/stage2/AstGen.zig +++ b/src/stage2/AstGen.zig @@ -1664,7 +1664,7 @@ fn structInitExpr( return rvalue(gz, ri, val, node); }, .none, .ref, .inferred_ptr => { - return rvalue(gz, ri, .empty_struct_type, node); + return rvalue(gz, ri, .empty_aggregate, node); }, .destructure => |destructure| { return astgen.failNodeNotes(node, "empty initializer cannot be destructured", .{}, &.{ diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig index da42399ac..faf3bca40 100644 --- a/tests/language_features/comptime_interpreter.zig +++ b/tests/language_features/comptime_interpreter.zig @@ -134,7 +134,7 @@ test "ComptimeInterpreter - variable lookup" { defer context.deinit(); const result = try context.interpret(context.findVar("bar")); - try expectEqualKey(context.interpreter.ip.*, .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 3 } }, result.val); + try std.testing.expect(result.val.?.eql(Key{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 3 } })); } test "ComptimeInterpreter - field access" { @@ -240,7 +240,7 @@ test "ComptimeInterpreter - call return struct" { try std.testing.expectEqual(std.builtin.Type.ContainerLayout.Auto, struct_info.layout); try std.testing.expectEqual(@as(usize, 1), struct_info.fields.count()); - try std.testing.expectEqualStrings("slay", struct_info.fields.keys()[0]); + try std.testing.expectFmt("slay", "{}", .{context.interpreter.ip.fmtId(struct_info.fields.keys()[0])}); try std.testing.expect(struct_info.fields.values()[0].ty == Index.bool_type); } @@ -260,7 +260,7 @@ test "ComptimeInterpreter - call comptime argument" { }}); try std.testing.expect(result1.ty == .simple_type); try std.testing.expect(result1.ty.simple_type == .type); - try std.testing.expectEqual(Key{ .int_type = .{ .signedness = .unsigned, .bits = 8 } }, result1.val.?); + try std.testing.expect(result1.val.?.eql(Key{ .int_type = .{ .signedness = .unsigned, .bits = 8 } })); const result2 = try context.call(context.findFn("Foo"), &.{KV{ .ty = .{ .simple_type = .bool }, @@ -268,7 +268,7 @@ test "ComptimeInterpreter - call comptime argument" { }}); try std.testing.expect(result2.ty == .simple_type); try std.testing.expect(result2.ty.simple_type == .type); - try std.testing.expectEqual(Key{ .int_type = .{ .signedness = .unsigned, .bits = 69 } }, result2.val.?); + try std.testing.expect(result2.val.?.eql(Key{ .int_type = .{ .signedness = .unsigned, .bits = 69 } })); } test "ComptimeInterpreter - call inner function" { @@ -380,12 +380,12 @@ const Context = struct { const namespace: ComptimeInterpreter.Namespace.Index = @enumFromInt(0); // root namespace const result = (try self.interpreter.call(namespace, func_node, args, .{})).result; - const val = self.interpreter.ip.indexToKey(result.value.index); - const ty = self.interpreter.ip.indexToKey(val.typeOf()); + const val = result.value.index; + const ty = self.interpreter.ip.typeOf(val); return KV{ - .ty = ty, - .val = val, + .ty = self.interpreter.ip.indexToKey(ty), + .val = self.interpreter.ip.indexToKey(val), }; } @@ -393,12 +393,12 @@ const Context = struct { const namespace: ComptimeInterpreter.Namespace.Index = @enumFromInt(0); // root namespace const result = try (try self.interpreter.interpret(node, namespace, .{})).getValue(); - const val = self.interpreter.ip.indexToKey(result.index); - const ty = self.interpreter.ip.indexToKey(val.typeOf()); + const val = result.index; + const ty = self.interpreter.ip.typeOf(val); return KV{ - .ty = ty, - .val = val, + .ty = self.interpreter.ip.indexToKey(ty), + .val = self.interpreter.ip.indexToKey(val), }; } @@ -438,8 +438,12 @@ fn testCall( const result = try context.call(context.findFn("Foo"), arguments); - try expectEqualKey(context.interpreter.ip.*, Key{ .simple_type = .type }, result.ty); - try expectEqualKey(context.interpreter.ip.*, expected_ty, result.val); + const ty = try context.ip.get(allocator, result.ty); + const val = if (result.val) |key| try context.ip.get(allocator, key) else .none; + const expected_ty_index = try context.ip.get(allocator, expected_ty); + + try expectEqualIndex(context.interpreter.ip, .type_type, ty); + try expectEqualIndex(context.interpreter.ip, expected_ty_index, val); } fn testExpr( @@ -456,19 +460,16 @@ fn testExpr( const result = try context.interpret(context.findVar("foobarbaz")); - try expectEqualKey(context.interpreter.ip.*, expected, result.val); + const expected_index = try context.ip.get(allocator, expected); + const val = if (result.val) |key| try context.ip.get(allocator, key) else .none; + + try expectEqualIndex(context.interpreter.ip, expected_index, val); } -fn expectEqualKey(ip: InternPool, expected: Key, actual: ?Key) !void { - if (actual) |actual_key| { - if (!expected.eql(actual_key)) { - std.debug.print("expected `{}`, found `{}`\n", .{ expected.fmt(ip), actual_key.fmt(ip) }); - return error.TestExpectedEqual; - } - } else { - std.debug.print("expected `{}`, found null\n", .{expected.fmt(ip)}); - return error.TestExpectedEqual; - } +fn expectEqualIndex(ip: *InternPool, expected: Index, actual: Index) !void { + if (expected == actual) return; + std.debug.print("expected `{}`, found `{}`\n", .{ expected.fmtDebug(ip), actual.fmtDebug(ip) }); + return error.TestExpectedEqual; } fn reportErrors(interpreter: *ComptimeInterpreter) void { From f70cedba7bd1ba695fbce48f0ec117fe36377969 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Fri, 10 Nov 2023 21:17:28 +0100 Subject: [PATCH 03/30] stop comptime interpreter from always crashing Don't worry, it is still going to crash easily (just like ZLS in general) --- src/ComptimeInterpreter.zig | 32 +++++++++++++++++++++----------- src/analysis.zig | 20 +++++++++++++------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 328e2d323..e1342635d 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -25,6 +25,8 @@ ip: *InternPool, document_store: *DocumentStore, uri: DocumentStore.Uri, namespaces: std.MultiArrayList(Namespace) = .{}, +has_analyzed_root: bool = false, +mutex: std.Thread.Mutex = .{}, pub fn getHandle(interpreter: *ComptimeInterpreter) *DocumentStore.Handle { // This interpreter is loaded from a known-valid handle so a valid handle must exist @@ -892,18 +894,26 @@ pub fn interpret( const import_handle = interpreter.document_store.getOrLoadHandle(import_uri) orelse return error.ImportFailure; const import_interpreter = try import_handle.getComptimeInterpreter(interpreter.document_store, interpreter.ip); - return import_interpreter.interpret(0, .none, options) catch |err| { - log.err("Failed to interpret node: {s}", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); + if (import_interpreter.mutex.tryLock()) { + defer import_interpreter.mutex.unlock(); + + if (!import_interpreter.has_analyzed_root) { + interpreter.has_analyzed_root = true; + _ = import_interpreter.interpret(0, .none, .{}) catch |err| { + log.err("Failed to interpret file: {s}", .{@errorName(err)}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + }; } - return InterpretResult{ - .value = Value{ - .interpreter = import_interpreter, - .node_idx = 0, - .index = .unknown_type, - }, - }; + } + + return InterpretResult{ + .value = Value{ + .interpreter = import_interpreter, + .node_idx = 0, + .index = .unknown_type, + }, }; } diff --git a/src/analysis.zig b/src/analysis.zig index ad070486f..096870110 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1223,13 +1223,19 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e log.info("Invoking interpreter!", .{}); const interpreter = try handle.getComptimeInterpreter(analyser.store, analyser.ip); - _ = interpreter.interpret(0, .none, .{}) catch |err| { - log.err("Failed to interpret file: {s}", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - return null; - }; + interpreter.mutex.lock(); + defer interpreter.mutex.unlock(); + + if (!interpreter.has_analyzed_root) { + interpreter.has_analyzed_root = true; + _ = interpreter.interpret(0, .none, .{}) catch |err| { + log.err("Failed to interpret file: {s}", .{@errorName(err)}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return null; + }; + } const root_namespace: ComptimeInterpreter.Namespace.Index = @enumFromInt(0); From 7cff6bff15074391a589489bd0920bd968525574 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:53:42 +0100 Subject: [PATCH 04/30] remove some unnecessary calls to `@as` builtin --- src/analyser/InternPool.zig | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index a5a94bd72..2c7323254 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -210,7 +210,7 @@ pub const Decl = struct { _, pub fn toOptional(i: Decl.Index) OptionalIndex { - return @as(OptionalIndex, @enumFromInt(@intFromEnum(i))); + return @enumFromInt(@intFromEnum(i)); } }; @@ -224,7 +224,7 @@ pub const Decl = struct { pub fn unwrap(oi: OptionalIndex) ?Decl.Index { if (oi == .none) return null; - return @as(Decl.Index, @enumFromInt(@intFromEnum(oi))); + return @enumFromInt(@intFromEnum(oi)); } }; }; @@ -792,14 +792,8 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .simple_type => .{ .simple_type = @enumFromInt(data) }, .simple_value => .{ .simple_value = @enumFromInt(data) }, - .type_int_signed => .{ .int_type = .{ - .signedness = .signed, - .bits = @as(u16, @intCast(data)), - } }, - .type_int_unsigned => .{ .int_type = .{ - .signedness = .unsigned, - .bits = @as(u16, @intCast(data)), - } }, + .type_int_signed => .{ .int_type = .{ .signedness = .signed, .bits = @intCast(data) } }, + .type_int_unsigned => .{ .int_type = .{ .signedness = .unsigned, .bits = @intCast(data) } }, .type_pointer => .{ .pointer_type = ip.extraData(Pointer, data) }, .type_array => .{ .array_type = ip.extraData(Array, data) }, .type_optional => .{ .optional_type = .{ .payload_type = @enumFromInt(data) } }, @@ -840,16 +834,16 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .aggregate => .{ .aggregate = ip.extraData(Aggregate, data) }, .union_value => .{ .union_value = ip.extraData(UnionValue, data) }, .error_value => .{ .error_value = ip.extraData(ErrorValue, data) }, - .null_value => .{ .null_value = .{ .ty = @as(Index, @enumFromInt(data)) } }, - .undefined_value => .{ .undefined_value = .{ .ty = @as(Index, @enumFromInt(data)) } }, - .unknown_value => .{ .unknown_value = .{ .ty = @as(Index, @enumFromInt(data)) } }, + .null_value => .{ .null_value = .{ .ty = @enumFromInt(data) } }, + .undefined_value => .{ .undefined_value = .{ .ty = @enumFromInt(data) } }, + .unknown_value => .{ .unknown_value = .{ .ty = @enumFromInt(data) } }, }; } pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { const adapter: KeyAdapter = .{ .ip = ip }; const gop = try ip.map.getOrPutAdapted(gpa, key, adapter); - if (gop.found_existing) return @as(Index, @enumFromInt(gop.index)); + if (gop.found_existing) return @enumFromInt(gop.index); const tag: Tag = key.tag(); const data: u32 = switch (key) { @@ -942,7 +936,7 @@ fn addExtra(ip: *InternPool, gpa: Allocator, extra: anytype) Allocator.Error!u32 @compileError(@typeName(T) ++ " fits into a u32! Consider directly storing this extra in Item's data field"); }; - const result = @as(u32, @intCast(ip.extra.items.len)); + const result: u32 = @intCast(ip.extra.items.len); var managed = ip.extra.toManaged(gpa); defer ip.extra = managed.moveToUnmanaged(); try encoding.encode(&managed, T, extra); @@ -959,7 +953,7 @@ const KeyAdapter = struct { pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool { _ = b_void; - return a.eql(ctx.ip.indexToKey(@as(Index, @enumFromInt(b_map_index)))); + return a.eql(ctx.ip.indexToKey(@enumFromInt(b_map_index))); } pub fn hash(ctx: @This(), a: Key) u32 { From a73ca3a782ed80d6328eb7745ac52993c54477eb Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:14:01 +0100 Subject: [PATCH 05/30] simplify `InternPool.isUnknown` --- src/analyser/InternPool.zig | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 2c7323254..d0d7aa08d 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -2534,12 +2534,8 @@ pub fn isType(ip: *const InternPool, ty: Index) bool { pub fn isUnknown(ip: *const InternPool, index: Index) bool { switch (index) { - .unknown_type => return true, - .unknown_unknown => return true, - else => switch (ip.items.items(.tag)[@intFromEnum(index)]) { - .unknown_value => return true, - else => return false, - }, + .unknown_type, .unknown_unknown => return true, + else => return ip.items.items(.tag)[@intFromEnum(index)] == .unknown_value, } } From 8a852a1820ce1296bc77f4efe7c59bdbb3232c4d Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:37:54 +0100 Subject: [PATCH 06/30] avoid calls to `extraData` in `InternPool.typeOf` --- src/analyser/InternPool.zig | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index d0d7aa08d..6c3e71b36 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -2474,15 +2474,19 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { .float_f128 => .f128_type, .float_comptime => .comptime_float_type, - .int_u64 => ip.extraData(U64Value, data).ty, - .int_i64 => ip.extraData(I64Value, data).ty, - .int_big_positive, .int_big_negative => ip.extraData(BigIntInternal, data).ty, - .optional_value => ip.extraData(OptionalValue, data).ty, - .aggregate => ip.extraData(Aggregate, data).ty, - .slice => ip.extraData(Slice, data).ty, - .union_value => ip.extraData(UnionValue, data).ty, - .error_value => ip.extraData(ErrorValue, data).ty, + // the type is stored as the first entry in the extra + .int_u64, + .int_i64, + .int_big_positive, + .int_big_negative, + .optional_value, + .aggregate, + .slice, + .union_value, + .error_value, + => std.mem.bytesToValue(Index, ip.extra.items[ip.items.items(.data)[@intFromEnum(index)]..][0..@sizeOf(Index)]), + // the type is the `data` field .null_value, .undefined_value, .unknown_value, From be3321bb18223ca0bf9630a6588157c2c3495b80 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:15:33 +0100 Subject: [PATCH 07/30] fix typo in `InternPool.deepHash` --- src/analyser/InternPool.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 6c3e71b36..d5228f22e 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -1021,7 +1021,7 @@ fn deepHash(hasher: anytype, key: anytype) void { switch (@typeInfo(T)) { .Int => { - if (comptime std.meta.hasUniqueRepresentation(Tuple)) { + if (comptime std.meta.hasUniqueRepresentation(T)) { hasher.update(std.mem.asBytes(&key)); } else { const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(T), 8) catch unreachable; From 83c54e6c9b27cf74cb0f04c9c4e08a51de0ee143 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:33:31 +0100 Subject: [PATCH 08/30] move `InternPool.Key` sub-types into `InternPool.Key` --- src/analyser/InternPool.zig | 494 ++++++++++++++++++------------------ 1 file changed, 246 insertions(+), 248 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index d5228f22e..0108f1e79 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -23,217 +23,6 @@ pub const SPString = StringPool.String; const encoding = @import("encoding.zig"); const ErrorMsg = @import("error_msg.zig").ErrorMsg; -pub const Pointer = struct { - elem_type: Index, - sentinel: Index = .none, - size: std.builtin.Type.Pointer.Size, - alignment: u16 = 0, - bit_offset: u16 = 0, - host_size: u16 = 0, - is_const: bool = false, - is_volatile: bool = false, - is_allowzero: bool = false, - address_space: std.builtin.AddressSpace = .generic, -}; - -pub const Array = struct { - len: u64, - child: Index, - sentinel: Index = .none, -}; - -pub const FieldStatus = enum { - none, - field_types_wip, - have_field_types, - layout_wip, - have_layout, - fully_resolved_wip, - fully_resolved, -}; - -pub const StructIndex = enum(u32) { _ }; - -pub const Struct = struct { - fields: std.AutoArrayHashMapUnmanaged(SPString, Field), - owner_decl: Decl.OptionalIndex, - namespace: NamespaceIndex, - layout: std.builtin.Type.ContainerLayout = .Auto, - backing_int_ty: Index, - status: FieldStatus, - - pub const Field = struct { - ty: Index, - default_value: Index = .none, - alignment: u16 = 0, - is_comptime: bool = false, - }; -}; - -pub const Optional = struct { - payload_type: Index, -}; - -pub const ErrorUnion = struct { - // .none if inferred error set - error_set_type: Index, - payload_type: Index, -}; - -pub const ErrorSet = struct { - owner_decl: Decl.OptionalIndex, - names: []const SPString, -}; - -pub const EnumIndex = enum(u32) { _ }; - -pub const Enum = struct { - tag_type: Index, - fields: std.AutoArrayHashMapUnmanaged(SPString, void), - values: std.AutoArrayHashMapUnmanaged(Index, void), - namespace: NamespaceIndex, - tag_type_inferred: bool, -}; - -pub const Function = struct { - args: []const Index, - /// zig only lets the first 32 arguments be `comptime` - args_is_comptime: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), - /// zig only lets the first 32 arguments be generic - args_is_generic: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), - /// zig only lets the first 32 arguments be `noalias` - args_is_noalias: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), - return_type: Index, - alignment: u16 = 0, - calling_convention: std.builtin.CallingConvention = .Unspecified, - is_generic: bool = false, - is_var_args: bool = false, -}; - -pub const UnionIndex = enum(u32) { _ }; - -pub const Union = struct { - tag_type: Index, - fields: std.AutoArrayHashMapUnmanaged(SPString, Field), - namespace: NamespaceIndex, - layout: std.builtin.Type.ContainerLayout = .Auto, - status: FieldStatus, - - pub const Field = struct { - ty: Index, - alignment: u16, - }; -}; - -pub const Tuple = struct { - types: []const Index, - /// Index.none elements are used to indicate runtime-known. - values: []const Index, -}; - -pub const Vector = struct { - len: u32, - child: Index, -}; - -pub const AnyFrame = struct { - child: Index, -}; - -const U64Value = struct { - ty: Index, - int: u64, -}; - -const I64Value = struct { - ty: Index, - int: i64, -}; - -pub const BigInt = struct { - ty: Index, - int: std.math.big.int.Const, -}; - -pub const OptionalValue = struct { - ty: Index, - val: Index, -}; - -pub const Slice = struct { - ty: Index, - ptr: Index, - len: Index, -}; - -pub const Aggregate = struct { - ty: Index, - values: []const Index, -}; - -pub const UnionValue = struct { - ty: Index, - field_index: u32, - val: Index, -}; - -pub const ErrorValue = struct { - ty: Index, - error_tag_name: SPString, -}; - -pub const NullValue = struct { - ty: Index, -}; - -pub const UndefinedValue = struct { - ty: Index, -}; - -pub const UnknownValue = struct { - /// asserts that this is not .type_type because that is a the same as .unknown_type - ty: Index, -}; - -pub const Decl = struct { - name: SPString, - node_idx: u32, - /// this stores both the type and the value - index: InternPool.Index, - alignment: u16, - address_space: std.builtin.AddressSpace, - src_namespace: InternPool.NamespaceIndex, - is_pub: bool, - is_exported: bool, - - pub const Index = enum(u32) { - _, - - pub fn toOptional(i: Decl.Index) OptionalIndex { - return @enumFromInt(@intFromEnum(i)); - } - }; - - pub const OptionalIndex = enum(u32) { - none = std.math.maxInt(u32), - _, - - pub fn init(oi: ?Decl.Index) OptionalIndex { - return if (oi) |index| index.toOptional() else .none; - } - - pub fn unwrap(oi: OptionalIndex) ?Decl.Index { - if (oi == .none) return null; - return @enumFromInt(@intFromEnum(oi)); - } - }; -}; - -const BigIntInternal = struct { - ty: Index, - limbs: []const std.math.big.Limb, -}; - pub const Key = union(enum) { simple_type: SimpleType, simple_value: SimpleValue, @@ -241,13 +30,13 @@ pub const Key = union(enum) { int_type: std.builtin.Type.Int, pointer_type: Pointer, array_type: Array, - struct_type: StructIndex, + struct_type: Struct.Index, optional_type: Optional, error_union_type: ErrorUnion, error_set_type: ErrorSet, - enum_type: EnumIndex, + enum_type: Enum.Index, function_type: Function, - union_type: UnionIndex, + union_type: Union.Index, tuple_type: Tuple, vector_type: Vector, anyframe_type: AnyFrame, @@ -270,9 +59,132 @@ pub const Key = union(enum) { null_value: NullValue, undefined_value: UndefinedValue, unknown_value: UnknownValue, - // error union + pub const Pointer = struct { + elem_type: Index, + sentinel: Index = .none, + size: std.builtin.Type.Pointer.Size, + alignment: u16 = 0, + bit_offset: u16 = 0, + host_size: u16 = 0, + is_const: bool = false, + is_volatile: bool = false, + is_allowzero: bool = false, + address_space: std.builtin.AddressSpace = .generic, + }; + + pub const Array = struct { + len: u64, + child: Index, + sentinel: Index = .none, + }; + + pub const Optional = struct { + payload_type: Index, + }; + + pub const ErrorUnion = struct { + // .none if inferred error set + error_set_type: Index, + payload_type: Index, + }; + + pub const ErrorSet = struct { + owner_decl: Decl.OptionalIndex, + names: []const SPString, + }; + + pub const Function = struct { + args: []const Index, + /// zig only lets the first 32 arguments be `comptime` + args_is_comptime: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), + /// zig only lets the first 32 arguments be generic + args_is_generic: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), + /// zig only lets the first 32 arguments be `noalias` + args_is_noalias: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), + return_type: Index, + alignment: u16 = 0, + calling_convention: std.builtin.CallingConvention = .Unspecified, + is_generic: bool = false, + is_var_args: bool = false, + }; + + pub const Tuple = struct { + types: []const Index, + /// Index.none elements are used to indicate runtime-known. + values: []const Index, + }; + + pub const Vector = struct { + len: u32, + child: Index, + }; + + pub const AnyFrame = struct { + child: Index, + }; + + const U64Value = struct { + ty: Index, + int: u64, + }; + + const I64Value = struct { + ty: Index, + int: i64, + }; + + pub const BigInt = struct { + ty: Index, + int: std.math.big.int.Const, + }; + + pub const OptionalValue = struct { + ty: Index, + val: Index, + }; + + pub const Slice = struct { + ty: Index, + ptr: Index, + len: Index, + }; + + pub const Aggregate = struct { + ty: Index, + values: []const Index, + }; + + pub const UnionValue = struct { + ty: Index, + field_index: u32, + val: Index, + }; + + pub const ErrorValue = struct { + ty: Index, + error_tag_name: SPString, + }; + + pub const NullValue = struct { + ty: Index, + }; + + pub const UndefinedValue = struct { + ty: Index, + }; + + pub const UnknownValue = struct { + /// asserts that this is not .type_type because that is a the same as .unknown_type + ty: Index, + }; + + const BigIntInternal = struct { + ty: Index, + limbs: []const std.math.big.Limb, + }; + pub fn eql(a: Key, b: Key) bool { return deepEql(a, b); } @@ -465,11 +377,6 @@ comptime { assert(@intFromEnum(Zir.Inst.Ref.one_usize) == @intFromEnum(Index.one_usize)); } -pub const NamespaceIndex = enum(u32) { - none = std.math.maxInt(u32), - _, -}; - pub const Tag = enum(u8) { /// A type that can be represented with only an enum tag. /// data is SimpleType enum value @@ -642,6 +549,97 @@ comptime { assert(@sizeOf(SimpleType) == @sizeOf(SimpleValue)); } +pub const NamespaceIndex = enum(u32) { + none = std.math.maxInt(u32), + _, +}; + +pub const Decl = struct { + name: SPString, + node_idx: std.zig.Ast.Node.Index, + /// this stores both the type and the value + index: InternPool.Index, + alignment: u16, + address_space: std.builtin.AddressSpace, + src_namespace: InternPool.NamespaceIndex, + is_pub: bool, + is_exported: bool, + + pub const Index = enum(u32) { + _, + + pub fn toOptional(i: Decl.Index) OptionalIndex { + return @enumFromInt(@intFromEnum(i)); + } + }; + + pub const OptionalIndex = enum(u32) { + none = std.math.maxInt(u32), + _, + + pub fn init(oi: ?Decl.Index) OptionalIndex { + return if (oi) |index| index.toOptional() else .none; + } + + pub fn unwrap(oi: OptionalIndex) ?Decl.Index { + if (oi == .none) return null; + return @enumFromInt(@intFromEnum(oi)); + } + }; +}; +pub const FieldStatus = enum { + none, + field_types_wip, + have_field_types, + layout_wip, + have_layout, + fully_resolved_wip, + fully_resolved, +}; + +pub const Struct = struct { + fields: std.AutoArrayHashMapUnmanaged(SPString, Field), + owner_decl: Decl.OptionalIndex, + namespace: NamespaceIndex, + layout: std.builtin.Type.ContainerLayout = .Auto, + backing_int_ty: InternPool.Index, + status: FieldStatus, + + pub const Index = enum(u32) { _ }; + + pub const Field = struct { + ty: InternPool.Index, + default_value: InternPool.Index = .none, + alignment: u16 = 0, + is_comptime: bool = false, + }; +}; + +pub const Enum = struct { + tag_type: InternPool.Index, + fields: std.AutoArrayHashMapUnmanaged(SPString, void), + values: std.AutoArrayHashMapUnmanaged(InternPool.Index, void), + namespace: NamespaceIndex, + tag_type_inferred: bool, + + pub const Index = enum(u32) { _ }; +}; + +pub const Union = struct { + tag_type: InternPool.Index, + fields: std.AutoArrayHashMapUnmanaged(SPString, Field), + namespace: NamespaceIndex, + layout: std.builtin.Type.ContainerLayout = .Auto, + status: FieldStatus, + + pub const Field = struct { + ty: InternPool.Index, + alignment: u16, + }; + + pub const Index = enum(u32) { _ }; +}; + pub fn init(gpa: Allocator) Allocator.Error!InternPool { var ip: InternPool = .{}; errdefer ip.deinit(gpa); @@ -737,7 +735,7 @@ pub fn init(gpa: Allocator) Allocator.Error!InternPool { .{ .index = .unknown_unknown, .key = .{ .unknown_value = .{ .ty = .unknown_type } } }, }; - const extra_count = 6 * @sizeOf(Pointer) + @sizeOf(ErrorUnion) + 4 * @sizeOf(Function) + 8 * @sizeOf(InternPool.U64Value) + @sizeOf(InternPool.Aggregate); + const extra_count = 6 * @sizeOf(Key.Pointer) + @sizeOf(Key.ErrorUnion) + 4 * @sizeOf(Key.Function) + 8 * @sizeOf(Key.U64Value) + @sizeOf(Key.Aggregate); try ip.map.ensureTotalCapacity(gpa, items.len); try ip.items.ensureTotalCapacity(gpa, items.len); @@ -794,26 +792,26 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .type_int_signed => .{ .int_type = .{ .signedness = .signed, .bits = @intCast(data) } }, .type_int_unsigned => .{ .int_type = .{ .signedness = .unsigned, .bits = @intCast(data) } }, - .type_pointer => .{ .pointer_type = ip.extraData(Pointer, data) }, - .type_array => .{ .array_type = ip.extraData(Array, data) }, + .type_pointer => .{ .pointer_type = ip.extraData(Key.Pointer, data) }, + .type_array => .{ .array_type = ip.extraData(Key.Array, data) }, .type_optional => .{ .optional_type = .{ .payload_type = @enumFromInt(data) } }, .type_anyframe => .{ .anyframe_type = .{ .child = @enumFromInt(data) } }, - .type_error_union => .{ .error_union_type = ip.extraData(ErrorUnion, data) }, - .type_error_set => .{ .error_set_type = ip.extraData(ErrorSet, data) }, - .type_function => .{ .function_type = ip.extraData(Function, data) }, - .type_tuple => .{ .tuple_type = ip.extraData(Tuple, data) }, - .type_vector => .{ .vector_type = ip.extraData(Vector, data) }, + .type_error_union => .{ .error_union_type = ip.extraData(Key.ErrorUnion, data) }, + .type_error_set => .{ .error_set_type = ip.extraData(Key.ErrorSet, data) }, + .type_function => .{ .function_type = ip.extraData(Key.Function, data) }, + .type_tuple => .{ .tuple_type = ip.extraData(Key.Tuple, data) }, + .type_vector => .{ .vector_type = ip.extraData(Key.Vector, data) }, .type_struct => .{ .struct_type = @enumFromInt(data) }, .type_enum => .{ .enum_type = @enumFromInt(data) }, .type_union => .{ .union_type = @enumFromInt(data) }, - .int_u64 => .{ .int_u64_value = ip.extraData(U64Value, data) }, - .int_i64 => .{ .int_i64_value = ip.extraData(I64Value, data) }, + .int_u64 => .{ .int_u64_value = ip.extraData(Key.U64Value, data) }, + .int_i64 => .{ .int_i64_value = ip.extraData(Key.I64Value, data) }, .int_big_positive, .int_big_negative, => .{ .int_big_value = blk: { - const big_int = ip.extraData(BigIntInternal, data); + const big_int = ip.extraData(Key.BigIntInternal, data); break :blk .{ .ty = big_int.ty, .int = .{ @@ -829,11 +827,11 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .float_f128 => .{ .float_128_value = ip.extraData(f128, data) }, .float_comptime => .{ .float_comptime_value = ip.extraData(f128, data) }, - .optional_value => .{ .optional_value = ip.extraData(OptionalValue, data) }, - .slice => .{ .slice = ip.extraData(Slice, data) }, - .aggregate => .{ .aggregate = ip.extraData(Aggregate, data) }, - .union_value => .{ .union_value = ip.extraData(UnionValue, data) }, - .error_value => .{ .error_value = ip.extraData(ErrorValue, data) }, + .optional_value => .{ .optional_value = ip.extraData(Key.OptionalValue, data) }, + .slice => .{ .slice = ip.extraData(Key.Slice, data) }, + .aggregate => .{ .aggregate = ip.extraData(Key.Aggregate, data) }, + .union_value => .{ .union_value = ip.extraData(Key.UnionValue, data) }, + .error_value => .{ .error_value = ip.extraData(Key.ErrorValue, data) }, .null_value => .{ .null_value = .{ .ty = @enumFromInt(data) } }, .undefined_value => .{ .undefined_value = .{ .ty = @enumFromInt(data) } }, .unknown_value => .{ .unknown_value = .{ .ty = @enumFromInt(data) } }, @@ -860,7 +858,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { .int_u64_value => |int_val| try ip.addExtra(gpa, int_val), .int_i64_value => |int_val| try ip.addExtra(gpa, int_val), - .int_big_value => |big_int_val| try ip.addExtra(gpa, BigIntInternal{ + .int_big_value => |big_int_val| try ip.addExtra(gpa, Key.BigIntInternal{ .ty = big_int_val.ty, .limbs = big_int_val.int.limbs, }), @@ -894,38 +892,38 @@ pub fn getDecl(ip: *const InternPool, index: InternPool.Decl.Index) *const Inter pub fn getDeclMut(ip: *InternPool, index: InternPool.Decl.Index) *InternPool.Decl { return ip.decls.at(@intFromEnum(index)); } -pub fn getStruct(ip: *const InternPool, index: InternPool.StructIndex) *const InternPool.Struct { +pub fn getStruct(ip: *const InternPool, index: Struct.Index) *const Struct { return ip.structs.at(@intFromEnum(index)); } -pub fn getStructMut(ip: *InternPool, index: InternPool.StructIndex) *InternPool.Struct { +pub fn getStructMut(ip: *InternPool, index: Struct.Index) *Struct { return ip.structs.at(@intFromEnum(index)); } -pub fn getEnum(ip: *const InternPool, index: InternPool.EnumIndex) *const InternPool.Enum { +pub fn getEnum(ip: *const InternPool, index: Enum.Index) *const Enum { return ip.enums.at(@intFromEnum(index)); } -pub fn getEnumMut(ip: *InternPool, index: InternPool.EnumIndex) *InternPool.Enum { +pub fn getEnumMut(ip: *InternPool, index: Enum.Index) *Enum { return ip.enums.at(@intFromEnum(index)); } -pub fn getUnion(ip: *const InternPool, index: InternPool.UnionIndex) *const InternPool.Union { +pub fn getUnion(ip: *const InternPool, index: Union.Index) *const Union { return ip.unions.at(@intFromEnum(index)); } -pub fn getUnionMut(ip: *InternPool, index: InternPool.UnionIndex) *InternPool.Union { +pub fn getUnionMut(ip: *InternPool, index: Union.Index) *Union { return ip.unions.at(@intFromEnum(index)); } -pub fn createDecl(ip: *InternPool, gpa: Allocator, decl: InternPool.Decl) Allocator.Error!InternPool.Decl.Index { +pub fn createDecl(ip: *InternPool, gpa: Allocator, decl: Decl) Allocator.Error!Decl.Index { try ip.decls.append(gpa, decl); return @enumFromInt(ip.decls.count() - 1); } -pub fn createStruct(ip: *InternPool, gpa: Allocator, struct_info: InternPool.Struct) Allocator.Error!InternPool.StructIndex { +pub fn createStruct(ip: *InternPool, gpa: Allocator, struct_info: Struct) Allocator.Error!Struct.Index { try ip.structs.append(gpa, struct_info); return @enumFromInt(ip.structs.count() - 1); } -pub fn createEnum(ip: *InternPool, gpa: Allocator, enum_info: InternPool.Enum) Allocator.Error!InternPool.EnumIndex { +pub fn createEnum(ip: *InternPool, gpa: Allocator, enum_info: Enum) Allocator.Error!Enum.Index { try ip.enums.append(gpa, enum_info); return @enumFromInt(ip.enums.count() - 1); } -pub fn createUnion(ip: *InternPool, gpa: Allocator, union_info: InternPool.Union) Allocator.Error!InternPool.UnionIndex { +pub fn createUnion(ip: *InternPool, gpa: Allocator, union_info: Union) Allocator.Error!Union.Index { try ip.unions.append(gpa, union_info); return @enumFromInt(ip.unions.count() - 1); } @@ -1745,7 +1743,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t // turn []T => []const T switch (ip.indexToKey(chosen)) { .error_union_type => |error_union_info| { - var info: Pointer = ip.indexToKey(error_union_info.payload_type).pointer_type; + var info = ip.indexToKey(error_union_info.payload_type).pointer_type; info.is_const = true; const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); From 6129aa2475f64b6d1a30afe98d38c8c6c60dd011 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Mon, 13 Nov 2023 18:29:48 +0100 Subject: [PATCH 09/30] add TODO for representing arrays and vectors with unknown length --- src/analyser/InternPool.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 0108f1e79..fd1bb199e 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -75,6 +75,7 @@ pub const Key = union(enum) { }; pub const Array = struct { + // TODO convert this into an enum and have `std.math.maxInt(u64)` represent unknown length, len: u64, child: Index, sentinel: Index = .none, @@ -117,6 +118,7 @@ pub const Key = union(enum) { }; pub const Vector = struct { + // TODO convert this into an enum and have `std.math.maxInt(u32)` represent unknown length, len: u32, child: Index, }; From 201fc031ae008c888549ee902aba490010383e15 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:41:12 +0100 Subject: [PATCH 10/30] update some documentation in InternPool --- src/analyser/InternPool.zig | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index fd1bb199e..956334643 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -403,7 +403,7 @@ pub const Tag = enum(u8) { /// data is payload to Struct. type_struct, /// An optional type. - /// data is index to type + /// data is the child/payload type type_optional, /// An error union type. /// data is payload to ErrorUnion. @@ -869,7 +869,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { .null_value => |null_val| @intFromEnum(null_val.ty), .undefined_value => |undefined_val| @intFromEnum(undefined_val.ty), .unknown_value => |unknown_val| blk: { - assert(unknown_val.ty != .type_type); // .unknown_type instead + assert(unknown_val.ty != .type_type); // use .unknown_type instead break :blk @intFromEnum(unknown_val.ty); }, inline else => |data| try ip.addExtra(gpa, data), @@ -2334,7 +2334,7 @@ fn optionalPtrTy(ip: *const InternPool, ty: Index) Index { } } -/// will panic in during testing else will return `value` +/// will panic in during testing, otherwise will return `value` inline fn panicOrElse(message: []const u8, value: anytype) @TypeOf(value) { if (builtin.is_test) { @panic(message); @@ -2346,6 +2346,7 @@ inline fn panicOrElse(message: []const u8, value: anytype) @TypeOf(value) { // HELPER FUNCTIONS // --------------------------------------------- +/// TODO make the return type optional and return null on unknown type. pub fn zigTypeTag(ip: *const InternPool, index: Index) std.builtin.TypeId { return switch (ip.items.items(.tag)[@intFromEnum(index)]) { .simple_type => switch (@as(SimpleType, @enumFromInt(ip.items.items(.data)[@intFromEnum(index)]))) { @@ -2802,7 +2803,7 @@ pub fn isPtrAtRuntime(ip: *const InternPool, ty: Index) bool { /// For [*c]T, returns T. /// For [N]T, returns T. /// For ?T, returns T. -/// For @vector(T, _), returns T. +/// For @vector(_, T), returns T. /// For anyframe->T, returns T. pub fn childType(ip: *const InternPool, ty: Index) Index { return switch (ip.indexToKey(ty)) { @@ -2825,7 +2826,7 @@ pub fn childType(ip: *const InternPool, ty: Index) Index { /// For [*c]T, returns T. /// For [N]T, returns T. /// For ?T, returns T. -/// For @vector(T, _), returns T. +/// For @vector(_, T), returns T. /// For anyframe->T, returns T. pub fn elemType(ip: *const InternPool, ty: Index) Index { return switch (ip.indexToKey(ty)) { @@ -3062,6 +3063,7 @@ pub fn canHaveFields(ip: *const InternPool, ty: Index) bool { }; } +/// see `std.meta.trait.isIndexable` pub fn isIndexable(ip: *const InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .array_type, .vector_type => true, From e6815d0a53c6cb23dc4d1a90ec42a02520e3b6f0 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:42:02 +0100 Subject: [PATCH 11/30] include comptime_float in `InternPool.isFloat` --- src/analyser/InternPool.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 956334643..24e3c26f8 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -2720,6 +2720,7 @@ pub fn isFloat(ip: *const InternPool, ty: Index) bool { .f64_type, .f80_type, .f128_type, + .comptime_float_type, => true, else => false, }; From 7c917b798946cb5d9926f9b8ec331f8f4a2abfab Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:42:36 +0100 Subject: [PATCH 12/30] return null when integer doesn't fit in `InternPool.toInt` --- src/analyser/InternPool.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 24e3c26f8..7441037db 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -3111,6 +3111,7 @@ pub fn isZero(ip: *const InternPool, val: Index) bool { /// If the value fits in the given integer, return it, otherwise null. pub fn toInt(ip: *const InternPool, val: Index, comptime T: type) !?T { + comptime assert(std.meta.trait.isIntegral(T)); return switch (ip.indexToKey(val)) { .simple_value => |simple| switch (simple) { .null_value => 0, @@ -3119,8 +3120,8 @@ pub fn toInt(ip: *const InternPool, val: Index, comptime T: type) !?T { .the_only_possible_value => 0, else => null, }, - .int_u64_value => |int_value| @intCast(int_value.int), - .int_i64_value => |int_value| @intCast(int_value.int), + .int_u64_value => |int_value| std.math.cast(T, int_value.int), + .int_i64_value => |int_value| std.math.cast(T, int_value.int), .int_big_value => |int_value| int_value.int.to(T) catch null, .null_value => 0, else => null, From caf4e054bf63d2cac97f6c562472cb0f8a883acf Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:43:12 +0100 Subject: [PATCH 13/30] detect wrong value for `extra_count` in `InternPool.init` --- src/analyser/InternPool.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 7441037db..59b692def 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -741,7 +741,12 @@ pub fn init(gpa: Allocator) Allocator.Error!InternPool { try ip.map.ensureTotalCapacity(gpa, items.len); try ip.items.ensureTotalCapacity(gpa, items.len); - try ip.extra.ensureTotalCapacity(gpa, extra_count); + if (builtin.is_test or builtin.mode == .Debug) { + // detect wrong value for extra_count + try ip.extra.ensureTotalCapacityPrecise(gpa, extra_count); + } else { + try ip.extra.ensureTotalCapacity(gpa, extra_count); + } for (items, 0..) |item, i| { assert(@intFromEnum(item.index) == i); From 08c78958b18e6c1a35c21717b7b0adae5a23dc5c Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:43:56 +0100 Subject: [PATCH 14/30] add assertions to `InternPool.errorSetMerge` --- src/analyser/InternPool.zig | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 59b692def..38dd3bf05 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -2549,7 +2549,7 @@ pub fn isUnknown(ip: *const InternPool, index: Index) bool { } } -pub fn isUnknownDeep(ip: *const InternPool, gpa: std.mem.Allocator, index: Index) Allocator.Error!bool { +pub fn isUnknownDeep(ip: *const InternPool, gpa: Allocator, index: Index) Allocator.Error!bool { var set = std.AutoHashMap(Index, void).init(gpa); defer set.deinit(); return try ip.isUnknownDeepInternal(index, &set); @@ -2848,7 +2848,10 @@ pub fn elemType(ip: *const InternPool, ty: Index) Index { }; } -pub fn errorSetMerge(ip: *InternPool, gpa: std.mem.Allocator, a_ty: Index, b_ty: Index) Allocator.Error!Index { +pub fn errorSetMerge(ip: *InternPool, gpa: Allocator, a_ty: Index, b_ty: Index) Allocator.Error!Index { + assert(ip.zigTypeTag(a_ty) == .ErrorSet); + assert(ip.zigTypeTag(b_ty) == .ErrorSet); + // Anything merged with anyerror is anyerror. if (a_ty == .anyerror_type or b_ty == .anyerror_type) { return .anyerror_type; From 59282d14d0424ea95728048feb7e9d2512871e90 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Wed, 22 Nov 2023 21:12:49 +0100 Subject: [PATCH 15/30] fix looking up `len` on a single pointer to array --- src/ComptimeInterpreter.zig | 83 +++++++++++++++++++----------------- src/analyser/completions.zig | 45 +++++++++++-------- 2 files changed, 72 insertions(+), 56 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index e1342635d..8ccc8e84f 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -505,45 +505,52 @@ pub fn interpret( else => {}, }, .pointer_type => |pointer_info| { - if (pointer_info.size == .Slice) { - if (std.mem.eql(u8, field_name, "ptr")) { - var many_ptr_info = InternPool.Key{ .pointer_type = pointer_info }; - many_ptr_info.pointer_type.size = .Many; - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - // TODO resolve ptr of Slice - .index = try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = try interpreter.ip.get(interpreter.allocator, many_ptr_info) }, - }), + switch (pointer_info.size) { + .Many, .C => {}, + .One => { + switch (interpreter.ip.indexToKey(pointer_info.elem_type)) { + .array_type => |array_info| { + if (std.mem.eql(u8, field_name, "len")) { + return InterpretResult{ + .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + .index = try interpreter.ip.get(interpreter.allocator, .{ .int_u64_value = .{ + .ty = .usize_type, + .int = array_info.len, + } }), + }, + }; + } }, - }; - } else if (std.mem.eql(u8, field_name, "len")) { - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - // TODO resolve length of Slice - .index = try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = Index.usize_type }, - }), - }, - }; - } - } else if (interpreter.ip.indexToKey(pointer_info.elem_type) == .array_type) { - if (std.mem.eql(u8, field_name, "len")) { - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - // TODO resolve length of Slice - .index = try interpreter.ip.get(interpreter.allocator, .{ - .unknown_value = .{ .ty = Index.usize_type }, - }), - }, - }; - } + else => {}, + } + }, + .Slice => { + if (std.mem.eql(u8, field_name, "ptr")) { + var many_ptr_info = InternPool.Key{ .pointer_type = pointer_info }; + many_ptr_info.pointer_type.size = .Many; + return InterpretResult{ + .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + // TODO resolve ptr of Slice + .index = try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = try interpreter.ip.get(interpreter.allocator, many_ptr_info) }, + }), + }, + }; + } else if (std.mem.eql(u8, field_name, "len")) { + return InterpretResult{ + .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + // TODO resolve length of Slice + .index = try interpreter.ip.getUnknown(interpreter.allocator, .usize_type), + }, + }; + } + }, } }, .array_type => |array_info| { diff --git a/src/analyser/completions.zig b/src/analyser/completions.zig index eff170d73..54e917f23 100644 --- a/src/analyser/completions.zig +++ b/src/analyser/completions.zig @@ -61,24 +61,33 @@ pub fn dotCompletions( else => {}, }, .pointer_type => |pointer_info| { - if (pointer_info.size == .Slice) { - try completions.append(arena, .{ - .label = "ptr", - .kind = .Field, - // TODO this discards pointer attributes - .detail = try std.fmt.allocPrint(arena, "ptr: [*]{}", .{pointer_info.elem_type.fmt(ip)}), - }); - try completions.append(arena, .{ - .label = "len", - .kind = .Field, - .detail = "len: usize", - }); - } else if (ip.indexToKey(pointer_info.elem_type) == .array_type) { - try completions.append(arena, .{ - .label = "len", - .kind = .Field, - .detail = "len: usize", - }); + switch (pointer_info.size) { + .Many, .C => {}, + .One => { + switch (ip.indexToKey(pointer_info.elem_type)) { + .array_type => |array_info| { + try completions.append(arena, .{ + .label = "len", + .kind = .Field, + .detail = try std.fmt.allocPrint(arena, "const len: usize ({d})", .{array_info.len}), // TODO how should this be displayed + }); + }, + else => {}, + } + }, + .Slice => { + try completions.append(arena, .{ + .label = "ptr", + .kind = .Field, + // TODO this discards pointer attributes + .detail = try std.fmt.allocPrint(arena, "ptr: [*]{}", .{pointer_info.elem_type.fmt(ip)}), + }); + try completions.append(arena, .{ + .label = "len", + .kind = .Field, + .detail = "len: usize", + }); + }, } }, .array_type => |array_info| { From 03ea94912d92e8e91e8905cdbfeb3ca7e3ecbf71 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 23 Nov 2023 00:16:20 +0100 Subject: [PATCH 16/30] pack pointer type struct --- src/ComptimeInterpreter.zig | 14 +- src/analyser/InternPool.zig | 296 +++++++++++++++++++---------------- src/analyser/completions.zig | 4 +- 3 files changed, 171 insertions(+), 143 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 8ccc8e84f..28039e36e 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -453,7 +453,7 @@ pub fn interpret( const ty = interpreter.ip.typeOf(val); const inner_ty = switch (interpreter.ip.indexToKey(ty)) { - .pointer_type => |pointer_info| if (pointer_info.size == .One) pointer_info.elem_type else ty, + .pointer_type => |pointer_info| if (pointer_info.flags.size == .One) pointer_info.elem_type else ty, else => ty, }; @@ -505,7 +505,7 @@ pub fn interpret( else => {}, }, .pointer_type => |pointer_info| { - switch (pointer_info.size) { + switch (pointer_info.flags.size) { .Many, .C => {}, .One => { switch (interpreter.ip.indexToKey(pointer_info.elem_type)) { @@ -529,7 +529,7 @@ pub fn interpret( .Slice => { if (std.mem.eql(u8, field_name, "ptr")) { var many_ptr_info = InternPool.Key{ .pointer_type = pointer_info }; - many_ptr_info.pointer_type.size = .Many; + many_ptr_info.pointer_type.flags.size = .Many; return InterpretResult{ .value = Value{ .interpreter = interpreter, @@ -1019,8 +1019,10 @@ pub fn interpret( .len = @intCast(str.len), .sentinel = .zero_u8, } }), - .size = .One, - .is_const = true, + .flags = .{ + .size = .One, + .is_const = true, + }, } }); return InterpretResult{ .value = Value{ @@ -1174,7 +1176,7 @@ pub fn interpret( const pointer_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ .elem_type = ty, - .size = .One, + .flags = .{ .size = .One }, } }); return InterpretResult{ .value = .{ diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 38dd3bf05..2a24c44d1 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -61,17 +61,26 @@ pub const Key = union(enum) { unknown_value: UnknownValue, // error union - pub const Pointer = struct { + pub const Pointer = packed struct { elem_type: Index, sentinel: Index = .none, - size: std.builtin.Type.Pointer.Size, - alignment: u16 = 0, - bit_offset: u16 = 0, - host_size: u16 = 0, - is_const: bool = false, - is_volatile: bool = false, - is_allowzero: bool = false, - address_space: std.builtin.AddressSpace = .generic, + flags: Flags, + packed_offset: PackedOffset = .{ .bit_offset = 0, .host_size = 0 }, + + pub const Flags = packed struct(u32) { + size: std.builtin.Type.Pointer.Size, + is_const: bool = false, + is_volatile: bool = false, + is_allowzero: bool = false, + address_space: std.builtin.AddressSpace = .generic, + _: u6 = 0, + alignment: u16 = 0, + }; + + pub const PackedOffset = packed struct(u32) { + bit_offset: u16, + host_size: u16, + }; }; pub const Array = struct { @@ -702,16 +711,16 @@ pub fn init(gpa: Allocator) Allocator.Error!InternPool { .{ .index = .export_options_type, .key = .{ .simple_type = .export_options } }, .{ .index = .extern_options_type, .key = .{ .simple_type = .extern_options } }, .{ .index = .type_info_type, .key = .{ .simple_type = .type_info } }, - .{ .index = .manyptr_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .size = .Many } } }, - .{ .index = .manyptr_const_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .size = .Many, .is_const = true } } }, - .{ .index = .manyptr_const_u8_sentinel_0_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .sentinel = .zero_u8, .size = .Many, .is_const = true } } }, + .{ .index = .manyptr_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .flags = .{ .size = .Many } } } }, + .{ .index = .manyptr_const_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .flags = .{ .size = .Many, .is_const = true } } } }, + .{ .index = .manyptr_const_u8_sentinel_0_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .sentinel = .zero_u8, .flags = .{ .size = .Many, .is_const = true } } } }, .{ .index = .fn_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .noreturn_type } } }, .{ .index = .fn_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type } } }, .{ .index = .fn_naked_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .calling_convention = .Naked } } }, .{ .index = .fn_ccc_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .calling_convention = .C } } }, - .{ .index = .single_const_pointer_to_comptime_int_type, .key = .{ .pointer_type = .{ .elem_type = .comptime_int_type, .size = .One, .is_const = true } } }, - .{ .index = .slice_const_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .size = .Slice, .is_const = true } } }, - .{ .index = .slice_const_u8_sentinel_0_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .sentinel = .zero_u8, .size = .Slice, .is_const = true } } }, + .{ .index = .single_const_pointer_to_comptime_int_type, .key = .{ .pointer_type = .{ .elem_type = .comptime_int_type, .flags = .{ .size = .One, .is_const = true } } } }, + .{ .index = .slice_const_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .flags = .{ .size = .Slice, .is_const = true } } } }, + .{ .index = .slice_const_u8_sentinel_0_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .sentinel = .zero_u8, .flags = .{ .size = .Slice, .is_const = true } } } }, .{ .index = .optional_noreturn_type, .key = .{ .optional_type = .{ .payload_type = .noreturn_type } } }, .{ .index = .anyerror_void_error_union_type, .key = .{ .error_union_type = .{ .error_set_type = .anyerror_type, .payload_type = .void_type } } }, .{ .index = .generic_poison_type, .key = .{ .simple_type = .generic_poison } }, @@ -1158,14 +1167,14 @@ pub fn coerce( const inst_ty_key = ip.indexToKey(inst_ty); // *T to *[1]T - if (dest_info.size == .One and ip.isSinglePointer(inst_ty)) single_item: { + if (dest_info.flags.size == .One and ip.isSinglePointer(inst_ty)) single_item: { // TODO if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer; const ptr_elem_ty = ip.indexToKey(inst_ty).pointer_type.elem_type; const array_ty = ip.indexToKey(dest_info.elem_type); if (array_ty != .array_type) break :single_item; const array_elem_ty = array_ty.array_type.child; - if (try ip.coerceInMemoryAllowed(gpa, arena, array_elem_ty, ptr_elem_ty, dest_info.is_const, target) != .ok) { + if (try ip.coerceInMemoryAllowed(gpa, arena, array_elem_ty, ptr_elem_ty, dest_info.flags.is_const, target) != .ok) { break :single_item; } return try ip.getUnknown(gpa, dest_ty); @@ -1181,7 +1190,7 @@ pub fn coerce( if (array_ty != .array_type) break :src_array_ptr; const array_elem_type = array_ty.array_type.child; - const elem_res = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, array_elem_type, dest_info.is_const, target); + const elem_res = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, array_elem_type, dest_info.flags.is_const, target); if (elem_res != .ok) { in_memory_result = .{ .ptr_child = .{ .child = try elem_res.dupe(arena), @@ -1203,7 +1212,7 @@ pub fn coerce( } return try ip.getUnknown(gpa, dest_ty); - // switch (dest_info.size) { + // switch (dest_info.flags.size) { // // *[N]T to []T // .Slice => return ip.coerceArrayPtrToSlice(gpa, arena, dest_ty, inst), // // *[N]T to [*c]T @@ -1219,7 +1228,7 @@ pub fn coerce( // TODO if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :src_c_ptr; const src_elem_ty = ip.indexToKey(inst_ty).pointer_type.elem_type; - if (try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, src_elem_ty, dest_info.is_const, target) != .ok) { + if (try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, src_elem_ty, dest_info.flags.is_const, target) != .ok) { break :src_c_ptr; } return try ip.getUnknown(gpa, dest_ty); @@ -1436,7 +1445,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t } continue; }, - .pointer_type => |chosen_info| if (chosen_info.size == .C) continue, + .pointer_type => |chosen_info| if (chosen_info.flags.size == .C) continue, else => {}, }, @@ -1467,7 +1476,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t else => {}, }, .int_type => continue, - .pointer_type => |chosen_info| if (chosen_info.size == .C) continue, + .pointer_type => |chosen_info| if (chosen_info.flags.size == .C) continue, else => {}, }, .comptime_float => switch (chosen_key) { @@ -1523,13 +1532,13 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t } continue; }, - .pointer_type => |chosen_info| if (chosen_info.size == .C) continue, + .pointer_type => |chosen_info| if (chosen_info.flags.size == .C) continue, else => {}, }, .pointer_type => |candidate_info| switch (chosen_key) { .simple_type => |chosen_simple| switch (chosen_simple) { .comptime_int => { - if (candidate_info.size == .C) { + if (candidate_info.flags.size == .C) { chosen = candidate; continue; } @@ -1537,15 +1546,15 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t else => {}, }, .pointer_type => |chosen_info| { - seen_const = seen_const or chosen_info.is_const or candidate_info.is_const; + seen_const = seen_const or chosen_info.flags.is_const or candidate_info.flags.is_const; const candidate_elem_info = ip.indexToKey(candidate_info.elem_type); const chosen_elem_info = ip.indexToKey(chosen_info.elem_type); // *[N]T to [*]T // *[N]T to []T - if ((candidate_info.size == .Many or candidate_info.size == .Slice) and - chosen_info.size == .One and + if ((candidate_info.flags.size == .Many or candidate_info.flags.size == .Slice) and + chosen_info.flags.size == .One and chosen_elem_info == .array_type) { // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` @@ -1553,9 +1562,9 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t chosen = candidate; continue; } - if (candidate_info.size == .One and + if (candidate_info.flags.size == .One and candidate_elem_info == .array_type and - (chosen_info.size == .Many or chosen_info.size == .Slice)) + (chosen_info.flags.size == .Many or chosen_info.flags.size == .Slice)) { // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` convert_to_slice = false; @@ -1565,21 +1574,21 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t // *[N]T and *[M]T // Verify both are single-pointers to arrays. // Keep the one whose element type can be coerced into. - if (chosen_info.size == .One and - candidate_info.size == .One and + if (chosen_info.flags.size == .One and + candidate_info.flags.size == .One and chosen_elem_info == .array_type and candidate_elem_info == .array_type) { const chosen_elem_ty = chosen_elem_info.array_type.child; const cand_elem_ty = candidate_elem_info.array_type.child; - const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, chosen_elem_ty, cand_elem_ty, chosen_info.is_const, target); + const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, chosen_elem_ty, cand_elem_ty, chosen_info.flags.is_const, target); if (chosen_ok) { convert_to_slice = true; continue; } - const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, cand_elem_ty, chosen_elem_ty, candidate_info.is_const, target); + const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, cand_elem_ty, chosen_elem_ty, candidate_info.flags.is_const, target); if (cand_ok) { convert_to_slice = true; chosen = candidate; @@ -1597,12 +1606,12 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t // Whichever element type can coerce to the other one, is // the one we will keep. If they're both OK then we keep the // C pointer since it matches both single and many pointers. - if (candidate_info.size == .C or chosen_info.size == .C) { - const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, candidate_info.elem_type, chosen_info.elem_type, candidate_info.is_const, target); - const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.elem_type, candidate_info.elem_type, chosen_info.is_const, target); + if (candidate_info.flags.size == .C or chosen_info.flags.size == .C) { + const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, candidate_info.elem_type, chosen_info.elem_type, candidate_info.flags.is_const, target); + const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.elem_type, candidate_info.elem_type, chosen_info.flags.is_const, target); if (cand_ok) { - if (!chosen_ok or chosen_info.size != .C) { + if (!chosen_ok or chosen_info.flags.size != .C) { chosen = candidate; } continue; @@ -1617,20 +1626,20 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t } }, .int_type => { - if (candidate_info.size == .C) { + if (candidate_info.flags.size == .C) { chosen = candidate; continue; } }, .optional_type => |chosen_info| switch (ip.indexToKey(chosen_info.payload_type)) { .pointer_type => |chosen_ptr_info| { - seen_const = seen_const or chosen_ptr_info.is_const or candidate_info.is_const; + seen_const = seen_const or chosen_ptr_info.flags.is_const or candidate_info.flags.is_const; // *[N]T to ?![*]T // *[N]T to ?![]T - if (candidate_info.size == .One and + if (candidate_info.flags.size == .One and ip.indexToKey(candidate_info.elem_type) == .array_type and - (chosen_ptr_info.size == .Many or chosen_ptr_info.size == .Slice)) + (chosen_ptr_info.flags.size == .Many or chosen_ptr_info.flags.size == .Slice)) { continue; } @@ -1642,12 +1651,12 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t if (chosen_ptr_key == .pointer_type) { const chosen_ptr_info = chosen_ptr_key.pointer_type; - seen_const = seen_const or chosen_ptr_info.is_const or candidate_info.is_const; + seen_const = seen_const or chosen_ptr_info.flags.is_const or candidate_info.flags.is_const; // *[N]T to E![*]T // *[N]T to E![]T - if (candidate_info.size == .One and - (chosen_ptr_info.size == .Many or chosen_ptr_info.size == .Slice) and + if (candidate_info.flags.size == .One and + (chosen_ptr_info.flags.size == .Many or chosen_ptr_info.flags.size == .Slice) and ip.indexToKey(candidate_info.elem_type) == .array_type) { continue; @@ -1655,7 +1664,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t } }, .function_type => { - if (candidate_info.is_const and + if (candidate_info.flags.is_const and ip.zigTypeTag(candidate_info.elem_type) == .Fn and .ok == try ip.coerceInMemoryAllowedFns(gpa, arena, chosen, candidate_info.elem_type, target)) { @@ -1733,8 +1742,8 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t // turn *[N]T => []T var info = ip.indexToKey(chosen).pointer_type; info.sentinel = ip.sentinel(info.elem_type); - info.size = .Slice; - info.is_const = seen_const or ip.isConstPointer(info.elem_type); + info.flags.size = .Slice; + info.flags.is_const = seen_const or ip.isConstPointer(info.elem_type); info.elem_type = ip.elemType(info.elem_type); const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); @@ -1751,7 +1760,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t switch (ip.indexToKey(chosen)) { .error_union_type => |error_union_info| { var info = ip.indexToKey(error_union_info.payload_type).pointer_type; - info.is_const = true; + info.flags.is_const = true; const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty; @@ -1763,7 +1772,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t }, .pointer_type => |pointer_info| { var info = pointer_info; - info.is_const = true; + info.flags.is_const = true; const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty; @@ -2216,36 +2225,36 @@ fn coerceInMemoryAllowedPtrs( const dest_info = ip.indexToKey(dest_ty).pointer_type; const src_info = ip.indexToKey(src_ty).pointer_type; - const ok_ptr_size = src_info.size == dest_info.size or - src_info.size == .C or dest_info.size == .C; + const ok_ptr_size = src_info.flags.size == dest_info.flags.size or + src_info.flags.size == .C or dest_info.flags.size == .C; if (!ok_ptr_size) { return InMemoryCoercionResult{ .ptr_size = .{ - .actual = src_info.size, - .wanted = dest_info.size, + .actual = src_info.flags.size, + .wanted = dest_info.flags.size, } }; } const ok_cv_qualifiers = - (!src_info.is_const or dest_info.is_const) and - (!src_info.is_volatile or dest_info.is_volatile); + (!src_info.flags.is_const or dest_info.flags.is_const) and + (!src_info.flags.is_volatile or dest_info.flags.is_volatile); if (!ok_cv_qualifiers) { return InMemoryCoercionResult{ .ptr_qualifiers = .{ - .actual_const = src_info.is_const, - .wanted_const = dest_info.is_const, - .actual_volatile = src_info.is_volatile, - .wanted_volatile = dest_info.is_volatile, + .actual_const = src_info.flags.is_const, + .wanted_const = dest_info.flags.is_const, + .actual_volatile = src_info.flags.is_volatile, + .wanted_volatile = dest_info.flags.is_volatile, } }; } - if (dest_info.address_space != src_info.address_space) { + if (dest_info.flags.address_space != src_info.flags.address_space) { return InMemoryCoercionResult{ .ptr_addrspace = .{ - .actual = src_info.address_space, - .wanted = dest_info.address_space, + .actual = src_info.flags.address_space, + .wanted = dest_info.flags.address_space, } }; } - const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, src_info.elem_type, dest_info.is_const, target); + const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, src_info.elem_type, dest_info.flags.is_const, target); if (child != .ok) { return InMemoryCoercionResult{ .ptr_child = .{ .child = try child.dupe(arena), @@ -2265,18 +2274,18 @@ fn coerceInMemoryAllowedPtrs( } }; } - if (src_info.host_size != dest_info.host_size or - src_info.bit_offset != dest_info.bit_offset) + if (src_info.packed_offset.host_size != dest_info.packed_offset.host_size or + src_info.packed_offset.bit_offset != dest_info.packed_offset.bit_offset) { return InMemoryCoercionResult{ .ptr_bit_range = .{ - .actual_host = src_info.host_size, - .wanted_host = dest_info.host_size, - .actual_offset = src_info.bit_offset, - .wanted_offset = dest_info.bit_offset, + .actual_host = src_info.packed_offset.host_size, + .wanted_host = dest_info.packed_offset.host_size, + .actual_offset = src_info.packed_offset.bit_offset, + .wanted_offset = dest_info.packed_offset.bit_offset, } }; } - const ok_sent = dest_info.sentinel == .none or src_info.size == .C or dest_info.sentinel == src_info.sentinel; // is this enough for a value equality check? + const ok_sent = dest_info.sentinel == .none or src_info.flags.size == .C or dest_info.sentinel == src_info.sentinel; // is this enough for a value equality check? if (!ok_sent) { return InMemoryCoercionResult{ .ptr_sentinel = .{ .actual = src_info.sentinel, @@ -2290,19 +2299,19 @@ fn coerceInMemoryAllowedPtrs( // pointee type alignment. Otherwise both pointee types must have their alignment // resolved and we compare the alignment numerically. alignment: { - if (src_info.alignment == 0 and dest_info.alignment == 0 and + if (src_info.flags.alignment == 0 and dest_info.flags.alignment == 0 and dest_info.elem_type == src_info.elem_type // is this enough for a value equality check? ) { break :alignment; } - // const src_align = if (src_info.alignment != 0) - // src_info.alignment + // const src_align = if (src_info.flags.alignment != 0) + // src_info.flags.alignment // else // src_info.elem_type.abiAlignment(target); - // const dest_align = if (dest_info.alignment != 0) - // dest_info.alignment + // const dest_align = if (dest_info.flags.alignment != 0) + // dest_info.flags.alignment // else // dest_info.elem_type.abiAlignment(target); @@ -2322,10 +2331,10 @@ fn coerceInMemoryAllowedPtrs( fn optionalPtrTy(ip: *const InternPool, ty: Index) Index { switch (ip.indexToKey(ty)) { .optional_type => |optional_info| switch (ip.indexToKey(optional_info.payload_type)) { - .pointer_type => |pointer_info| switch (pointer_info.size) { + .pointer_type => |pointer_info| switch (pointer_info.flags.size) { .Slice, .C => return Index.none, .Many, .One => { - if (pointer_info.is_allowzero) return Index.none; + if (pointer_info.flags.is_allowzero) return Index.none; // optionals of zero sized types behave like bools, not pointers if (ip.onePossibleValue(optional_info.payload_type) != Index.none) return Index.none; @@ -2733,35 +2742,35 @@ pub fn isFloat(ip: *const InternPool, ty: Index) bool { pub fn isSinglePointer(ip: *const InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { - .pointer_type => |pointer_info| pointer_info.size == .One, + .pointer_type => |pointer_info| pointer_info.flags.size == .One, else => false, }; } pub fn isManyPointer(ip: *const InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { - .pointer_type => |pointer_info| pointer_info.size == .Many, + .pointer_type => |pointer_info| pointer_info.flags.size == .Many, else => false, }; } pub fn isSlicePointer(ip: *const InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { - .pointer_type => |pointer_info| pointer_info.size == .Slice, + .pointer_type => |pointer_info| pointer_info.flags.size == .Slice, else => false, }; } pub fn isCPointer(ip: *const InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { - .pointer_type => |pointer_info| pointer_info.size == .C, + .pointer_type => |pointer_info| pointer_info.flags.size == .C, else => false, }; } pub fn isConstPointer(ip: *const InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { - .pointer_type => |pointer_info| pointer_info.is_const, + .pointer_type => |pointer_info| pointer_info.flags.is_const, else => false, }; } @@ -2769,7 +2778,7 @@ pub fn isConstPointer(ip: *const InternPool, ty: Index) bool { /// For pointer-like optionals, returns true, otherwise returns the allowzero property /// of pointers. pub fn ptrAllowsZero(ip: *const InternPool, ty: Index) bool { - if (ip.indexToKey(ty).pointer_type.is_allowzero) return true; + if (ip.indexToKey(ty).pointer_type.flags.is_allowzero) return true; return ip.isPtrLikeOptional(ty); } @@ -2778,24 +2787,24 @@ pub fn ptrAllowsZero(ip: *const InternPool, ty: Index) bool { pub fn isPtrLikeOptional(ip: *const InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .optional_type => |optional_info| switch (ip.indexToKey(optional_info.payload_type)) { - .pointer_type => |pointer_info| switch (pointer_info.size) { + .pointer_type => |pointer_info| switch (pointer_info.flags.size) { .Slice, .C => false, - .Many, .One => !pointer_info.is_allowzero, + .Many, .One => !pointer_info.flags.is_allowzero, }, else => false, }, - .pointer_type => |pointer_info| pointer_info.size == .C, + .pointer_type => |pointer_info| pointer_info.flags.size == .C, else => false, }; } pub fn isPtrAtRuntime(ip: *const InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { - .pointer_type => |pointer_info| pointer_info.size != .Slice, + .pointer_type => |pointer_info| pointer_info.flags.size != .Slice, .optional_type => |optional_info| switch (ip.indexToKey(optional_info.payload_type)) { - .pointer_type => |pointer_info| switch (pointer_info.size) { + .pointer_type => |pointer_info| switch (pointer_info.flags.size) { .Slice, .C => false, - .Many, .One => !pointer_info.is_allowzero, + .Many, .One => !pointer_info.flags.is_allowzero, }, else => false, }, @@ -2836,7 +2845,7 @@ pub fn childType(ip: *const InternPool, ty: Index) Index { /// For anyframe->T, returns T. pub fn elemType(ip: *const InternPool, ty: Index) Index { return switch (ip.indexToKey(ty)) { - .pointer_type => |pointer_info| switch (pointer_info.size) { + .pointer_type => |pointer_info| switch (pointer_info.flags.size) { .One => ip.childType(pointer_info.elem_type), .Many, .C, .Slice => pointer_info.elem_type, }, @@ -3076,7 +3085,7 @@ pub fn canHaveFields(ip: *const InternPool, ty: Index) bool { pub fn isIndexable(ip: *const InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .array_type, .vector_type => true, - .pointer_type => |pointer_info| switch (pointer_info.size) { + .pointer_type => |pointer_info| switch (pointer_info.flags.size) { .Slice, .Many, .C => true, .One => ip.indexToKey(pointer_info.elem_type) == .array_type, }, @@ -3251,35 +3260,35 @@ fn printInternal(ip: *InternPool, ty: Index, writer: anytype, options: FormatOpt }, .pointer_type => |pointer_info| { if (pointer_info.sentinel != Index.none) { - switch (pointer_info.size) { + switch (pointer_info.flags.size) { .One, .C => unreachable, .Many => try writer.print("[*:{}]", .{pointer_info.sentinel.fmt(ip)}), .Slice => try writer.print("[:{}]", .{pointer_info.sentinel.fmt(ip)}), } - } else switch (pointer_info.size) { + } else switch (pointer_info.flags.size) { .One => try writer.writeAll("*"), .Many => try writer.writeAll("[*]"), .C => try writer.writeAll("[*c]"), .Slice => try writer.writeAll("[]"), } - if (pointer_info.alignment != 0) { - try writer.print("align({d}", .{pointer_info.alignment}); + if (pointer_info.flags.alignment != 0) { + try writer.print("align({d}", .{pointer_info.flags.alignment}); - if (pointer_info.bit_offset != 0 or pointer_info.host_size != 0) { - try writer.print(":{d}:{d}", .{ pointer_info.bit_offset, pointer_info.host_size }); + if (pointer_info.packed_offset.bit_offset != 0 or pointer_info.packed_offset.host_size != 0) { + try writer.print(":{d}:{d}", .{ pointer_info.packed_offset.bit_offset, pointer_info.packed_offset.host_size }); } try writer.writeAll(") "); } - if (pointer_info.address_space != .generic) { - try writer.print("addrspace(.{s}) ", .{@tagName(pointer_info.address_space)}); + if (pointer_info.flags.address_space != .generic) { + try writer.print("addrspace(.{s}) ", .{@tagName(pointer_info.flags.address_space)}); } - if (pointer_info.is_const) try writer.writeAll("const "); - if (pointer_info.is_volatile) try writer.writeAll("volatile "); - if (pointer_info.is_allowzero and pointer_info.size != .C) try writer.writeAll("allowzero "); + if (pointer_info.flags.is_const) try writer.writeAll("const "); + if (pointer_info.flags.is_volatile) try writer.writeAll("volatile "); + if (pointer_info.flags.is_allowzero and pointer_info.flags.size != .C) try writer.writeAll("allowzero "); return pointer_info.elem_type; }, @@ -3680,53 +3689,61 @@ test "pointer type" { const @"*i32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .i32_type, - .size = .One, + .flags = .{ .size = .One }, } }); const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, - .size = .One, + .flags = .{ .size = .One }, } }); const @"*const volatile u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, - .size = .One, - .is_const = true, - .is_volatile = true, + .flags = .{ + .size = .One, + .is_const = true, + .is_volatile = true, + }, } }); const @"*align(4:2:3) u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, - .size = .One, - .alignment = 4, - .bit_offset = 2, - .host_size = 3, + .flags = .{ + .size = .One, + .alignment = 4, + }, + .packed_offset = .{ + .bit_offset = 2, + .host_size = 3, + }, } }); const @"*addrspace(.shared) const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, - .size = .One, - .is_const = true, - .address_space = .shared, + .flags = .{ + .size = .One, + .is_const = true, + .address_space = .shared, + }, } }); const @"[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, - .size = .Many, + .flags = .{ .size = .Many }, } }); const @"[*:0]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, - .size = .Many, .sentinel = .zero_comptime_int, + .flags = .{ .size = .Many }, } }); const @"[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, - .size = .Slice, + .flags = .{ .size = .Slice }, } }); const @"[:0]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, - .size = .Slice, .sentinel = .zero_comptime_int, + .flags = .{ .size = .Slice }, } }); const @"[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, - .size = .C, + .flags = .{ .size = .C }, } }); try expect(@"*i32" != @"*u32"); @@ -4204,19 +4221,19 @@ test "resolvePeerTypes pointers" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); - const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One } }); - const @"[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Many } }); - const @"[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Slice } }); - const @"[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .C } }); + const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .flags = .{ .size = .One } } }); + const @"[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .flags = .{ .size = .Many } } }); + const @"[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .flags = .{ .size = .Slice } } }); + const @"[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .flags = .{ .size = .C } } }); const @"?*u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*u32" } }); const @"?[*]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[*]u32" } }); const @"?[]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[]u32" } }); - const @"**u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"*u32", .size = .One } }); - const @"*[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[*]u32", .size = .One } }); - const @"*[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[]u32", .size = .One } }); - const @"*[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[*c]u32", .size = .One } }); + const @"**u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"*u32", .flags = .{ .size = .One } } }); + const @"*[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[*]u32", .flags = .{ .size = .One } } }); + const @"*[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[]u32", .flags = .{ .size = .One } } }); + const @"*[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[*c]u32", .flags = .{ .size = .One } } }); const @"?*[*]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[*]u32" } }); const @"?*[]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[]u32" } }); @@ -4224,16 +4241,16 @@ test "resolvePeerTypes pointers" { const @"[1]u32" = try ip.get(gpa, .{ .array_type = .{ .len = 1, .child = .u32_type, .sentinel = .none } }); const @"[2]u32" = try ip.get(gpa, .{ .array_type = .{ .len = 2, .child = .u32_type, .sentinel = .none } }); - const @"*[1]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[1]u32", .size = .One } }); - const @"*[2]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[2]u32", .size = .One } }); + const @"*[1]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[1]u32", .flags = .{ .size = .One } } }); + const @"*[2]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[2]u32", .flags = .{ .size = .One } } }); const @"?*[1]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[1]u32" } }); const @"?*[2]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[2]u32" } }); - const @"*const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One, .is_const = true } }); - const @"[*]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Many, .is_const = true } }); - const @"[]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Slice, .is_const = true } }); - const @"[*c]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .C, .is_const = true } }); + const @"*const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .flags = .{ .size = .One, .is_const = true } } }); + const @"[*]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .flags = .{ .size = .Many, .is_const = true } } }); + const @"[]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .flags = .{ .size = .Slice, .is_const = true } } }); + const @"[*c]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .flags = .{ .size = .C, .is_const = true } } }); const @"?*const u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*const u32" } }); const @"?[*]const u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[*]const u32" } }); @@ -4297,8 +4314,17 @@ test "resolvePeerTypes function pointers" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); - const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One } }); - const @"*const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One, .is_const = true } }); + const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .flags = .{ .size = .One }, + } }); + const @"*const u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .flags = .{ + .size = .One, + .is_const = true, + }, + } }); const @"fn(*u32) void" = try ip.get(gpa, .{ .function_type = .{ .args = &.{@"*u32"}, diff --git a/src/analyser/completions.zig b/src/analyser/completions.zig index 54e917f23..dbef06f8f 100644 --- a/src/analyser/completions.zig +++ b/src/analyser/completions.zig @@ -20,7 +20,7 @@ pub fn dotCompletions( const ty: InternPool.Index = ip.typeOf(index); const inner_ty = switch (ip.indexToKey(ty)) { - .pointer_type => |pointer_info| if (pointer_info.size == .One) pointer_info.elem_type else ty, + .pointer_type => |pointer_info| if (pointer_info.flags.size == .One) pointer_info.elem_type else ty, else => ty, }; @@ -61,7 +61,7 @@ pub fn dotCompletions( else => {}, }, .pointer_type => |pointer_info| { - switch (pointer_info.size) { + switch (pointer_info.flags.size) { .Many, .C => {}, .One => { switch (ip.indexToKey(pointer_info.elem_type)) { From 556f95c2b04ebf7c223e69d1e1505c0691a457db Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 23 Nov 2023 00:39:12 +0100 Subject: [PATCH 17/30] add test for `StringPool.getOrPut` on existing string without allocation --- src/analyser/string_pool.zig | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/analyser/string_pool.zig b/src/analyser/string_pool.zig index 3f78dfcab..c03f8d5ca 100644 --- a/src/analyser/string_pool.zig +++ b/src/analyser/string_pool.zig @@ -192,3 +192,16 @@ test "StringPool - empty string" { try std.testing.expectEqualStrings("", pool.stringToSliceUnsafe(.empty)); } + +test "StringPool - getOrPut on existing string without allocation" { + const gpa = std.testing.allocator; + var failing_gpa = std.testing.FailingAllocator.init(gpa, .{ .fail_index = 0 }); + + var pool = StringPool(.{}){}; + defer pool.deinit(gpa); + + const hello_string = try pool.getOrPutString(gpa, "hello"); + + try std.testing.expectError(error.OutOfMemory, pool.getOrPutString(failing_gpa.allocator(), "world")); + try std.testing.expectEqual(hello_string, try pool.getOrPutString(failing_gpa.allocator(), "hello")); +} From 8720d147d93ea0c93a657014530625108f9b5aca Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 23 Nov 2023 01:39:59 +0100 Subject: [PATCH 18/30] pack function type struct --- src/ComptimeInterpreter.zig | 6 +---- src/analyser/InternPool.zig | 51 ++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 28039e36e..e9ad5878c 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -1050,12 +1050,8 @@ pub fn interpret( // TODO: Resolve function type const function_type = try interpreter.ip.get(interpreter.allocator, Key{ .function_type = .{ - .calling_convention = .Unspecified, - .alignment = 0, - .is_generic = false, - .is_var_args = false, - .return_type = Index.none, .args = &.{}, + .return_type = Index.none, } }); // var it = func.iterate(&tree); diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 2a24c44d1..63251fe15 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -114,10 +114,15 @@ pub const Key = union(enum) { /// zig only lets the first 32 arguments be `noalias` args_is_noalias: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), return_type: Index, - alignment: u16 = 0, - calling_convention: std.builtin.CallingConvention = .Unspecified, - is_generic: bool = false, - is_var_args: bool = false, + flags: Flags = .{}, + + pub const Flags = packed struct(u32) { + calling_convention: std.builtin.CallingConvention = .Unspecified, + is_generic: bool = false, + is_var_args: bool = false, + _: u6 = 0, + alignment: u16 = 0, + }; }; pub const Tuple = struct { @@ -716,8 +721,8 @@ pub fn init(gpa: Allocator) Allocator.Error!InternPool { .{ .index = .manyptr_const_u8_sentinel_0_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .sentinel = .zero_u8, .flags = .{ .size = .Many, .is_const = true } } } }, .{ .index = .fn_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .noreturn_type } } }, .{ .index = .fn_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type } } }, - .{ .index = .fn_naked_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .calling_convention = .Naked } } }, - .{ .index = .fn_ccc_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .calling_convention = .C } } }, + .{ .index = .fn_naked_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .flags = .{ .calling_convention = .Naked } } } }, + .{ .index = .fn_ccc_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .flags = .{ .calling_convention = .C } } } }, .{ .index = .single_const_pointer_to_comptime_int_type, .key = .{ .pointer_type = .{ .elem_type = .comptime_int_type, .flags = .{ .size = .One, .is_const = true } } } }, .{ .index = .slice_const_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .flags = .{ .size = .Slice, .is_const = true } } } }, .{ .index = .slice_const_u8_sentinel_0_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .sentinel = .zero_u8, .flags = .{ .size = .Slice, .is_const = true } } } }, @@ -2141,18 +2146,18 @@ fn coerceInMemoryAllowedFns( const dest_info = ip.indexToKey(dest_ty).function_type; const src_info = ip.indexToKey(src_ty).function_type; - if (dest_info.is_var_args != src_info.is_var_args) { - return InMemoryCoercionResult{ .fn_var_args = dest_info.is_var_args }; + if (dest_info.flags.is_var_args != src_info.flags.is_var_args) { + return InMemoryCoercionResult{ .fn_var_args = dest_info.flags.is_var_args }; } - if (dest_info.is_generic != src_info.is_generic) { - return InMemoryCoercionResult{ .fn_generic = dest_info.is_generic }; + if (dest_info.flags.is_generic != src_info.flags.is_generic) { + return InMemoryCoercionResult{ .fn_generic = dest_info.flags.is_generic }; } - if (dest_info.calling_convention != src_info.calling_convention) { + if (dest_info.flags.calling_convention != src_info.flags.calling_convention) { return InMemoryCoercionResult{ .fn_cc = .{ - .actual = src_info.calling_convention, - .wanted = dest_info.calling_convention, + .actual = src_info.flags.calling_convention, + .wanted = dest_info.flags.calling_convention, } }; } @@ -3352,7 +3357,7 @@ fn printInternal(ip: *InternPool, ty: Index, writer: anytype, options: FormatOpt try ip.print(arg_ty, writer, options); } - if (function_info.is_var_args) { + if (function_info.flags.is_var_args) { if (function_info.args.len != 0) { try writer.writeAll(", "); } @@ -3360,11 +3365,11 @@ fn printInternal(ip: *InternPool, ty: Index, writer: anytype, options: FormatOpt } try writer.writeAll(") "); - if (function_info.alignment != 0) { - try writer.print("align({d}) ", .{function_info.alignment}); + if (function_info.flags.alignment != 0) { + try writer.print("align({d}) ", .{function_info.flags.alignment}); } - if (function_info.calling_convention != .Unspecified) { - try writer.print("callconv(.{s}) ", .{@tagName(function_info.calling_convention)}); + if (function_info.flags.calling_convention != .Unspecified) { + try writer.print("callconv(.{s}) ", .{@tagName(function_info.flags.calling_convention)}); } return function_info.return_type; @@ -3929,14 +3934,18 @@ test "function type" { const @"fn(i32, ...) type" = try ip.get(gpa, .{ .function_type = .{ .args = &.{.i32_type}, .return_type = .type_type, - .is_var_args = true, + .flags = .{ + .is_var_args = true, + }, } }); const @"fn() align(4) callconv(.C) type" = try ip.get(gpa, .{ .function_type = .{ .args = &.{}, .return_type = .type_type, - .alignment = 4, - .calling_convention = .C, + .flags = .{ + .calling_convention = .C, + .alignment = 4, + }, } }); try expectFmt("fn(i32) bool", "{}", .{@"fn(i32) bool".fmt(&ip)}); From c9de3a059efbebf7c9636f27cff12cb4fb62a0d4 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 23 Nov 2023 02:12:51 +0100 Subject: [PATCH 19/30] remove `InternPool.deepEql` and `InternPool.deepHash` --- src/analyser/InternPool.zig | 315 +++++++++++++++++++++--------------- 1 file changed, 187 insertions(+), 128 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 63251fe15..f614cc40f 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -61,7 +61,7 @@ pub const Key = union(enum) { unknown_value: UnknownValue, // error union - pub const Pointer = packed struct { + pub const Pointer = struct { elem_type: Index, sentinel: Index = .none, flags: Flags, @@ -201,14 +201,194 @@ pub const Key = union(enum) { limbs: []const std.math.big.Limb, }; - pub fn eql(a: Key, b: Key) bool { - return deepEql(a, b); + pub fn hash32(key: Key) u32 { + return @truncate(key.hash64()); } - pub fn hash(a: Key) u32 { + pub fn hash64(key: Key) u64 { var hasher = std.hash.Wyhash.init(0); - deepHash(&hasher, a); - return @truncate(hasher.final()); + key.hashWithHasher(&hasher); + return hasher.final(); + } + + pub fn hashWithHasher(key: Key, hasher: anytype) void { + std.hash.autoHash(hasher, std.meta.activeTag(key)); + switch (key) { + inline .simple_type, + .simple_value, + .pointer_type, + .array_type, + .struct_type, + .optional_type, + .error_union_type, + .enum_type, + .union_type, + .vector_type, + .anyframe_type, + // .int_u64_value, + // .int_i64_value, + .optional_value, + .slice, + .union_value, + .error_value, + .null_value, + .undefined_value, + .unknown_value, + => |*data| { + hasher.update(std.mem.asBytes(data)); + }, + + .int_type => |int_type| { + std.hash.autoHash(hasher, int_type.signedness); + std.hash.autoHash(hasher, int_type.bits); + }, + + .int_u64_value => |int_u64_value| { + std.hash.autoHash(hasher, int_u64_value.ty); + std.hash.autoHash(hasher, int_u64_value.int); + }, + .int_i64_value => |int_i64_value| { + std.hash.autoHash(hasher, int_i64_value.ty); + std.hash.autoHash(hasher, int_i64_value.int); + }, + .float_16_value => |float| std.hash.autoHash(hasher, @as(u16, @bitCast(float))), + .float_32_value => |float| std.hash.autoHash(hasher, @as(u32, @bitCast(float))), + .float_64_value => |float| std.hash.autoHash(hasher, @as(u64, @bitCast(float))), + .float_80_value => |float| std.hash.autoHash(hasher, @as(u80, @bitCast(float))), + .float_128_value, .float_comptime_value => |float| std.hash.autoHash(hasher, @as(u128, @bitCast(float))), + + .error_set_type => |error_set_type| { + std.hash.autoHash(hasher, error_set_type.owner_decl); + std.hash.autoHashStrat(hasher, error_set_type.names, .Deep); + }, + .function_type => |function_type| { + std.hash.autoHashStrat(hasher, function_type, .Deep); + }, + .tuple_type => |tuple_type| { + std.hash.autoHashStrat(hasher, tuple_type.types, .Deep); + std.hash.autoHashStrat(hasher, tuple_type.values, .Deep); + }, + .int_big_value => |int_big_value| { + std.hash.autoHash(hasher, int_big_value.ty); + std.hash.autoHashStrat(hasher, int_big_value.int, .Deep); + }, + .aggregate => |aggregate| { + std.hash.autoHash(hasher, aggregate.ty); + std.hash.autoHashStrat(hasher, aggregate.values, .Deep); + }, + } + } + + pub fn eql(a: Key, b: Key) bool { + const a_tag = std.meta.activeTag(a); + const b_tag = std.meta.activeTag(b); + if (a_tag != b_tag) return false; + + switch (a) { + inline .simple_type, + .simple_value, + .int_type, + .pointer_type, + .array_type, + .struct_type, + .optional_type, + .error_union_type, + .enum_type, + .union_type, + .vector_type, + .anyframe_type, + .int_u64_value, + .int_i64_value, + .optional_value, + .slice, + .union_value, + .error_value, + .null_value, + .undefined_value, + .unknown_value, + => |a_info, t| { + return std.meta.eql(a_info, @field(b, @tagName(t))); + }, + + inline .float_16_value, + .float_32_value, + .float_64_value, + .float_80_value, + .float_128_value, + .float_comptime_value, + => |a_data, t| { + const b_data = @field(b, @tagName(t)); + + const Int = std.meta.Int(.unsigned, @bitSizeOf(@TypeOf(a_data))); + return @as(Int, @bitCast(a_data)) == @as(Int, @bitCast(b_data)); + }, + + .error_set_type => |a_info| { + const b_info = b.error_set_type; + + if (a_info.owner_decl != b_info.owner_decl) return false; + + if (a_info.names.len != b_info.names.len) return false; + for (a_info.names, b_info.names) |a_name, b_name| { + if (a_name != b_name) return false; + } + + return true; + }, + .function_type => |a_info| { + const b_info = b.function_type; + + if (a_info.return_type != b_info.return_type) return false; + if (a_info.flags.alignment != b_info.flags.alignment) return false; + if (a_info.flags.calling_convention != b_info.flags.calling_convention) return false; + if (a_info.flags.is_generic != b_info.flags.is_generic) return false; + if (a_info.flags.is_var_args != b_info.flags.is_var_args) return false; + + if (!a_info.args_is_comptime.eql(b_info.args_is_comptime)) return false; + if (!a_info.args_is_generic.eql(b_info.args_is_generic)) return false; + if (!a_info.args_is_noalias.eql(b_info.args_is_noalias)) return false; + + if (a_info.args.len != b_info.args.len) return false; + for (a_info.args, b_info.args) |a_arg, b_arg| { + if (a_arg != b_arg) return false; + } + + return true; + }, + .tuple_type => |a_info| { + const b_info = b.tuple_type; + + std.debug.assert(a_info.types.len == b_info.types.len); + if (a_info.types.len != b_info.types.len) return false; + if (a_info.values.len != b_info.values.len) return false; + + for (a_info.types, a_info.values, b_info.types, b_info.values) |a_ty, a_val, b_ty, b_val| { + if (a_ty != b_ty) return false; + if (a_val != b_val) return false; + } + return true; + }, + .int_big_value => |a_info| { + const b_info = b.int_big_value; + + if (a_info.ty != b_info.ty) return false; + if (!a_info.int.eql(b_info.int)) return false; + + return true; + }, + .aggregate => |a_info| { + const b_info = b.aggregate; + + if (a_info.ty != b_info.ty) return false; + + if (a_info.values.len != b_info.values.len) return false; + for (a_info.values, b_info.values) |a_val, b_val| { + if (a_val != b_val) return false; + } + + return true; + }, + } } pub fn tag(key: Key) Tag { @@ -977,131 +1157,10 @@ const KeyAdapter = struct { pub fn hash(ctx: @This(), a: Key) u32 { _ = ctx; - return a.hash(); + return a.hash32(); } }; -fn deepEql(a: anytype, b: @TypeOf(a)) bool { - const T = @TypeOf(a); - - switch (@typeInfo(T)) { - .Struct => |info| { - if (info.layout == .Packed and comptime std.meta.hasUniqueRepresentation(T)) { - return std.mem.eql(u8, std.mem.asBytes(&a), std.mem.asBytes(&b)); - } - inline for (info.fields) |field_info| { - if (!deepEql(@field(a, field_info.name), @field(b, field_info.name))) return false; - } - return true; - }, - .Union => |info| { - const UnionTag = info.tag_type.?; - - const tag_a = std.meta.activeTag(a); - const tag_b = std.meta.activeTag(b); - if (tag_a != tag_b) return false; - - inline for (info.fields) |field_info| { - if (@field(UnionTag, field_info.name) == tag_a) { - return deepEql(@field(a, field_info.name), @field(b, field_info.name)); - } - } - return false; - }, - .Pointer => |info| switch (info.size) { - .One => return deepEql(a.*, b.*), - .Slice => { - if (a.len != b.len) return false; - - var i: usize = 0; - while (i < a.len) : (i += 1) { - if (!deepEql(a[i], b[i])) return false; - } - return true; - }, - .Many, - .C, - => @compileError("Unable to equality compare pointer " ++ @typeName(T)), - }, - .Float => { - const I = std.meta.Int(.unsigned, @bitSizeOf(T)); - return @as(I, @bitCast(a)) == @as(I, @bitCast(b)); - }, - .Bool, - .Int, - .Enum, - => return a == b, - else => @compileError("Unable to equality compare type " ++ @typeName(T)), - } -} - -fn deepHash(hasher: anytype, key: anytype) void { - const T = @TypeOf(key); - - switch (@typeInfo(T)) { - .Int => { - if (comptime std.meta.hasUniqueRepresentation(T)) { - hasher.update(std.mem.asBytes(&key)); - } else { - const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(T), 8) catch unreachable; - hasher.update(std.mem.asBytes(&key)[0..byte_size]); - } - }, - - .Bool => deepHash(hasher, @intFromBool(key)), - .Enum => deepHash(hasher, @intFromEnum(key)), - .Float => |info| deepHash(hasher, switch (info.bits) { - 16 => @as(u16, @bitCast(key)), - 32 => @as(u32, @bitCast(key)), - 64 => @as(u64, @bitCast(key)), - 80 => @as(u80, @bitCast(key)), - 128 => @as(u128, @bitCast(key)), - else => unreachable, - }), - - .Pointer => |info| switch (info.size) { - .One => { - deepHash(hasher, key.*); - }, - .Slice => { - if (info.child == u8) { - hasher.update(key); - } else { - for (key) |item| { - deepHash(hasher, item); - } - } - }, - .Many, - .C, - => @compileError("Unable to hash pointer " ++ @typeName(T)), - }, - .Struct => |info| { - if (info.layout == .Packed and comptime std.meta.hasUniqueRepresentation(T)) { - hasher.update(std.mem.asBytes(&key)); - } else { - inline for (info.fields) |field| { - deepHash(hasher, @field(key, field.name)); - } - } - }, - - .Union => |info| { - const TagType = info.tag_type.?; - - const tag = std.meta.activeTag(key); - deepHash(hasher, tag); - inline for (info.fields) |field| { - if (@field(TagType, field.name) == tag) { - deepHash(hasher, @field(key, field.name)); - break; - } - } - }, - else => @compileError("Unable to hash type " ++ @typeName(T)), - } -} - // --------------------------------------------- // UTILITY // --------------------------------------------- From ddcd10625e2de88674aca83269ab2420e8a4e5cf Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 23 Nov 2023 02:56:23 +0100 Subject: [PATCH 20/30] add `InternPool.Index.Slice` and `InternPool.StringSlice` --- src/ComptimeInterpreter.zig | 4 +- src/analyser/InternPool.zig | 321 ++++++++++++++---- src/analyser/completions.zig | 15 +- src/analyser/string_pool.zig | 1 + .../comptime_interpreter.zig | 6 +- 5 files changed, 263 insertions(+), 84 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index e9ad5878c..e2cbe5c2e 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -595,7 +595,7 @@ pub fn interpret( const field = struct_info.fields.values()[field_index]; const result = switch (interpreter.ip.indexToKey(val)) { - .aggregate => |aggregate| aggregate.values[field_index], + .aggregate => |aggregate| aggregate.values.at(@intCast(field_index), interpreter.ip), .unknown_value => try interpreter.ip.get(interpreter.allocator, .{ .unknown_value = .{ .ty = field.ty }, }), @@ -1050,7 +1050,7 @@ pub fn interpret( // TODO: Resolve function type const function_type = try interpreter.ip.get(interpreter.allocator, Key{ .function_type = .{ - .args = &.{}, + .args = Index.Slice.empty, .return_type = Index.none, } }); diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index f614cc40f..128163d9c 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -19,7 +19,7 @@ const expect = std.testing.expect; const expectFmt = std.testing.expectFmt; pub const StringPool = @import("string_pool.zig").StringPool(.{}); -pub const SPString = StringPool.String; +pub const String = StringPool.String; const encoding = @import("encoding.zig"); const ErrorMsg = @import("error_msg.zig").ErrorMsg; @@ -102,11 +102,11 @@ pub const Key = union(enum) { pub const ErrorSet = struct { owner_decl: Decl.OptionalIndex, - names: []const SPString, + names: StringSlice, }; pub const Function = struct { - args: []const Index, + args: Index.Slice, /// zig only lets the first 32 arguments be `comptime` args_is_comptime: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), /// zig only lets the first 32 arguments be generic @@ -126,9 +126,9 @@ pub const Key = union(enum) { }; pub const Tuple = struct { - types: []const Index, + types: Index.Slice, /// Index.none elements are used to indicate runtime-known. - values: []const Index, + values: Index.Slice, }; pub const Vector = struct { @@ -169,7 +169,7 @@ pub const Key = union(enum) { pub const Aggregate = struct { ty: Index, - values: []const Index, + values: Index.Slice, }; pub const UnionValue = struct { @@ -180,7 +180,7 @@ pub const Key = union(enum) { pub const ErrorValue = struct { ty: Index, - error_tag_name: SPString, + error_tag_name: String, }; pub const NullValue = struct { @@ -201,17 +201,17 @@ pub const Key = union(enum) { limbs: []const std.math.big.Limb, }; - pub fn hash32(key: Key) u32 { - return @truncate(key.hash64()); + pub fn hash32(key: Key, ip: *const InternPool) u32 { + return @truncate(key.hash64(ip)); } - pub fn hash64(key: Key) u64 { + pub fn hash64(key: Key, ip: *const InternPool) u64 { var hasher = std.hash.Wyhash.init(0); - key.hashWithHasher(&hasher); + key.hashWithHasher(&hasher, ip); return hasher.final(); } - pub fn hashWithHasher(key: Key, hasher: anytype) void { + pub fn hashWithHasher(key: Key, hasher: anytype, ip: *const InternPool) void { std.hash.autoHash(hasher, std.meta.activeTag(key)); switch (key) { inline .simple_type, @@ -259,14 +259,29 @@ pub const Key = union(enum) { .error_set_type => |error_set_type| { std.hash.autoHash(hasher, error_set_type.owner_decl); - std.hash.autoHashStrat(hasher, error_set_type.names, .Deep); + std.hash.autoHash(hasher, error_set_type.names.len); + for (0..error_set_type.names.len) |i| { + std.hash.autoHash(hasher, error_set_type.names.at(@intCast(i), ip)); + } }, .function_type => |function_type| { - std.hash.autoHashStrat(hasher, function_type, .Deep); + std.hash.autoHash(hasher, function_type.args_is_comptime); + std.hash.autoHash(hasher, function_type.args_is_generic); + std.hash.autoHash(hasher, function_type.args_is_noalias); + std.hash.autoHash(hasher, function_type.return_type); + + std.hash.autoHash(hasher, function_type.args.len); + for (0..function_type.args.len) |i| { + std.hash.autoHash(hasher, function_type.args.at(@intCast(i), ip)); + } }, .tuple_type => |tuple_type| { - std.hash.autoHashStrat(hasher, tuple_type.types, .Deep); - std.hash.autoHashStrat(hasher, tuple_type.values, .Deep); + std.debug.assert(tuple_type.types.len == tuple_type.values.len); + std.hash.autoHash(hasher, tuple_type.types.len); + for (0..tuple_type.types.len) |i| { + std.hash.autoHash(hasher, tuple_type.types.at(@intCast(i), ip)); + std.hash.autoHash(hasher, tuple_type.values.at(@intCast(i), ip)); + } }, .int_big_value => |int_big_value| { std.hash.autoHash(hasher, int_big_value.ty); @@ -274,12 +289,15 @@ pub const Key = union(enum) { }, .aggregate => |aggregate| { std.hash.autoHash(hasher, aggregate.ty); - std.hash.autoHashStrat(hasher, aggregate.values, .Deep); + std.hash.autoHash(hasher, aggregate.values.len); + for (0..aggregate.values.len) |i| { + std.hash.autoHash(hasher, aggregate.values.at(@intCast(i), ip)); + } }, } } - pub fn eql(a: Key, b: Key) bool { + pub fn eql(a: Key, b: Key, ip: *const InternPool) bool { const a_tag = std.meta.activeTag(a); const b_tag = std.meta.activeTag(b); if (a_tag != b_tag) return false; @@ -329,7 +347,9 @@ pub const Key = union(enum) { if (a_info.owner_decl != b_info.owner_decl) return false; if (a_info.names.len != b_info.names.len) return false; - for (a_info.names, b_info.names) |a_name, b_name| { + for (0..a_info.names.len) |i| { + const a_name = a_info.names.at(@intCast(i), ip); + const b_name = b_info.names.at(@intCast(i), ip); if (a_name != b_name) return false; } @@ -349,7 +369,9 @@ pub const Key = union(enum) { if (!a_info.args_is_noalias.eql(b_info.args_is_noalias)) return false; if (a_info.args.len != b_info.args.len) return false; - for (a_info.args, b_info.args) |a_arg, b_arg| { + for (0..a_info.args.len) |i| { + const a_arg = a_info.args.at(@intCast(i), ip); + const b_arg = b_info.args.at(@intCast(i), ip); if (a_arg != b_arg) return false; } @@ -362,8 +384,13 @@ pub const Key = union(enum) { if (a_info.types.len != b_info.types.len) return false; if (a_info.values.len != b_info.values.len) return false; - for (a_info.types, a_info.values, b_info.types, b_info.values) |a_ty, a_val, b_ty, b_val| { + for (0..a_info.types.len) |i| { + const a_ty = a_info.types.at(@intCast(i), ip); + const b_ty = b_info.types.at(@intCast(i), ip); if (a_ty != b_ty) return false; + + const a_val = a_info.values.at(@intCast(i), ip); + const b_val = b_info.values.at(@intCast(i), ip); if (a_val != b_val) return false; } return true; @@ -382,7 +409,9 @@ pub const Key = union(enum) { if (a_info.ty != b_info.ty) return false; if (a_info.values.len != b_info.values.len) return false; - for (a_info.values, b_info.values) |a_val, b_val| { + for (0..a_info.values.len) |i| { + const a_val = a_info.values.at(@intCast(i), ip); + const b_val = b_info.values.at(@intCast(i), ip); if (a_val != b_val) return false; } @@ -553,6 +582,28 @@ pub const Index = enum(u32) { none = std.math.maxInt(u32), _, + pub const Slice = struct { + start: u32, + len: u32, + + pub const empty = Slice{ + .start = std.math.maxInt(u32), + .len = 0, + }; + + /// prefer using `dupe` when iterating over all elements. + pub fn at(slice: Slice, index: u32, ip: *const InternPool) Index { + assert(index < slice.len); + return std.mem.bytesToValue(Index, ip.extra.items[slice.start + @sizeOf(u32) * index ..][0..@sizeOf(u32)]); + } + + pub fn dupe(slice: Slice, gpa: Allocator, ip: *const InternPool) error{OutOfMemory}![]Index { + if (slice.len == 0) return &.{}; + const bytes: []align(4) const u8 = @alignCast(ip.extra.items[slice.start..][0 .. @sizeOf(u32) * slice.len]); + return try gpa.dupe(Index, std.mem.bytesAsSlice(Index, bytes)); + } + }; + pub inline fn fmt(index: Index, ip: *InternPool) std.fmt.Formatter(format) { return fmtOptions(index, ip, .{}); } @@ -573,6 +624,28 @@ comptime { assert(@intFromEnum(Zir.Inst.Ref.one_usize) == @intFromEnum(Index.one_usize)); } +pub const StringSlice = struct { + start: u32, + len: u32, + + pub const empty = StringSlice{ + .start = std.math.maxInt(u32), + .len = 0, + }; + + /// prefer using `dupe` when iterating over all elements. + pub fn at(slice: StringSlice, index: u32, ip: *const InternPool) String { + assert(index < slice.len); + return std.mem.bytesToValue(String, ip.extra.items[slice.start + @sizeOf(String) * index ..][0..@sizeOf(String)]); + } + + pub fn dupe(slice: StringSlice, gpa: Allocator, ip: *const InternPool) error{OutOfMemory}![]String { + if (slice.len == 0) return &.{}; + const bytes: []align(4) const u8 = @alignCast(ip.extra.items[slice.start..][0 .. @sizeOf(String) * slice.len]); + return try gpa.dupe(String, std.mem.bytesAsSlice(String, bytes)); + } +}; + pub const Tag = enum(u8) { /// A type that can be represented with only an enum tag. /// data is SimpleType enum value @@ -751,7 +824,7 @@ pub const NamespaceIndex = enum(u32) { }; pub const Decl = struct { - name: SPString, + name: String, node_idx: std.zig.Ast.Node.Index, /// this stores both the type and the value index: InternPool.Index, @@ -794,7 +867,7 @@ pub const FieldStatus = enum { }; pub const Struct = struct { - fields: std.AutoArrayHashMapUnmanaged(SPString, Field), + fields: std.AutoArrayHashMapUnmanaged(String, Field), owner_decl: Decl.OptionalIndex, namespace: NamespaceIndex, layout: std.builtin.Type.ContainerLayout = .Auto, @@ -813,7 +886,7 @@ pub const Struct = struct { pub const Enum = struct { tag_type: InternPool.Index, - fields: std.AutoArrayHashMapUnmanaged(SPString, void), + fields: std.AutoArrayHashMapUnmanaged(String, void), values: std.AutoArrayHashMapUnmanaged(InternPool.Index, void), namespace: NamespaceIndex, tag_type_inferred: bool, @@ -823,7 +896,7 @@ pub const Enum = struct { pub const Union = struct { tag_type: InternPool.Index, - fields: std.AutoArrayHashMapUnmanaged(SPString, Field), + fields: std.AutoArrayHashMapUnmanaged(String, Field), namespace: NamespaceIndex, layout: std.builtin.Type.ContainerLayout = .Auto, status: FieldStatus, @@ -899,10 +972,10 @@ pub fn init(gpa: Allocator) Allocator.Error!InternPool { .{ .index = .manyptr_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .flags = .{ .size = .Many } } } }, .{ .index = .manyptr_const_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .flags = .{ .size = .Many, .is_const = true } } } }, .{ .index = .manyptr_const_u8_sentinel_0_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .sentinel = .zero_u8, .flags = .{ .size = .Many, .is_const = true } } } }, - .{ .index = .fn_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .noreturn_type } } }, - .{ .index = .fn_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type } } }, - .{ .index = .fn_naked_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .flags = .{ .calling_convention = .Naked } } } }, - .{ .index = .fn_ccc_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .flags = .{ .calling_convention = .C } } } }, + .{ .index = .fn_noreturn_no_args_type, .key = .{ .function_type = .{ .args = Index.Slice.empty, .return_type = .noreturn_type } } }, + .{ .index = .fn_void_no_args_type, .key = .{ .function_type = .{ .args = Index.Slice.empty, .return_type = .void_type } } }, + .{ .index = .fn_naked_noreturn_no_args_type, .key = .{ .function_type = .{ .args = Index.Slice.empty, .return_type = .void_type, .flags = .{ .calling_convention = .Naked } } } }, + .{ .index = .fn_ccc_void_no_args_type, .key = .{ .function_type = .{ .args = Index.Slice.empty, .return_type = .void_type, .flags = .{ .calling_convention = .C } } } }, .{ .index = .single_const_pointer_to_comptime_int_type, .key = .{ .pointer_type = .{ .elem_type = .comptime_int_type, .flags = .{ .size = .One, .is_const = true } } } }, .{ .index = .slice_const_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .flags = .{ .size = .Slice, .is_const = true } } } }, .{ .index = .slice_const_u8_sentinel_0_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .sentinel = .zero_u8, .flags = .{ .size = .Slice, .is_const = true } } } }, @@ -925,7 +998,7 @@ pub fn init(gpa: Allocator) Allocator.Error!InternPool { .{ .index = .null_value, .key = .{ .simple_value = .null_value } }, .{ .index = .bool_true, .key = .{ .simple_value = .bool_true } }, .{ .index = .bool_false, .key = .{ .simple_value = .bool_false } }, - .{ .index = .empty_aggregate, .key = .{ .aggregate = .{ .ty = .empty_struct_type, .values = &.{} } } }, + .{ .index = .empty_aggregate, .key = .{ .aggregate = .{ .ty = .empty_struct_type, .values = Index.Slice.empty } } }, .{ .index = .the_only_possible_value, .key = .{ .simple_value = .the_only_possible_value } }, .{ .index = .generic_poison, .key = .{ .simple_value = .generic_poison } }, .{ .index = .unknown_unknown, .key = .{ .unknown_value = .{ .ty = .unknown_type } } }, @@ -1087,6 +1160,30 @@ pub fn contains(ip: *const InternPool, key: Key) ?Index { return @enumFromInt(index); } +pub fn getIndexSlice(ip: *InternPool, gpa: Allocator, data: []const Index) error{OutOfMemory}!Index.Slice { + if (data.len == 0) return Index.Slice.empty; + + const start: u32 = @intCast(ip.extra.items.len); + try ip.extra.appendSlice(gpa, std.mem.sliceAsBytes(data)); + + return .{ + .start = start, + .len = @intCast(data.len), + }; +} + +pub fn getStringSlice(ip: *InternPool, gpa: Allocator, data: []const String) error{OutOfMemory}!StringSlice { + if (data.len == 0) return StringSlice.empty; + + const start: u32 = @intCast(ip.extra.items.len); + try ip.extra.appendSlice(gpa, std.mem.sliceAsBytes(data)); + + return .{ + .start = start, + .len = @intCast(data.len), + }; +} + pub fn getDecl(ip: *const InternPool, index: InternPool.Decl.Index) *const InternPool.Decl { return ip.decls.at(@intFromEnum(index)); } @@ -1152,12 +1249,11 @@ const KeyAdapter = struct { pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool { _ = b_void; - return a.eql(ctx.ip.indexToKey(@enumFromInt(b_map_index))); + return a.eql(ctx.ip.indexToKey(@enumFromInt(b_map_index)), ctx.ip); } pub fn hash(ctx: @This(), a: Key) u32 { - _ = ctx; - return a.hash32(); + return a.hash32(ctx.ip); } }; @@ -1882,7 +1978,7 @@ const InMemoryCoercionResult = union(enum) { optional_shape: Pair, optional_child: PairAndChild, from_anyerror, - missing_error: []const SPString, + missing_error: []const String, /// true if wanted is var args fn_var_args: bool, /// true if wanted is generic @@ -2175,14 +2271,17 @@ fn coerceInMemoryAllowedErrorSets( if (dest_ty == .anyerror_type) return .ok; if (src_ty == .anyerror_type) return .from_anyerror; - const dest_set = ip.indexToKey(dest_ty).error_set_type; - const src_set = ip.indexToKey(src_ty).error_set_type; + const dest_set_names = try ip.indexToKey(dest_ty).error_set_type.names.dupe(gpa, ip); + defer gpa.free(dest_set_names); + + const src_set_names = try ip.indexToKey(src_ty).error_set_type.names.dupe(gpa, ip); + defer gpa.free(src_set_names); - var missing_error_buf = std.ArrayListUnmanaged(SPString){}; + var missing_error_buf = std.ArrayListUnmanaged(String){}; defer missing_error_buf.deinit(gpa); - for (src_set.names) |name| { - if (std.mem.indexOfScalar(SPString, dest_set.names, name) == null) { + for (src_set_names) |name| { + if (std.mem.indexOfScalar(String, dest_set_names, name) == null) { try missing_error_buf.append(gpa, name); } } @@ -2190,7 +2289,7 @@ fn coerceInMemoryAllowedErrorSets( if (missing_error_buf.items.len == 0) return .ok; return InMemoryCoercionResult{ - .missing_error = try arena.dupe(SPString, missing_error_buf.items), + .missing_error = try arena.dupe(String, missing_error_buf.items), }; } @@ -2253,7 +2352,13 @@ fn coerceInMemoryAllowedFns( } }; } - for (dest_info.args, src_info.args, 0..) |dest_arg_ty, src_arg_ty, i| { + const dest_arg_types = try dest_info.args.dupe(gpa, ip); + defer gpa.free(dest_arg_types); + + const src_arg_types = try src_info.args.dupe(gpa, ip); + defer gpa.free(src_arg_types); + + for (dest_arg_types, src_arg_types, 0..) |dest_arg_ty, src_arg_ty, i| { // Note: Cast direction is reversed here. const param = try ip.coerceInMemoryAllowed(gpa, arena, src_arg_ty, dest_arg_ty, true, target); if (param != .ok) { @@ -2932,10 +3037,13 @@ pub fn errorSetMerge(ip: *InternPool, gpa: Allocator, a_ty: Index, b_ty: Index) if (a_ty == b_ty) return a_ty; - const a_names = ip.indexToKey(a_ty).error_set_type.names; - const b_names = ip.indexToKey(b_ty).error_set_type.names; + const a_names = try ip.indexToKey(a_ty).error_set_type.names.dupe(gpa, ip); + defer gpa.free(a_names); - var set = std.AutoArrayHashMapUnmanaged(SPString, void){}; + const b_names = try ip.indexToKey(b_ty).error_set_type.names.dupe(gpa, ip); + defer gpa.free(b_names); + + var set = std.AutoArrayHashMapUnmanaged(String, void){}; defer set.deinit(gpa); try set.ensureTotalCapacity(gpa, a_names.len + b_names.len); @@ -3389,10 +3497,10 @@ fn printInternal(ip: *InternPool, ty: Index, writer: anytype, options: FormatOpt try writer.print("{}", .{ip.fmtId(decl.name)}); return null; } - const names = error_set_info.names; try writer.writeAll("error{"); - for (names, 0..) |name, i| { + for (0..error_set_info.names.len) |i| { if (i != 0) try writer.writeByte(','); + const name = error_set_info.names.at(@intCast(i), ip); try writer.print("{}", .{ip.fmtId(name)}); } try writer.writeByte('}'); @@ -3401,7 +3509,8 @@ fn printInternal(ip: *InternPool, ty: Index, writer: anytype, options: FormatOpt .function_type => |function_info| { try writer.writeAll("fn("); - for (function_info.args, 0..) |arg_ty, i| { + for (0..function_info.args.len) |i| { + const arg_ty = function_info.args.at(@intCast(i), ip); if (i != 0) try writer.writeAll(", "); if (i < 32) { @@ -3435,8 +3544,13 @@ fn printInternal(ip: *InternPool, ty: Index, writer: anytype, options: FormatOpt }, .union_type => return panicOrElse("TODO", null), .tuple_type => |tuple_info| { + assert(tuple_info.types.len == tuple_info.values.len); try writer.writeAll("tuple{"); - for (tuple_info.types, tuple_info.values, 0..) |field_ty, field_val, i| { + + for (0..tuple_info.types.len) |i| { + const field_ty = tuple_info.types.at(@intCast(i), ip); + const field_val = tuple_info.values.at(@intCast(i), ip); + if (i != 0) try writer.writeAll(", "); if (field_val != Index.none) { try writer.writeAll("comptime "); @@ -3496,7 +3610,8 @@ fn printInternal(ip: *InternPool, ty: Index, writer: anytype, options: FormatOpt const struct_info = ip.getStruct(ip.indexToKey(aggregate.ty).struct_type); try writer.writeAll(".{"); - for (struct_info.fields.keys(), aggregate.values, 0..) |field_name, field, i| { + for (struct_info.fields.keys(), 0..) |field_name, i| { + const field = aggregate.values.at(@intCast(i), ip); if (i != 0) try writer.writeAll(", "); try writer.print(".{} = {}", .{ @@ -3526,7 +3641,7 @@ fn printInternal(ip: *InternPool, ty: Index, writer: anytype, options: FormatOpt fn formatId( ctx: struct { ip: *InternPool, - string: SPString, + string: String, }, comptime fmt: []const u8, options: std.fmt.FormatOptions, @@ -3539,7 +3654,7 @@ fn formatId( try writer.print("{}", .{std.zig.fmtId(ctx.ip.string_pool.stringToSliceUnsafe(ctx.string))}); } -pub fn fmtId(ip: *InternPool, field: SPString) std.fmt.Formatter(formatId) { +pub fn fmtId(ip: *InternPool, field: String) std.fmt.Formatter(formatId) { return .{ .data = .{ .ip = ip, .string = field } }; } @@ -3717,17 +3832,17 @@ test "float value" { try expect(f32_inf_value != f32_ninf_value); try expect(f32_zero_value != f32_nzero_value); - try expect(!ip.indexToKey(f16_value).eql(ip.indexToKey(f32_value))); - try expect(ip.indexToKey(f32_value).eql(ip.indexToKey(f32_value))); + try expect(!ip.indexToKey(f16_value).eql(ip.indexToKey(f32_value), &ip)); + try expect(ip.indexToKey(f32_value).eql(ip.indexToKey(f32_value), &ip)); - try expect(ip.indexToKey(f32_snan_value).eql(ip.indexToKey(f32_snan_value))); - try expect(!ip.indexToKey(f32_snan_value).eql(ip.indexToKey(f32_qnan_value))); + try expect(ip.indexToKey(f32_snan_value).eql(ip.indexToKey(f32_snan_value), &ip)); + try expect(!ip.indexToKey(f32_snan_value).eql(ip.indexToKey(f32_qnan_value), &ip)); - try expect(ip.indexToKey(f32_inf_value).eql(ip.indexToKey(f32_inf_value))); - try expect(!ip.indexToKey(f32_inf_value).eql(ip.indexToKey(f32_ninf_value))); + try expect(ip.indexToKey(f32_inf_value).eql(ip.indexToKey(f32_inf_value), &ip)); + try expect(!ip.indexToKey(f32_inf_value).eql(ip.indexToKey(f32_ninf_value), &ip)); - try expect(ip.indexToKey(f32_zero_value).eql(ip.indexToKey(f32_zero_value))); - try expect(!ip.indexToKey(f32_zero_value).eql(ip.indexToKey(f32_nzero_value))); + try expect(ip.indexToKey(f32_zero_value).eql(ip.indexToKey(f32_zero_value), &ip)); + try expect(!ip.indexToKey(f32_zero_value).eql(ip.indexToKey(f32_nzero_value), &ip)); try expectFmt("0.25", "{}", .{f16_value.fmt(&ip)}); try expectFmt("0.5", "{}", .{f32_value.fmt(&ip)}); @@ -3874,17 +3989,17 @@ test "error set type" { const empty_error_set = try ip.get(gpa, .{ .error_set_type = .{ .owner_decl = .none, - .names = &.{}, + .names = StringSlice.empty, } }); const foo_bar_baz_set = try ip.get(gpa, .{ .error_set_type = .{ .owner_decl = .none, - .names = &.{ foo_name, bar_name, baz_name }, + .names = try ip.getStringSlice(gpa, &.{ foo_name, bar_name, baz_name }), } }); const foo_bar_set = try ip.get(gpa, .{ .error_set_type = .{ .owner_decl = .none, - .names = &.{ foo_name, bar_name }, + .names = try ip.getStringSlice(gpa, &.{ foo_name, bar_name }), } }); try expect(empty_error_set != foo_bar_baz_set); @@ -3903,7 +4018,7 @@ test "error union type" { const empty_error_set = try ip.get(gpa, .{ .error_set_type = .{ .owner_decl = .none, - .names = &.{}, + .names = StringSlice.empty, } }); const bool_type = try ip.get(gpa, .{ .simple_type = .bool }); @@ -3961,7 +4076,7 @@ test "struct value" { const aggregate_value = try ip.get(gpa, .{ .aggregate = .{ .ty = struct_type, - .values = &.{ .one_usize, .bool_true }, + .values = try ip.getIndexSlice(gpa, &.{ .one_usize, .bool_true }), } }); try expectFmt(".{.foo = 1, .bar = true}", "{}", .{aggregate_value.fmt(&ip)}); @@ -3974,7 +4089,7 @@ test "function type" { defer ip.deinit(gpa); const @"fn(i32) bool" = try ip.get(gpa, .{ .function_type = .{ - .args = &.{.i32_type}, + .args = try ip.getIndexSlice(gpa, &.{.i32_type}), .return_type = .bool_type, } }); @@ -3984,14 +4099,14 @@ test "function type" { args_is_noalias.set(1); const @"fn(comptime type, noalias i32) type" = try ip.get(gpa, .{ .function_type = .{ - .args = &.{ .type_type, .i32_type }, + .args = try ip.getIndexSlice(gpa, &.{ .type_type, .i32_type }), .args_is_comptime = args_is_comptime, .args_is_noalias = args_is_noalias, .return_type = .type_type, } }); const @"fn(i32, ...) type" = try ip.get(gpa, .{ .function_type = .{ - .args = &.{.i32_type}, + .args = try ip.getIndexSlice(gpa, &.{.i32_type}), .return_type = .type_type, .flags = .{ .is_var_args = true, @@ -3999,7 +4114,7 @@ test "function type" { } }); const @"fn() align(4) callconv(.C) type" = try ip.get(gpa, .{ .function_type = .{ - .args = &.{}, + .args = Index.Slice.empty, .return_type = .type_type, .flags = .{ .calling_convention = .C, @@ -4087,6 +4202,62 @@ test "vector type" { try expectFmt("@Vector(2,bool)", "{}", .{@"@Vector(2,bool)".fmt(&ip)}); } +test "Index.Slice" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + _ = try ip.getIndexSlice(gpa, &.{ .none, .c_ulonglong_type, .call_modifier_type }); + const index_slice = try ip.getIndexSlice(gpa, &.{ .bool_type, .f32_type, .one_u8 }); + _ = try ip.getIndexSlice(gpa, &.{ .bool_false, .none, .anyerror_type }); + + try std.testing.expectEqual(@as(u32, 3), index_slice.len); + try std.testing.expectEqual(Index.bool_type, index_slice.at(0, &ip)); + try std.testing.expectEqual(Index.f32_type, index_slice.at(1, &ip)); + try std.testing.expectEqual(Index.one_u8, index_slice.at(2, &ip)); + + const indices = try index_slice.dupe(gpa, &ip); + defer gpa.free(indices); + + try std.testing.expectEqualSlices(Index, &.{ .bool_type, .f32_type, .one_u8 }, indices); + + const empty_indices = try Index.Slice.empty.dupe(gpa, &ip); + defer gpa.free(empty_indices); + + try std.testing.expectEqualSlices(Index, &.{}, empty_indices); +} + +test StringSlice { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const str1 = try ip.string_pool.getOrPutString(gpa, "aaa"); + const str2 = try ip.string_pool.getOrPutString(gpa, "bbb"); + const str3 = try ip.string_pool.getOrPutString(gpa, "ccc"); + + _ = try ip.getStringSlice(gpa, &.{ str2, str1, str3 }); + const string_slice = try ip.getStringSlice(gpa, &.{ str1, str2, str3 }); + _ = try ip.getStringSlice(gpa, &.{ str3, str2, str1 }); + + try std.testing.expectEqual(@as(u32, 3), string_slice.len); + try std.testing.expectEqual(str1, string_slice.at(0, &ip)); + try std.testing.expectEqual(str2, string_slice.at(1, &ip)); + try std.testing.expectEqual(str3, string_slice.at(2, &ip)); + + const strings = try string_slice.dupe(gpa, &ip); + defer gpa.free(strings); + + try std.testing.expectEqualSlices(String, &.{ str1, str2, str3 }, strings); + + const empty_string = try StringSlice.empty.dupe(gpa, &ip); + defer gpa.free(empty_string); + + try std.testing.expectEqualSlices(String, &.{}, empty_string); +} + test "coerceInMemoryAllowed integers and floats" { const gpa = std.testing.allocator; @@ -4127,19 +4298,19 @@ test "coerceInMemoryAllowed error set" { const foo_bar_baz_set = try ip.get(gpa, .{ .error_set_type = .{ .owner_decl = .none, - .names = &.{ baz_name, bar_name, foo_name }, + .names = try ip.getStringSlice(gpa, &.{ baz_name, bar_name, foo_name }), } }); const foo_bar_set = try ip.get(gpa, .{ .error_set_type = .{ .owner_decl = .none, - .names = &.{ foo_name, bar_name }, + .names = try ip.getStringSlice(gpa, &.{ foo_name, bar_name }), } }); const foo_set = try ip.get(gpa, .{ .error_set_type = .{ .owner_decl = .none, - .names = &.{foo_name}, + .names = try ip.getStringSlice(gpa, &.{foo_name}), } }); const empty_set = try ip.get(gpa, .{ .error_set_type = .{ .owner_decl = .none, - .names = &.{}, + .names = StringSlice.empty, } }); try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, foo_bar_baz_set, true, builtin.target) == .ok); @@ -4395,12 +4566,12 @@ test "resolvePeerTypes function pointers" { } }); const @"fn(*u32) void" = try ip.get(gpa, .{ .function_type = .{ - .args = &.{@"*u32"}, + .args = try ip.getIndexSlice(gpa, &.{@"*u32"}), .return_type = .void_type, } }); const @"fn(*const u32) void" = try ip.get(gpa, .{ .function_type = .{ - .args = &.{@"*const u32"}, + .args = try ip.getIndexSlice(gpa, &.{@"*const u32"}), .return_type = .void_type, } }); diff --git a/src/analyser/completions.zig b/src/analyser/completions.zig index dbef06f8f..13171cfbb 100644 --- a/src/analyser/completions.zig +++ b/src/analyser/completions.zig @@ -36,8 +36,11 @@ pub fn dotCompletions( switch (ip.indexToKey(val)) { .error_set_type => |error_set_info| { - for (error_set_info.names) |name| { - try completions.append(arena, .{ + const names = try error_set_info.names.dupe(arena, ip); + + try completions.ensureUnusedCapacity(arena, names.len); + for (names) |name| { + completions.appendAssumeCapacity(.{ .label = try std.fmt.allocPrint(arena, "{}", .{name.fmt(&ip.string_pool)}), .kind = .Constant, .detail = try std.fmt.allocPrint(arena, "error.{}", .{ip.fmtId(name)}), @@ -142,8 +145,12 @@ pub fn dotCompletions( } }, .tuple_type => |tuple_info| { - for (tuple_info.types, 0..) |tuple_ty, i| { - try completions.append(arena, .{ + std.debug.assert(tuple_info.types.len == tuple_info.values.len); + const tuple_types = try tuple_info.types.dupe(arena, ip); + + try completions.ensureUnusedCapacity(arena, tuple_info.types.len); + for (tuple_types, 0..) |tuple_ty, i| { + completions.appendAssumeCapacity(.{ .label = try std.fmt.allocPrint(arena, "{d}", .{i}), .kind = .Field, .detail = try std.fmt.allocPrint(arena, "{d}: {}", .{ i, tuple_ty.fmt(ip) }), diff --git a/src/analyser/string_pool.zig b/src/analyser/string_pool.zig index c03f8d5ca..fb185686f 100644 --- a/src/analyser/string_pool.zig +++ b/src/analyser/string_pool.zig @@ -112,6 +112,7 @@ pub fn StringPool(comptime config: Config) type { /// returns the underlying slice from an interned string /// equal strings are guaranteed to share the same storage pub fn stringToSliceUnsafe(pool: *Pool, index: String) [:0]const u8 { + std.debug.assert(@intFromEnum(index) < pool.bytes.items.len); const string_bytes: [*:0]u8 = @ptrCast(pool.bytes.items.ptr); const start = @intFromEnum(index); return std.mem.sliceTo(string_bytes + start, 0); diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig index faf3bca40..016aa7fe8 100644 --- a/tests/language_features/comptime_interpreter.zig +++ b/tests/language_features/comptime_interpreter.zig @@ -134,7 +134,7 @@ test "ComptimeInterpreter - variable lookup" { defer context.deinit(); const result = try context.interpret(context.findVar("bar")); - try std.testing.expect(result.val.?.eql(Key{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 3 } })); + try std.testing.expect(result.val.?.eql(Key{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 3 } }, context.ip)); } test "ComptimeInterpreter - field access" { @@ -260,7 +260,7 @@ test "ComptimeInterpreter - call comptime argument" { }}); try std.testing.expect(result1.ty == .simple_type); try std.testing.expect(result1.ty.simple_type == .type); - try std.testing.expect(result1.val.?.eql(Key{ .int_type = .{ .signedness = .unsigned, .bits = 8 } })); + try std.testing.expect(result1.val.?.eql(Key{ .int_type = .{ .signedness = .unsigned, .bits = 8 } }, context.ip)); const result2 = try context.call(context.findFn("Foo"), &.{KV{ .ty = .{ .simple_type = .bool }, @@ -268,7 +268,7 @@ test "ComptimeInterpreter - call comptime argument" { }}); try std.testing.expect(result2.ty == .simple_type); try std.testing.expect(result2.ty.simple_type == .type); - try std.testing.expect(result2.val.?.eql(Key{ .int_type = .{ .signedness = .unsigned, .bits = 69 } })); + try std.testing.expect(result2.val.?.eql(Key{ .int_type = .{ .signedness = .unsigned, .bits = 69 } }, context.ip)); } test "ComptimeInterpreter - call inner function" { From dfd511a4fa52c80fa0382c084d6fc077d220636e Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 3 Dec 2023 01:45:46 +0100 Subject: [PATCH 21/30] add a degibberish function for Zig types We may use this in the future to explain types to new Zig users. --- src/analyser/InternPool.zig | 6 +- src/analyser/analyser.zig | 1 + src/analyser/degibberish.zig | 279 +++++++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 src/analyser/degibberish.zig diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 128163d9c..252346597 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -3648,14 +3648,14 @@ fn formatId( writer: anytype, ) @TypeOf(writer).Error!void { _ = options; - if (fmt.len != 0) std.fmt.invalidFmtError(fmt, InternPool.Struct.Field); + if (fmt.len != 0) std.fmt.invalidFmtError(fmt, ctx.string); ctx.ip.string_pool.mutex.lock(); defer ctx.ip.string_pool.mutex.unlock(); try writer.print("{}", .{std.zig.fmtId(ctx.ip.string_pool.stringToSliceUnsafe(ctx.string))}); } -pub fn fmtId(ip: *InternPool, field: String) std.fmt.Formatter(formatId) { - return .{ .data = .{ .ip = ip, .string = field } }; +pub fn fmtId(ip: *InternPool, string: String) std.fmt.Formatter(formatId) { + return .{ .data = .{ .ip = ip, .string = string } }; } // --------------------------------------------- diff --git a/src/analyser/analyser.zig b/src/analyser/analyser.zig index 9603aee36..b0e2084f0 100644 --- a/src/analyser/analyser.zig +++ b/src/analyser/analyser.zig @@ -2,6 +2,7 @@ pub const completions = @import("completions.zig"); pub const InternPool = @import("InternPool.zig"); pub const StringPool = @import("string_pool.zig").StringPool; pub const encoding = @import("encoding.zig"); +pub const degibberish = @import("degibberish.zig"); comptime { const std = @import("std"); diff --git a/src/analyser/degibberish.zig b/src/analyser/degibberish.zig new file mode 100644 index 000000000..26b888cd6 --- /dev/null +++ b/src/analyser/degibberish.zig @@ -0,0 +1,279 @@ +const std = @import("std"); +const InternPool = @import("InternPool.zig"); + +const FormatDegibberishData = struct { + ip: *InternPool, + ty: InternPool.Index, +}; + +pub fn fmtDegibberish(ip: *InternPool, ty: InternPool.Index) std.fmt.Formatter(formatDegibberish) { + const data = FormatDegibberishData{ .ip = ip, .ty = ty }; + return .{ .data = data }; +} + +fn formatDegibberish(data: FormatDegibberishData, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { + if (fmt.len != 0) std.fmt.invalidFmtError(fmt, data.ty); + _ = options; + + const ip = data.ip; + var ty = data.ty; + + while (ty != .none) { + switch (ip.indexToKey(ty)) { + .simple_type, + .int_type, + => try writer.print("{}", .{ty.fmt(ip)}), + + .pointer_type => |pointer_info| { + // ignored attributes: + // - address_space + // - is_allowzero + // - is_volatile + // - packed_offset + + if (pointer_info.sentinel != .none) { + try writer.print("{} terminated ", .{pointer_info.sentinel.fmt(ip)}); + } + + // single pointer + const size_prefix = switch (pointer_info.flags.size) { + .One => "single-item pointer", + .Many => "many-item pointer", + .Slice => "slice (pointer + length)", + .C => "C pointer", + }; + + try writer.writeAll(size_prefix); + + if (pointer_info.flags.alignment != 0) { + try writer.print(" with alignment {d}", .{pointer_info.flags.alignment}); + } + + try writer.writeAll(" to "); + + if (pointer_info.flags.is_const) { + try writer.writeAll("const "); + } + + ty = pointer_info.elem_type; + continue; + }, + .array_type => |array_info| { + if (array_info.sentinel != .none) { + try writer.print("{} terminated ", .{array_info.sentinel.fmt(ip)}); + } + try writer.print("array {d} of ", .{array_info.len}); + ty = array_info.child; + continue; + }, + .struct_type => try writer.print("struct {}", .{ty.fmt(ip)}), + .optional_type => |optional_info| { + try writer.writeAll("optional of "); + ty = optional_info.payload_type; + continue; + }, + .error_union_type => |error_union_info| { + try writer.writeAll("error union with "); + try writer.print("{}", .{fmtDegibberish(ip, error_union_info.error_set_type)}); + try writer.writeAll(" and payload "); + ty = error_union_info.payload_type; + continue; + }, + .error_set_type => |error_set_info| { + try writer.writeAll("error set of ("); + for (0..error_set_info.names.len) |i| { + if (i != 0) try writer.writeByte(','); + const name = error_set_info.names.at(@intCast(i), ip); + try writer.print("{}", .{InternPool.fmtId(ip, name)}); + } + try writer.writeAll(")"); + }, + .enum_type => try writer.print("enum {}", .{ty.fmt(ip)}), + .function_type => |function_info| { + try writer.writeAll("function ("); + for (0..function_info.args.len) |i| { + if (i != 0) try writer.writeAll(", "); + const arg_ty = function_info.args.at(@intCast(i), ip); + try writer.print("{}", .{fmtDegibberish(ip, arg_ty)}); + } + try writer.writeAll(") returning "); + ty = function_info.return_type; + continue; + }, + .union_type => try writer.print("union {}", .{ty.fmt(ip)}), + .tuple_type => |tuple_info| { + std.debug.assert(tuple_info.types.len == tuple_info.values.len); + try writer.writeAll("tuple of ("); + for (0..tuple_info.types.len) |i| { + if (i != 0) try writer.writeAll(", "); + const field_ty = tuple_info.types.at(@intCast(i), ip); + try writer.print("{}", .{fmtDegibberish(ip, field_ty)}); + } + try writer.writeAll(")"); + }, + .vector_type => |vector_info| { + try writer.print("vector {d} of ", .{vector_info.len}); + ty = vector_info.child; + continue; + }, + .anyframe_type => |anyframe_info| { + try writer.writeAll("function frame returning "); + ty = anyframe_info.child; + continue; + }, + + .simple_value, + .int_u64_value, + .int_i64_value, + .int_big_value, + .float_16_value, + .float_32_value, + .float_64_value, + .float_80_value, + .float_128_value, + .float_comptime_value, + .optional_value, + .slice, + .aggregate, + .union_value, + .error_value, + .null_value, + .undefined_value, + .unknown_value, + => unreachable, + } + break; + } +} + +test "degibberish - simple types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + try std.testing.expectFmt("u32", "{}", .{fmtDegibberish(&ip, .u32_type)}); + try std.testing.expectFmt("comptime_float", "{}", .{fmtDegibberish(&ip, .comptime_float_type)}); +} + +test "degibberish - pointer types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + try std.testing.expectFmt("many-item pointer to u8", "{}", .{fmtDegibberish(&ip, .manyptr_u8_type)}); + try std.testing.expectFmt("many-item pointer to const u8", "{}", .{fmtDegibberish(&ip, .manyptr_const_u8_type)}); + try std.testing.expectFmt("0 terminated many-item pointer to const u8", "{}", .{fmtDegibberish(&ip, .manyptr_const_u8_sentinel_0_type)}); + try std.testing.expectFmt("single-item pointer to const comptime_int", "{}", .{fmtDegibberish(&ip, .single_const_pointer_to_comptime_int_type)}); + try std.testing.expectFmt("slice (pointer + length) to const u8", "{}", .{fmtDegibberish(&ip, .slice_const_u8_type)}); + try std.testing.expectFmt("0 terminated slice (pointer + length) to const u8", "{}", .{fmtDegibberish(&ip, .slice_const_u8_sentinel_0_type)}); +} + +test "degibberish - array types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"[3:0]u8" = try ip.get(gpa, .{ .array_type = .{ .len = 3, .child = .u8_type, .sentinel = .zero_u8 } }); + const @"[0]u32" = try ip.get(gpa, .{ .array_type = .{ .len = 0, .child = .u32_type } }); + + try std.testing.expectFmt("0 terminated array 3 of u8", "{}", .{fmtDegibberish(&ip, @"[3:0]u8")}); + try std.testing.expectFmt("array 0 of u32", "{}", .{fmtDegibberish(&ip, @"[0]u32")}); +} + +test "degibberish - optional types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"?u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .u32_type } }); + + try std.testing.expectFmt("optional of u32", "{}", .{fmtDegibberish(&ip, @"?u32")}); +} + +test "degibberish - error union types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const foo_string = try ip.string_pool.getOrPutString(gpa, "foo"); + const bar_string = try ip.string_pool.getOrPutString(gpa, "bar"); + const baz_string = try ip.string_pool.getOrPutString(gpa, "baz"); + + const @"error{foo,bar,baz}" = try ip.get(gpa, .{ .error_set_type = .{ + .names = try ip.getStringSlice(gpa, &.{ foo_string, bar_string, baz_string }), + .owner_decl = .none, + } }); + + const @"error{foo,bar,baz}!u32" = try ip.get(gpa, .{ .error_union_type = .{ + .error_set_type = @"error{foo,bar,baz}", + .payload_type = .u32_type, + } }); + + try std.testing.expectFmt("error union with error set of (foo,bar,baz) and payload u32", "{}", .{fmtDegibberish(&ip, @"error{foo,bar,baz}!u32")}); +} + +test "degibberish - error set types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const foo_string = try ip.string_pool.getOrPutString(gpa, "foo"); + const bar_string = try ip.string_pool.getOrPutString(gpa, "bar"); + const baz_string = try ip.string_pool.getOrPutString(gpa, "baz"); + + const @"error{foo,bar,baz}" = try ip.get(gpa, .{ .error_set_type = .{ + .names = try ip.getStringSlice(gpa, &.{ foo_string, bar_string, baz_string }), + .owner_decl = .none, + } }); + + try std.testing.expectFmt("error set of (foo,bar,baz)", "{}", .{fmtDegibberish(&ip, @"error{foo,bar,baz}")}); +} + +test "degibberish - function types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"fn(u32, void) type" = try ip.get(gpa, .{ .function_type = .{ + .args = try ip.getIndexSlice(gpa, &.{ .u32_type, .void_type }), + .return_type = .type_type, + } }); + + try std.testing.expectFmt("function () returning noreturn", "{}", .{fmtDegibberish(&ip, .fn_noreturn_no_args_type)}); + try std.testing.expectFmt("function () returning void", "{}", .{fmtDegibberish(&ip, .fn_void_no_args_type)}); + try std.testing.expectFmt("function (u32, void) returning type", "{}", .{fmtDegibberish(&ip, @"fn(u32, void) type")}); +} + +test "degibberish - tuple types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"struct{u32, comptime_float, c_int}" = try ip.get(gpa, .{ .tuple_type = .{ + .types = try ip.getIndexSlice(gpa, &.{ .u32_type, .comptime_float_type, .c_int_type }), + .values = try ip.getIndexSlice(gpa, &.{ .none, .none, .none }), + } }); + + try std.testing.expectFmt("tuple of (u32, comptime_float, c_int)", "{}", .{fmtDegibberish(&ip, @"struct{u32, comptime_float, c_int}")}); +} + +test "degibberish - vector types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"@Vector(3, u8)" = try ip.get(gpa, .{ .vector_type = .{ .len = 3, .child = .u8_type } }); + const @"@Vector(0, u32)" = try ip.get(gpa, .{ .vector_type = .{ .len = 0, .child = .u32_type } }); + + try std.testing.expectFmt("vector 3 of u8", "{}", .{fmtDegibberish(&ip, @"@Vector(3, u8)")}); + try std.testing.expectFmt("vector 0 of u32", "{}", .{fmtDegibberish(&ip, @"@Vector(0, u32)")}); +} + +test "degibberish - anyframe types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"anyframe->u32" = try ip.get(gpa, .{ .anyframe_type = .{ .child = .u32_type } }); + try std.testing.expectFmt("function frame returning u32", "{}", .{fmtDegibberish(&ip, @"anyframe->u32")}); +} From 50d20be9f8fb39bcc6f2f8719e29392e65ffd583 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 17 Dec 2023 19:38:19 +0100 Subject: [PATCH 22/30] remove usage of inline else from InternPool.get --- src/analyser/InternPool.zig | 234 +++++++++++++++++++++++------------- 1 file changed, 148 insertions(+), 86 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 252346597..02040e69d 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -419,49 +419,6 @@ pub const Key = union(enum) { }, } } - - pub fn tag(key: Key) Tag { - return switch (key) { - .simple_type => .simple_type, - .simple_value => .simple_value, - - .int_type => |int_info| switch (int_info.signedness) { - .signed => .type_int_signed, - .unsigned => .type_int_unsigned, - }, - .pointer_type => .type_pointer, - .array_type => .type_array, - .struct_type => .type_struct, - .optional_type => .type_optional, - .error_union_type => .type_error_union, - .error_set_type => .type_error_set, - .enum_type => .type_enum, - .function_type => .type_function, - .union_type => .type_union, - .tuple_type => .type_tuple, - .vector_type => .type_vector, - .anyframe_type => .type_anyframe, - - .int_u64_value => .int_u64, - .int_i64_value => .int_i64, - .int_big_value => |big_int| if (big_int.int.positive) .int_big_positive else .int_big_negative, - .float_16_value => .float_f16, - .float_32_value => .float_f32, - .float_64_value => .float_f64, - .float_80_value => .float_f80, - .float_128_value => .float_f128, - .float_comptime_value => .float_comptime, - - .optional_value => .optional_value, - .slice => .slice, - .aggregate => .aggregate, - .union_value => .union_value, - .error_value => .error_value, - .null_value => .null_value, - .undefined_value => .undefined_value, - .unknown_value => .unknown_value, - }; - } }; pub const Item = struct { @@ -732,12 +689,12 @@ pub const Tag = enum(u8) { /// A optional value that is not null. /// data is index to OptionalValue. optional_value, - /// A aggregate (struct) value. - /// data is index to Aggregate. - aggregate, /// A slice value. /// data is index to Slice. - slice, + slice_value, + /// A aggregate (struct) value. + /// data is index to Aggregate. + aggregate_value, /// A union value. /// data is index to UnionValue. union_value, @@ -1102,8 +1059,8 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { .float_comptime => .{ .float_comptime_value = ip.extraData(f128, data) }, .optional_value => .{ .optional_value = ip.extraData(Key.OptionalValue, data) }, - .slice => .{ .slice = ip.extraData(Key.Slice, data) }, - .aggregate => .{ .aggregate = ip.extraData(Key.Aggregate, data) }, + .slice_value => .{ .slice = ip.extraData(Key.Slice, data) }, + .aggregate_value => .{ .aggregate = ip.extraData(Key.Aggregate, data) }, .union_value => .{ .union_value = ip.extraData(Key.UnionValue, data) }, .error_value => .{ .error_value = ip.extraData(Key.ErrorValue, data) }, .null_value => .{ .null_value = .{ .ty = @enumFromInt(data) } }, @@ -1117,40 +1074,146 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { const gop = try ip.map.getOrPutAdapted(gpa, key, adapter); if (gop.found_existing) return @enumFromInt(gop.index); - const tag: Tag = key.tag(); - const data: u32 = switch (key) { - .simple_type => |simple| @intFromEnum(simple), - .simple_value => |simple| @intFromEnum(simple), - - .int_type => |int_ty| int_ty.bits, - .optional_type => |optional_ty| @intFromEnum(optional_ty.payload_type), - .anyframe_type => |anyframe_ty| @intFromEnum(anyframe_ty.child), - - .struct_type => |struct_index| @intFromEnum(struct_index), - .enum_type => |enum_index| @intFromEnum(enum_index), - .union_type => |union_index| @intFromEnum(union_index), - - .int_u64_value => |int_val| try ip.addExtra(gpa, int_val), - .int_i64_value => |int_val| try ip.addExtra(gpa, int_val), - .int_big_value => |big_int_val| try ip.addExtra(gpa, Key.BigIntInternal{ - .ty = big_int_val.ty, - .limbs = big_int_val.int.limbs, - }), - .float_16_value => |float_val| @as(u16, @bitCast(float_val)), - .float_32_value => |float_val| @as(u32, @bitCast(float_val)), - .null_value => |null_val| @intFromEnum(null_val.ty), - .undefined_value => |undefined_val| @intFromEnum(undefined_val.ty), + const item: Item = switch (key) { + .simple_type => |simple| .{ + .tag = .simple_type, + .data = @intFromEnum(simple), + }, + .simple_value => |simple| .{ + .tag = .simple_value, + .data = @intFromEnum(simple), + }, + .int_type => |int_ty| switch (int_ty.signedness) { + .signed => .{ .tag = .type_int_signed, .data = int_ty.bits }, + .unsigned => .{ .tag = .type_int_unsigned, .data = int_ty.bits }, + }, + .pointer_type => |pointer_ty| .{ + .tag = .type_pointer, + .data = try ip.addExtra(gpa, Key.Pointer, pointer_ty), + }, + .array_type => |array_ty| .{ + .tag = .type_array, + .data = try ip.addExtra(gpa, Key.Array, array_ty), + }, + .struct_type => |struct_index| .{ + .tag = .type_struct, + .data = @intFromEnum(struct_index), + }, + .optional_type => |optional_ty| .{ + .tag = .type_optional, + .data = @intFromEnum(optional_ty.payload_type), + }, + .error_union_type => |error_union_ty| .{ + .tag = .type_error_union, + .data = try ip.addExtra(gpa, Key.ErrorUnion, error_union_ty), + }, + .error_set_type => |error_set_ty| .{ + .tag = .type_error_set, + .data = try ip.addExtra(gpa, Key.ErrorSet, error_set_ty), + }, + .enum_type => |enum_index| .{ + .tag = .type_enum, + .data = @intFromEnum(enum_index), + }, + .function_type => |function_ty| .{ + .tag = .type_function, + .data = try ip.addExtra(gpa, Key.Function, function_ty), + }, + .union_type => |union_index| .{ + .tag = .type_union, + .data = @intFromEnum(union_index), + }, + .tuple_type => |tuple_ty| .{ + .tag = .type_tuple, + .data = try ip.addExtra(gpa, Key.Tuple, tuple_ty), + }, + .vector_type => |vector_ty| .{ + .tag = .type_vector, + .data = try ip.addExtra(gpa, Key.Vector, vector_ty), + }, + .anyframe_type => |anyframe_ty| .{ + .tag = .type_anyframe, + .data = @intFromEnum(anyframe_ty.child), + }, + + .int_u64_value => |int_val| .{ + .tag = .int_u64, + .data = try ip.addExtra(gpa, Key.U64Value, int_val), + }, + .int_i64_value => |int_val| .{ + .tag = .int_i64, + .data = try ip.addExtra(gpa, Key.I64Value, int_val), + }, + .int_big_value => |big_int_val| .{ + .tag = if (big_int_val.int.positive) .int_big_positive else .int_big_negative, + .data = try ip.addExtra(gpa, Key.BigIntInternal, .{ + .ty = big_int_val.ty, + .limbs = big_int_val.int.limbs, + }), + }, + .float_16_value => |float_val| .{ + .tag = .float_f16, + .data = @as(u16, @bitCast(float_val)), + }, + .float_32_value => |float_val| .{ + .tag = .float_f32, + .data = @bitCast(float_val), + }, + .float_64_value => |float_val| .{ + .tag = .float_f64, + .data = try ip.addExtra(gpa, f64, float_val), + }, + .float_80_value => |float_val| .{ + .tag = .float_f80, + .data = try ip.addExtra(gpa, f80, float_val), + }, + .float_128_value => |float_val| .{ + .tag = .float_f128, + .data = try ip.addExtra(gpa, f128, float_val), + }, + .float_comptime_value => |float_val| .{ + .tag = .float_comptime, + .data = try ip.addExtra(gpa, f128, float_val), + }, + + .optional_value => |optional_val| .{ + .tag = .optional_value, + .data = try ip.addExtra(gpa, Key.OptionalValue, optional_val), + }, + .slice => |slice_val| .{ + .tag = .slice_value, + .data = try ip.addExtra(gpa, Key.Slice, slice_val), + }, + .aggregate => |aggregate_val| .{ + .tag = .aggregate_value, + .data = try ip.addExtra(gpa, Key.Aggregate, aggregate_val), + }, + .union_value => |union_val| .{ + .tag = .union_value, + .data = try ip.addExtra(gpa, Key.UnionValue, union_val), + }, + .error_value => |error_val| .{ + .tag = .error_value, + .data = try ip.addExtra(gpa, Key.ErrorValue, error_val), + }, + .null_value => |null_val| .{ + .tag = .null_value, + .data = @intFromEnum(null_val.ty), + }, + .undefined_value => |undefined_val| .{ + .tag = .undefined_value, + .data = @intFromEnum(undefined_val.ty), + }, .unknown_value => |unknown_val| blk: { assert(unknown_val.ty != .type_type); // use .unknown_type instead - break :blk @intFromEnum(unknown_val.ty); + break :blk .{ + .tag = .unknown_value, + .data = @intFromEnum(unknown_val.ty), + }; }, - inline else => |data| try ip.addExtra(gpa, data), }; - try ip.items.append(gpa, .{ - .tag = tag, - .data = data, - }); + try ip.items.append(gpa, item); return @enumFromInt(ip.items.len - 1); } @@ -1226,8 +1289,7 @@ pub fn createUnion(ip: *InternPool, gpa: Allocator, union_info: Union) Allocator return @enumFromInt(ip.unions.count() - 1); } -fn addExtra(ip: *InternPool, gpa: Allocator, extra: anytype) Allocator.Error!u32 { - const T = @TypeOf(extra); +fn addExtra(ip: *InternPool, gpa: Allocator, comptime T: type, extra: T) Allocator.Error!u32 { comptime if (@sizeOf(T) <= 4) { @compileError(@typeName(T) ++ " fits into a u32! Consider directly storing this extra in Item's data field"); }; @@ -2610,8 +2672,8 @@ pub fn zigTypeTag(ip: *const InternPool, index: Index) std.builtin.TypeId { .float_f128, .float_comptime, .optional_value, - .aggregate, - .slice, + .slice_value, + .aggregate_value, .union_value, .null_value, .error_value, @@ -2664,8 +2726,8 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { .int_big_positive, .int_big_negative, .optional_value, - .aggregate, - .slice, + .slice_value, + .aggregate_value, .union_value, .error_value, => std.mem.bytesToValue(Index, ip.extra.items[ip.items.items(.data)[@intFromEnum(index)]..][0..@sizeOf(Index)]), @@ -2709,8 +2771,8 @@ pub fn isType(ip: *const InternPool, ty: Index) bool { .int_big_positive, .int_big_negative, .optional_value, - .aggregate, - .slice, + .slice_value, + .aggregate_value, .union_value, .error_value, .null_value, From dab5cb7d417293ab36b37cb7299e033024d7a359 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 17 Dec 2023 21:43:07 +0100 Subject: [PATCH 23/30] encode large floats as u32 integers --- src/analyser/InternPool.zig | 68 ++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 02040e69d..507dac666 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -156,6 +156,50 @@ pub const Key = union(enum) { int: std.math.big.int.Const, }; + const F64Value = packed struct { + a: u32, + b: u32, + + fn pack(val: f64) F64Value { + return @bitCast(val); + } + + fn unpack(val: F64Value) f64 { + return @bitCast(val); + } + }; + + const F80Value = packed struct { + a: u32, + b: u32, + c: u32, + + fn pack(val: f80) F80Value { + // f80 -> u80 -> u96 -> F80Value + return @bitCast(@as(u96, @as(u80, @bitCast(val)))); + } + + fn unpack(val: F80Value) f80 { + // F80Value -> u96 -> u80 -> f80 + return @bitCast(@as(u80, @truncate(@as(u96, @bitCast(val))))); + } + }; + + const F128Value = packed struct { + a: u32, + b: u32, + c: u32, + d: u32, + + fn pack(val: f128) F128Value { + return @bitCast(val); + } + + fn unpack(val: F128Value) f128 { + return @bitCast(val); + } + }; + pub const OptionalValue = struct { ty: Index, val: Index, @@ -674,16 +718,16 @@ pub const Tag = enum(u8) { /// data is f32 bitcasted to u32. float_f32, /// A float value that can be represented by f64. - /// data is payload to f64. + /// data is payload to F64Value. float_f64, /// A float value that can be represented by f80. - /// data is payload to f80. + /// data is payload to F80Value. float_f80, /// A float value that can be represented by f128. - /// data is payload to f128. + /// data is payload to F128Value. float_f128, /// A comptime float value. - /// data is payload to f128. + /// data is payload to F128Value. float_comptime, /// A optional value that is not null. @@ -1053,10 +1097,10 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { } }, .float_f16 => .{ .float_16_value = @bitCast(@as(u16, @intCast(data))) }, .float_f32 => .{ .float_32_value = @bitCast(data) }, - .float_f64 => .{ .float_64_value = ip.extraData(f64, data) }, - .float_f80 => .{ .float_80_value = ip.extraData(f80, data) }, - .float_f128 => .{ .float_128_value = ip.extraData(f128, data) }, - .float_comptime => .{ .float_comptime_value = ip.extraData(f128, data) }, + .float_f64 => .{ .float_64_value = ip.extraData(Key.F64Value, data).unpack() }, + .float_f80 => .{ .float_80_value = ip.extraData(Key.F80Value, data).unpack() }, + .float_f128 => .{ .float_128_value = ip.extraData(Key.F128Value, data).unpack() }, + .float_comptime => .{ .float_comptime_value = ip.extraData(Key.F128Value, data).unpack() }, .optional_value => .{ .optional_value = ip.extraData(Key.OptionalValue, data) }, .slice_value => .{ .slice = ip.extraData(Key.Slice, data) }, @@ -1161,19 +1205,19 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { }, .float_64_value => |float_val| .{ .tag = .float_f64, - .data = try ip.addExtra(gpa, f64, float_val), + .data = try ip.addExtra(gpa, Key.F64Value, Key.F64Value.pack(float_val)), }, .float_80_value => |float_val| .{ .tag = .float_f80, - .data = try ip.addExtra(gpa, f80, float_val), + .data = try ip.addExtra(gpa, Key.F80Value, Key.F80Value.pack(float_val)), }, .float_128_value => |float_val| .{ .tag = .float_f128, - .data = try ip.addExtra(gpa, f128, float_val), + .data = try ip.addExtra(gpa, Key.F128Value, Key.F128Value.pack(float_val)), }, .float_comptime_value => |float_val| .{ .tag = .float_comptime, - .data = try ip.addExtra(gpa, f128, float_val), + .data = try ip.addExtra(gpa, Key.F128Value, Key.F128Value.pack(float_val)), }, .optional_value => |optional_val| .{ From 9a42f5d3d11887f255522987c88d24ea4b11668a Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Mon, 18 Dec 2023 22:46:37 +0100 Subject: [PATCH 24/30] improve StringPool API, safety and documentation - more doc comments - compute string hash before acquiring lock - add safety check for calling deinit while holding lock - add stringToSliceLock - add stringToSlice - remove StringPool.String.empty - remove hashString --- src/analyser/InternPool.zig | 6 +- src/analyser/string_pool.zig | 133 ++++++++++++++++++++++++++--------- 2 files changed, 104 insertions(+), 35 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 507dac666..142621487 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -3755,9 +3755,9 @@ fn formatId( ) @TypeOf(writer).Error!void { _ = options; if (fmt.len != 0) std.fmt.invalidFmtError(fmt, ctx.string); - ctx.ip.string_pool.mutex.lock(); - defer ctx.ip.string_pool.mutex.unlock(); - try writer.print("{}", .{std.zig.fmtId(ctx.ip.string_pool.stringToSliceUnsafe(ctx.string))}); + const locked_string = ctx.ip.string_pool.stringToSliceLock(ctx.string); + defer locked_string.release(&ctx.ip.string_pool); + try std.fmt.format(writer, "{}", .{std.zig.fmtId(locked_string.slice)}); } pub fn fmtId(ip: *InternPool, string: String) std.fmt.Formatter(formatId) { diff --git a/src/analyser/string_pool.zig b/src/analyser/string_pool.zig index fb185686f..29aef5767 100644 --- a/src/analyser/string_pool.zig +++ b/src/analyser/string_pool.zig @@ -18,12 +18,19 @@ pub const Config = struct { MutexType: ?type = null, }; +/// The StringPool is a Data structure that stores only one copy of distinct and immutable strings i.e. `[]const u8`. +/// +/// The `getOrPutString` function will intern a given string and return a unique identifier +/// that can then be used to retrieve the original string with the `stringToSlice*` functions. pub fn StringPool(comptime config: Config) type { return struct { const Pool = @This(); + /// A unique number that identifier a interned string. + /// + /// Two interned string can be checked for equality simply by checking + /// if this identifier are equal if they both come from the same StringPool. pub const String = enum(u32) { - empty = 0, _, pub fn toOptional(self: String) OptionalString { @@ -36,7 +43,6 @@ pub fn StringPool(comptime config: Config) type { }; pub const OptionalString = enum(u32) { - empty = 0, none = std.math.maxInt(u32), _, @@ -46,35 +52,55 @@ pub fn StringPool(comptime config: Config) type { } }; - /// asserts that `str` contains no null bytes + /// Asserts that `str` contains no null bytes. pub fn getString(pool: *Pool, str: []const u8) ?String { assert(std.mem.indexOfScalar(u8, str, 0) == null); + + // precompute the hash before acquiring the lock + const precomputed_key_hash = std.hash_map.hashString(str); + pool.mutex.lock(); defer pool.mutex.unlock(); - const index = pool.map.getKeyAdapted(str, std.hash_map.StringIndexAdapter{ .bytes = &pool.bytes }) orelse return null; + + const adapter = PrecomputedStringIndexAdapter{ + .bytes = &pool.bytes, + .adapted_key = str, + .precomputed_key_hash = precomputed_key_hash, + }; + + const index = pool.map.getKeyAdapted(str, adapter) orelse return null; return @enumFromInt(index); } - /// asserts that `str` contains no null bytes - /// returns `error.OutOfMemory` if adding this new string would increase the amount of allocated bytes above std.math.maxInt(u32) + /// Asserts that `str` contains no null bytes. + /// Returns `error.OutOfMemory` if adding this new string would increase the amount of allocated bytes above std.math.maxInt(u32) pub fn getOrPutString(pool: *Pool, allocator: Allocator, str: []const u8) error{OutOfMemory}!String { assert(std.mem.indexOfScalar(u8, str, 0) == null); const start_index = std.math.cast(u32, pool.bytes.items.len) orelse return error.OutOfMemory; + // precompute the hash before acquiring the lock + const precomputed_key_hash = std.hash_map.hashString(str); + pool.mutex.lock(); defer pool.mutex.unlock(); + const adapter = PrecomputedStringIndexAdapter{ + .bytes = &pool.bytes, + .adapted_key = str, + .precomputed_key_hash = precomputed_key_hash, + }; + pool.bytes.ensureUnusedCapacity(allocator, str.len + 1) catch { // If allocation fails, try to do the lookup anyway. - const index = pool.map.getKeyAdapted(str, std.hash_map.StringIndexAdapter{ .bytes = &pool.bytes }) orelse return error.OutOfMemory; + const index = pool.map.getKeyAdapted(str, adapter) orelse return error.OutOfMemory; return @enumFromInt(index); }; const gop = try pool.map.getOrPutContextAdapted( allocator, str, - std.hash_map.StringIndexAdapter{ .bytes = &pool.bytes }, + adapter, std.hash_map.StringIndexContext{ .bytes = &pool.bytes }, ); @@ -86,13 +112,7 @@ pub fn StringPool(comptime config: Config) type { return @enumFromInt(gop.key_ptr.*); } - pub fn hashString(pool: *Pool, hasher: anytype, index: String) void { - pool.mutex.lock(); - defer pool.mutex.unlock(); - const str = pool.stringToSliceUnsafe(index); - hasher.update(str); - } - + /// Caller owns the memory. pub fn stringToSliceAlloc(pool: *Pool, allocator: Allocator, index: String) Allocator.Error![]const u8 { pool.mutex.lock(); defer pool.mutex.unlock(); @@ -101,6 +121,7 @@ pub fn StringPool(comptime config: Config) type { return try allocator.dupe(u8, std.mem.sliceTo(string_bytes + start, 0)); } + /// Caller owns the memory. pub fn stringToSliceAllocZ(pool: *Pool, allocator: Allocator, index: String) Allocator.Error![:0]const u8 { pool.mutex.lock(); defer pool.mutex.unlock(); @@ -109,10 +130,41 @@ pub fn StringPool(comptime config: Config) type { return try allocator.dupeZ(u8, std.mem.sliceTo(string_bytes + start, 0)); } + /// storage a slice that points into the internal storage of the `StringPool`. + /// always call `release` method to unlock the `StringPool`. + /// + /// see `stringToSliceLock` + pub const LockedString = struct { + slice: [:0]const u8, + + pub fn release(locked_string: LockedString, pool: *Pool) void { + _ = locked_string; + pool.mutex.unlock(); + } + }; + + /// returns the underlying slice from an interned string + /// equal strings are guaranteed to share the same storage + /// + /// Will lock the `StringPool` until the `release` method is called on the returned locked string. + pub fn stringToSliceLock(pool: *Pool, index: String) LockedString { + pool.mutex.lock(); + return .{ .slice = pool.stringToSliceUnsafe(index) }; + } + + /// returns the underlying slice from an interned string + /// equal strings are guaranteed to share the same storage + /// + /// only callable when thread safety is disabled. + pub fn stringToSlice(pool: *Pool, index: String) [:0]const u8 { + if (config.thread_safe) @compileError("use stringToSliceLock instead"); + return pool.stringToSliceUnsafe(index); + } + /// returns the underlying slice from an interned string /// equal strings are guaranteed to share the same storage pub fn stringToSliceUnsafe(pool: *Pool, index: String) [:0]const u8 { - std.debug.assert(@intFromEnum(index) < pool.bytes.items.len); + assert(@intFromEnum(index) < pool.bytes.items.len); const string_bytes: [*:0]u8 = @ptrCast(pool.bytes.items.ptr); const start = @intFromEnum(index); return std.mem.sliceTo(string_bytes + start, 0); @@ -125,6 +177,11 @@ pub fn StringPool(comptime config: Config) type { pub fn deinit(pool: *Pool, allocator: Allocator) void { pool.bytes.deinit(allocator); pool.map.deinit(allocator); + if (builtin.mode == .Debug and !builtin.single_threaded and config.thread_safe) { + // detect deadlock when calling deinit while holding the lock + pool.mutex.lock(); + pool.mutex.unlock(); + } pool.* = undefined; } @@ -147,34 +204,55 @@ pub fn StringPool(comptime config: Config) type { fn print(ctx: FormatContext, comptime fmt_str: []const u8, _: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { if (fmt_str.len != 0) std.fmt.invalidFmtError(fmt_str, ctx.string); - ctx.pool.mutex.lock(); - defer ctx.pool.mutex.unlock(); - try writer.writeAll(ctx.pool.stringToSliceUnsafe(ctx.string)); + const locked_string = ctx.pool.stringToSliceLock(ctx.string); + defer locked_string.release(ctx.pool); + try writer.writeAll(locked_string.slice); } }; } +/// same as `std.hash_map.StringIndexAdapter` but the hash of the adapted key is precomputed +const PrecomputedStringIndexAdapter = struct { + bytes: *const std.ArrayListUnmanaged(u8), + adapted_key: []const u8, + precomputed_key_hash: u64, + + pub fn eql(self: @This(), a_slice: []const u8, b: u32) bool { + const b_slice = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(self.bytes.items.ptr)) + b, 0); + return std.mem.eql(u8, a_slice, b_slice); + } + + pub fn hash(self: @This(), adapted_key: []const u8) u64 { + assert(std.mem.eql(u8, self.adapted_key, adapted_key)); + return self.precomputed_key_hash; + } +}; + test StringPool { const gpa = std.testing.allocator; - var pool = StringPool(.{}){}; + var pool = StringPool(.{ .thread_safe = false }){}; defer pool.deinit(gpa); const str = "All Your Codebase Are Belong To Us"; const index = try pool.getOrPutString(gpa, str); - try std.testing.expectEqualStrings(str, pool.stringToSliceUnsafe(index)); + + const locked_string = pool.stringToSliceLock(index); + defer locked_string.release(&pool); + + try std.testing.expectEqualStrings(str, locked_string.slice); try std.testing.expectFmt(str, "{}", .{index.fmt(&pool)}); } test "StringPool - check interning" { const gpa = std.testing.allocator; - var pool = StringPool(.{}){}; + var pool = StringPool(.{ .thread_safe = false }){}; defer pool.deinit(gpa); const str = "All Your Codebase Are Belong To Us"; const index1 = try pool.getOrPutString(gpa, str); const index2 = try pool.getOrPutString(gpa, str); const index3 = pool.getString(str).?; - const storage1 = pool.stringToSliceUnsafe(index1); + const storage1 = pool.stringToSlice(index1); const storage2 = pool.stringToSliceUnsafe(index2); try std.testing.expectEqual(index1, index2); @@ -185,15 +263,6 @@ test "StringPool - check interning" { try std.testing.expectEqual(storage1.len, storage2.len); } -test "StringPool - empty string" { - if (true) return error.SkipZigTest; // TODO - const gpa = std.testing.allocator; - var pool = StringPool(.{}){}; - defer pool.deinit(gpa); - - try std.testing.expectEqualStrings("", pool.stringToSliceUnsafe(.empty)); -} - test "StringPool - getOrPut on existing string without allocation" { const gpa = std.testing.allocator; var failing_gpa = std.testing.FailingAllocator.init(gpa, .{ .fail_index = 0 }); From be396db7940478d3686fc4dcf7ef8fde6a9ec6d8 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Wed, 20 Dec 2023 05:24:31 +0100 Subject: [PATCH 25/30] rework storage of BigInt limbs --- src/ComptimeInterpreter.zig | 51 +++++++------- src/analyser/InternPool.zig | 129 ++++++++++++++++++++++++++++++------ 2 files changed, 133 insertions(+), 47 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index e2cbe5c2e..025f805d8 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -738,34 +738,31 @@ pub fn interpret( const number_type = if (nl == .float) Index.comptime_float_type else Index.comptime_int_type; - const value = try interpreter.ip.get( - interpreter.allocator, - switch (nl) { - .float => Key{ - .float_comptime_value = try std.fmt.parseFloat(f128, s), - }, - .int => if (s[0] == '-') Key{ - .int_i64_value = .{ - .ty = number_type, - .int = try std.fmt.parseInt(i64, s, 0), - }, - } else Key{ - .int_u64_value = .{ - .ty = number_type, - .int = try std.fmt.parseInt(u64, s, 0), - }, - }, - .big_int => |base| blk: { - var big_int = try std.math.big.int.Managed.init(interpreter.allocator); - defer big_int.deinit(); - const prefix_length: usize = if (base != .decimal) 2 else 0; - try big_int.setString(@intFromEnum(base), s[prefix_length..]); - std.debug.assert(number_type == .comptime_int_type); - break :blk Key{ .int_big_value = .{ .ty = number_type, .int = big_int.toConst() } }; - }, - .failure => return error.CriticalAstFailure, + const value = switch (nl) { + .float => try interpreter.ip.get( + interpreter.allocator, + .{ .float_comptime_value = try std.fmt.parseFloat(f128, s) }, + ), + .int => if (s[0] == '-') + try interpreter.ip.get( + interpreter.allocator, + .{ .int_i64_value = .{ .ty = number_type, .int = try std.fmt.parseInt(i64, s, 0) } }, + ) + else + try interpreter.ip.get( + interpreter.allocator, + .{ .int_u64_value = .{ .ty = number_type, .int = try std.fmt.parseInt(u64, s, 0) } }, + ), + .big_int => |base| blk: { + var big_int = try std.math.big.int.Managed.init(interpreter.allocator); + defer big_int.deinit(); + const prefix_length: usize = if (base != .decimal) 2 else 0; + try big_int.setString(@intFromEnum(base), s[prefix_length..]); + std.debug.assert(number_type == .comptime_int_type); + break :blk try interpreter.ip.getBigInt(interpreter.allocator, number_type, big_int.toConst()); }, - ); + .failure => return error.CriticalAstFailure, + }; return InterpretResult{ .value = Value{ .interpreter = interpreter, diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 142621487..7c8a64355 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -5,6 +5,8 @@ items: std.MultiArrayList(Item) = .{}, extra: std.ArrayListUnmanaged(u8) = .{}, string_pool: StringPool = .{}, +limbs: std.ArrayListUnmanaged(usize) = .{}, + decls: std.SegmentedList(InternPool.Decl, 0) = .{}, structs: std.SegmentedList(InternPool.Struct, 0) = .{}, enums: std.SegmentedList(InternPool.Enum, 0) = .{}, @@ -153,7 +155,40 @@ pub const Key = union(enum) { pub const BigInt = struct { ty: Index, - int: std.math.big.int.Const, + storage: union(enum) { + /// The limbs are stored outside of the `InternPool` and are managed by the caller of + /// the `Internpool.get` function. + /// Use this field when inserting a new BigInt into the `InternPool` instead of manually + /// storing the limbs in the `InternPool` with `getLimbSlice` because `Internpool.get` + /// function can perform deduplication of big ints to avoid storing the limbs twice. + /// See `InternPool.getBigInt`. + /// + /// This field will never be active as the returned `Key` from `InternPool.get`. + external: std.math.big.int.Const, + /// The limbs are stored in the `InternPool`. + /// This field will always be active as the returned `Key` from `InternPool.get`. + internal: struct { + positive: bool, + limbs: LimbSlice, + }, + }, + + pub fn isPositive(int: BigInt) bool { + switch (int.storage) { + .external => |external| return external.positive, + .internal => |internal| return internal.positive, + } + } + + pub fn getConst(int: BigInt, ip: *const InternPool) std.math.big.int.Const { + switch (int.storage) { + .external => |external| return external, + .internal => |internal| return .{ + .positive = internal.positive, + .limbs = internal.limbs.get(ip), + }, + } + } }; const F64Value = packed struct { @@ -242,7 +277,7 @@ pub const Key = union(enum) { const BigIntInternal = struct { ty: Index, - limbs: []const std.math.big.Limb, + limbs: LimbSlice, }; pub fn hash32(key: Key, ip: *const InternPool) u32 { @@ -329,7 +364,12 @@ pub const Key = union(enum) { }, .int_big_value => |int_big_value| { std.hash.autoHash(hasher, int_big_value.ty); - std.hash.autoHashStrat(hasher, int_big_value.int, .Deep); + std.hash.autoHash(hasher, int_big_value.isPositive()); + const limbs = switch (int_big_value.storage) { + .external => |int| int.limbs, + .internal => |int| int.limbs.get(ip), + }; + hasher.update(std.mem.sliceAsBytes(limbs)); }, .aggregate => |aggregate| { std.hash.autoHash(hasher, aggregate.ty); @@ -443,7 +483,7 @@ pub const Key = union(enum) { const b_info = b.int_big_value; if (a_info.ty != b_info.ty) return false; - if (!a_info.int.eql(b_info.int)) return false; + if (!a_info.getConst(ip).eql(b_info.getConst(ip))) return false; return true; }, @@ -647,6 +687,20 @@ pub const StringSlice = struct { } }; +pub const LimbSlice = struct { + start: u32, + len: u32, + + pub const empty = LimbSlice{ + .start = std.math.maxInt(u32), + .len = 0, + }; + + pub fn get(limbs: LimbSlice, ip: *const InternPool) []std.math.big.Limb { + return ip.limbs.items[limbs.start..][0..limbs.len]; + } +}; + pub const Tag = enum(u8) { /// A type that can be represented with only an enum tag. /// data is SimpleType enum value @@ -1037,6 +1091,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { ip.items.deinit(gpa); ip.extra.deinit(gpa); ip.string_pool.deinit(gpa); + ip.limbs.deinit(gpa); var struct_it = ip.structs.iterator(0); while (struct_it.next()) |item| { @@ -1089,9 +1144,11 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { const big_int = ip.extraData(Key.BigIntInternal, data); break :blk .{ .ty = big_int.ty, - .int = .{ - .positive = item.tag == .int_big_positive, - .limbs = big_int.limbs, + .storage = .{ + .internal = .{ + .positive = item.tag == .int_big_positive, + .limbs = big_int.limbs, + }, }, }; } }, @@ -1189,10 +1246,13 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { .data = try ip.addExtra(gpa, Key.I64Value, int_val), }, .int_big_value => |big_int_val| .{ - .tag = if (big_int_val.int.positive) .int_big_positive else .int_big_negative, + .tag = if (big_int_val.isPositive()) .int_big_positive else .int_big_negative, .data = try ip.addExtra(gpa, Key.BigIntInternal, .{ .ty = big_int_val.ty, - .limbs = big_int_val.int.limbs, + .limbs = switch (big_int_val.storage) { + .external => |int| try ip.getLimbSlice(gpa, int.limbs), + .internal => |int| int.limbs, + }, }), }, .float_16_value => |float_val| .{ @@ -1291,6 +1351,19 @@ pub fn getStringSlice(ip: *InternPool, gpa: Allocator, data: []const String) err }; } +/// prefer `getBigInt` when creating new big ints to allow for deduplication. +pub fn getLimbSlice(ip: *InternPool, gpa: Allocator, data: []const std.math.big.Limb) error{OutOfMemory}!LimbSlice { + if (data.len == 0) return LimbSlice.empty; + + const start: u32 = @intCast(ip.limbs.items.len); + try ip.limbs.appendSlice(gpa, data); + + return .{ + .start = start, + .len = @intCast(data.len), + }; +} + pub fn getDecl(ip: *const InternPool, index: InternPool.Decl.Index) *const InternPool.Decl { return ip.decls.at(@intFromEnum(index)); } @@ -1582,7 +1655,7 @@ fn intFitsInType( var big_int = std.math.big.int.Mutable.init(&buffer, value.int); return big_int.toConst().fitsInTwosComp(info.signedness, info.bits); }, - .int_big_value => |int| return int.int.fitsInTwosComp(info.signedness, info.bits), + .int_big_value => |int| return int.getConst(ip).fitsInTwosComp(info.signedness, info.bits), else => unreachable, } } @@ -1596,7 +1669,17 @@ fn coerceInt( switch (ip.indexToKey(val)) { .int_i64_value => |int| return try ip.get(gpa, .{ .int_i64_value = .{ .int = int.int, .ty = dest_ty } }), .int_u64_value => |int| return try ip.get(gpa, .{ .int_u64_value = .{ .int = int.int, .ty = dest_ty } }), - .int_big_value => |int| return try ip.get(gpa, .{ .int_big_value = .{ .int = int.int, .ty = dest_ty } }), + .int_big_value => |int| return try ip.get(gpa, .{ + .int_big_value = .{ + .ty = dest_ty, + .storage = .{ + .internal = .{ + .positive = int.storage.internal.positive, + .limbs = int.storage.internal.limbs, + }, + }, + }, + }), .undefined_value => |info| return try ip.getUndefined(gpa, info.ty), .unknown_value => |info| return try ip.getUnknown(gpa, info.ty), else => unreachable, @@ -3423,6 +3506,13 @@ pub fn toInt(ip: *const InternPool, val: Index, comptime T: type) !?T { }; } +pub fn getBigInt(ip: *InternPool, gpa: Allocator, ty: Index, int: std.math.big.int.Const) Allocator.Error!Index { + assert(ip.isType(ty)); + return try ip.get(gpa, .{ + .int_big_value = .{ .ty = ty, .storage = .{ .external = int } }, + }); +} + pub fn getNull(ip: *InternPool, gpa: Allocator, ty: Index) Allocator.Error!Index { if (ty == .none) return Index.null_value; assert(ip.isType(ty)); @@ -3692,7 +3782,7 @@ fn printInternal(ip: *InternPool, ty: Index, writer: anytype, options: FormatOpt }, .int_u64_value => |i| try writer.print("{d}", .{i.int}), .int_i64_value => |i| try writer.print("{d}", .{i.int}), - .int_big_value => |i| try writer.print("{d}", .{i.int}), + .int_big_value => |i| try writer.print("{d}", .{i.getConst(ip)}), .float_16_value => |float| try writer.print("{d}", .{float}), .float_32_value => |float| try writer.print("{d}", .{float}), .float_64_value => |float| try writer.print("{d}", .{float}), @@ -3865,14 +3955,13 @@ test "big int value" { try result.pow(&a, 128); - const positive_big_int_value = try ip.get(gpa, .{ .int_big_value = .{ - .ty = .comptime_int_type, - .int = result.toConst(), - } }); - const negative_big_int_value = try ip.get(gpa, .{ .int_big_value = .{ - .ty = .comptime_int_type, - .int = result.toConst().negate(), - } }); + const positive_big_int_value = try ip.getBigInt(gpa, .comptime_int_type, result.toConst()); + const negative_big_int_value = try ip.getBigInt(gpa, .comptime_int_type, result.toConst().negate()); + + const another_positive_big_int_value = try ip.getBigInt(gpa, .comptime_int_type, result.toConst()); + + try std.testing.expect(positive_big_int_value != negative_big_int_value); + try std.testing.expectEqual(positive_big_int_value, another_positive_big_int_value); try expectFmt("340282366920938463463374607431768211456", "{}", .{positive_big_int_value.fmt(&ip)}); try expectFmt("-340282366920938463463374607431768211456", "{}", .{negative_big_int_value.fmt(&ip)}); From d592eaad4b75f55b1cf1eef555689cc9be684a32 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Wed, 20 Dec 2023 05:46:00 +0100 Subject: [PATCH 26/30] replace `encoding.zig` with simpler u32 based encoding --- src/analyser/InternPool.zig | 109 ++++++++++++++++--- src/analyser/analyser.zig | 1 - src/analyser/encoding.zig | 210 ------------------------------------ 3 files changed, 92 insertions(+), 228 deletions(-) delete mode 100644 src/analyser/encoding.zig diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 7c8a64355..875cc539b 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -2,7 +2,7 @@ /// https://github.com/ziglang/zig/blob/master/src/InternPool.zig map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, items: std.MultiArrayList(Item) = .{}, -extra: std.ArrayListUnmanaged(u8) = .{}, +extra: std.ArrayListUnmanaged(u32) = .{}, string_pool: StringPool = .{}, limbs: std.ArrayListUnmanaged(usize) = .{}, @@ -22,7 +22,6 @@ const expectFmt = std.testing.expectFmt; pub const StringPool = @import("string_pool.zig").StringPool(.{}); pub const String = StringPool.String; -const encoding = @import("encoding.zig"); const ErrorMsg = @import("error_msg.zig").ErrorMsg; pub const Key = union(enum) { @@ -635,13 +634,12 @@ pub const Index = enum(u32) { /// prefer using `dupe` when iterating over all elements. pub fn at(slice: Slice, index: u32, ip: *const InternPool) Index { assert(index < slice.len); - return std.mem.bytesToValue(Index, ip.extra.items[slice.start + @sizeOf(u32) * index ..][0..@sizeOf(u32)]); + return @enumFromInt(ip.extra.items[slice.start + index]); } pub fn dupe(slice: Slice, gpa: Allocator, ip: *const InternPool) error{OutOfMemory}![]Index { if (slice.len == 0) return &.{}; - const bytes: []align(4) const u8 = @alignCast(ip.extra.items[slice.start..][0 .. @sizeOf(u32) * slice.len]); - return try gpa.dupe(Index, std.mem.bytesAsSlice(Index, bytes)); + return try gpa.dupe(Index, @ptrCast(ip.extra.items[slice.start..][0..slice.len])); } }; @@ -677,13 +675,12 @@ pub const StringSlice = struct { /// prefer using `dupe` when iterating over all elements. pub fn at(slice: StringSlice, index: u32, ip: *const InternPool) String { assert(index < slice.len); - return std.mem.bytesToValue(String, ip.extra.items[slice.start + @sizeOf(String) * index ..][0..@sizeOf(String)]); + return @enumFromInt(ip.extra.items[slice.start + index]); } pub fn dupe(slice: StringSlice, gpa: Allocator, ip: *const InternPool) error{OutOfMemory}![]String { if (slice.len == 0) return &.{}; - const bytes: []align(4) const u8 = @alignCast(ip.extra.items[slice.start..][0 .. @sizeOf(String) * slice.len]); - return try gpa.dupe(String, std.mem.bytesAsSlice(String, bytes)); + return try gpa.dupe(String, @ptrCast(ip.extra.items[slice.start..][0..slice.len])); } }; @@ -697,6 +694,7 @@ pub const LimbSlice = struct { }; pub fn get(limbs: LimbSlice, ip: *const InternPool) []std.math.big.Limb { + if (limbs.len == 0) return &.{}; return ip.limbs.items[limbs.start..][0..limbs.len]; } }; @@ -1331,7 +1329,7 @@ pub fn getIndexSlice(ip: *InternPool, gpa: Allocator, data: []const Index) error if (data.len == 0) return Index.Slice.empty; const start: u32 = @intCast(ip.extra.items.len); - try ip.extra.appendSlice(gpa, std.mem.sliceAsBytes(data)); + try ip.extra.appendSlice(gpa, @ptrCast(data)); return .{ .start = start, @@ -1343,7 +1341,7 @@ pub fn getStringSlice(ip: *InternPool, gpa: Allocator, data: []const String) err if (data.len == 0) return StringSlice.empty; const start: u32 = @intCast(ip.extra.items.len); - try ip.extra.appendSlice(gpa, std.mem.sliceAsBytes(data)); + try ip.extra.appendSlice(gpa, @ptrCast(data)); return .{ .start = start, @@ -1412,15 +1410,88 @@ fn addExtra(ip: *InternPool, gpa: Allocator, comptime T: type, extra: T) Allocat }; const result: u32 = @intCast(ip.extra.items.len); - var managed = ip.extra.toManaged(gpa); - defer ip.extra = managed.moveToUnmanaged(); - try encoding.encode(&managed, T, extra); + + const size = @divExact(@sizeOf(T), 4); + + try ip.extra.ensureUnusedCapacity(gpa, size); + inline for (std.meta.fields(T)) |field| { + const item = @field(extra, field.name); + switch (field.type) { + Index, + Decl.Index, + Decl.OptionalIndex, + StringPool.String, + StringPool.OptionalString, + std.builtin.Type.Pointer.Size, + => ip.extra.appendAssumeCapacity(@intFromEnum(item)), + + u32, + i32, + std.StaticBitSet(32), + Key.Pointer.Flags, + Key.Pointer.PackedOffset, + Key.Function.Flags, + => ip.extra.appendAssumeCapacity(@bitCast(item)), + + u64, + i64, + => ip.extra.appendSliceAssumeCapacity(&@as([2]u32, @bitCast(item))), + + Index.Slice, + StringSlice, + LimbSlice, + => ip.extra.appendSliceAssumeCapacity(&.{ item.start, item.len }), + + else => @compileError("unexpected: " ++ @typeName(field.type)), + } + } return result; } -fn extraData(ip: InternPool, comptime T: type, index: usize) T { - var bytes: []const u8 = ip.extra.items[index..]; - return encoding.decode(&bytes, T); +fn extraData(ip: *const InternPool, comptime T: type, index: u32) T { + var result: T = undefined; + var i: u32 = 0; + inline for (std.meta.fields(T)) |field| { + const item = ip.extra.items[index + i]; + i += 1; + @field(result, field.name) = switch (field.type) { + Index, + StringPool.String, + StringPool.OptionalString, + Decl.Index, + Decl.OptionalIndex, + std.builtin.Type.Pointer.Size, + // std.builtin.AddressSpace, + // std.builtin.CallingConvention, + => @enumFromInt(item), + + u32, + i32, + std.StaticBitSet(32), + Key.Pointer.Flags, + Key.Pointer.PackedOffset, + Key.Function.Flags, + => @bitCast(item), + + u64, + i64, + => blk: { + defer i += 1; + break :blk @bitCast([2]u32{ item, ip.extra.items[index + i] }); + }, + + Index.Slice, + StringSlice, + LimbSlice, + => blk: { + defer i += 1; + break :blk .{ .start = item, .len = ip.extra.items[index + i] }; + }, + + else => @compileError("unexpected: " ++ @typeName(field.type)), + }; + } + return result; } const KeyAdapter = struct { @@ -2857,7 +2928,7 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { .aggregate_value, .union_value, .error_value, - => std.mem.bytesToValue(Index, ip.extra.items[ip.items.items(.data)[@intFromEnum(index)]..][0..@sizeOf(Index)]), + => @enumFromInt(ip.extra.items[ip.items.items(.data)[@intFromEnum(index)]]), // the type is the `data` field .null_value, @@ -3963,6 +4034,10 @@ test "big int value" { try std.testing.expect(positive_big_int_value != negative_big_int_value); try std.testing.expectEqual(positive_big_int_value, another_positive_big_int_value); + try std.testing.expectEqual(Index.comptime_int_type, ip.typeOf(positive_big_int_value)); + try std.testing.expectEqual(Index.comptime_int_type, ip.typeOf(negative_big_int_value)); + try std.testing.expectEqual(Index.comptime_int_type, ip.typeOf(another_positive_big_int_value)); + try expectFmt("340282366920938463463374607431768211456", "{}", .{positive_big_int_value.fmt(&ip)}); try expectFmt("-340282366920938463463374607431768211456", "{}", .{negative_big_int_value.fmt(&ip)}); } diff --git a/src/analyser/analyser.zig b/src/analyser/analyser.zig index b0e2084f0..726005b67 100644 --- a/src/analyser/analyser.zig +++ b/src/analyser/analyser.zig @@ -1,7 +1,6 @@ pub const completions = @import("completions.zig"); pub const InternPool = @import("InternPool.zig"); pub const StringPool = @import("string_pool.zig").StringPool; -pub const encoding = @import("encoding.zig"); pub const degibberish = @import("degibberish.zig"); comptime { diff --git a/src/analyser/encoding.zig b/src/analyser/encoding.zig deleted file mode 100644 index 2f18dd5e1..000000000 --- a/src/analyser/encoding.zig +++ /dev/null @@ -1,210 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; - -const Index = usize; - -pub fn encode(extra: *std.ArrayList(u8), comptime T: type, data: anytype) Allocator.Error!void { - switch (@typeInfo(T)) { - .Type, - .NoReturn, - .ComptimeFloat, - .ComptimeInt, - .Undefined, - .Null, - .ErrorUnion, - .ErrorSet, - .Fn, - .Opaque, - .Frame, - .AnyFrame, - .EnumLiteral, - => @compileError("Unable to encode type " ++ @typeName(T)), - - .Void => {}, - .Bool => try encode(extra, u1, @intFromBool(data)), - .Int => try extra.appendSlice(std.mem.asBytes(&data)), - .Float => |info| switch (info.bits) { - 16 => try encode(extra, u16, @as(u16, @bitCast(data))), - 32 => try encode(extra, u32, @as(u32, @bitCast(data))), - 64 => try encode(extra, u64, @as(u64, @bitCast(data))), - 80 => try encode(extra, u80, @as(u80, @bitCast(data))), - 128 => try encode(extra, u128, @as(u128, @bitCast(data))), - else => @compileError("Unable to encode type " ++ @typeName(T)), - }, - .Pointer => |info| { - switch (info.size) { - .One => { - if (comptime canEncodeAsBytes(info.child)) { - try extra.appendNTimes(undefined, std.mem.alignPointerOffset(extra.items.ptr + extra.items.len, info.alignment).?); - try encode(extra, info.child, data.*); - } else { - @compileError("Encoding " ++ @typeName(T) ++ " would require allocation"); - } - }, - .Slice => { - if (comptime canEncodeAsBytes(info.child)) { - try encode(extra, u32, @as(u32, @intCast(data.len))); - try extra.appendNTimes(undefined, std.mem.alignPointerOffset(extra.items.ptr + extra.items.len, info.alignment).?); - try extra.appendSlice(std.mem.sliceAsBytes(data)); - } else { - @compileError("Encoding " ++ @typeName(T) ++ " would require allocation"); - } - }, - - .Many, - .C, - => @compileError("Unable to encode type " ++ @typeName(T)), - } - }, - .Array => |info| { - for (data) |item| { - try encode(extra, info.child, item); - } - }, - .Struct => |info| { - switch (info.layout) { - .Packed, - .Extern, - => return try extra.appendSlice(std.mem.asBytes(&data)), - .Auto => { - inline for (info.fields) |field| { - try encode(extra, field.type, @field(data, field.name)); - } - }, - } - }, - .Optional => { - try encode(extra, bool, data == null); - if (data) |item| { - try encode(extra, item); - } - }, - .Enum => |info| try encode(extra, info.tag_type, @intFromEnum(data)), - .Union => @compileError("TODO"), - .Vector => |info| { - const array: [info.len]info.child = data; - try encode(extra, array); - }, - } -} - -pub fn decode(extra: *[]const u8, comptime T: type) T { - return switch (@typeInfo(T)) { - .Type, - .NoReturn, - .ComptimeFloat, - .ComptimeInt, - .Undefined, - .Null, - .ErrorUnion, - .ErrorSet, - .Fn, - .Opaque, - .Frame, - .AnyFrame, - .EnumLiteral, - => @compileError("Unable to decode type " ++ @typeName(T)), - - .Void => {}, - .Bool => decode(extra, u1) == 1, - .Int => std.mem.bytesToValue(T, readArray(extra, @sizeOf(T))), - .Float => |info| switch (info.bits) { - 16 => @as(T, @bitCast(decode(extra, u16))), - 32 => @as(T, @bitCast(decode(extra, u32))), - 64 => @as(T, @bitCast(decode(extra, u64))), - 80 => @as(T, @bitCast(decode(extra, u80))), - 128 => @as(T, @bitCast(decode(extra, u128))), - else => @compileError("Unable to decode type " ++ @typeName(T)), - }, - .Pointer => |info| { - switch (info.size) { - .One => { - if (comptime canEncodeAsBytes(info.child)) { - extra.* = alignForward(extra.*, info.alignment); - return std.mem.bytesAsValue(T, readArray(extra, @sizeOf(info.child))); - } else { - @compileError("Decoding " ++ @typeName(T) ++ " would require allocation"); - } - }, - .Slice => { - if (comptime canEncodeAsBytes(info.child)) { - const len = decode(extra, u32); - extra.* = alignForward(extra.*, info.alignment); - const bytes = readBytes(extra, len * @sizeOf(info.child)); - return std.mem.bytesAsSlice(info.child, @as([]align(info.alignment) const u8, @alignCast(bytes))); - } else { - @compileError("Decoding " ++ @typeName(T) ++ " would require allocation"); - } - }, - - .Many, - .C, - => @compileError("Unable to decode type " ++ @typeName(T)), - } - }, - .Array => |info| blk: { - var array: T = undefined; - var i: usize = 0; - while (i < info.len) : (i += 1) { - array[i] = decode(extra, info.child); - } - break :blk array; - }, - .Struct => |info| { - switch (info.layout) { - .Packed, - .Extern, - => return std.mem.bytesToValue(T, readArray(extra, @sizeOf(T))), - .Auto => { - var result: T = undefined; - inline for (info.fields) |field| { - @field(result, field.name) = decode(extra, field.type); - } - return result; - }, - } - }, - .Optional => |info| blk: { - const is_null = decode(extra, bool); - if (is_null) { - break :blk null; - } else { - break :blk decode(extra, info.child); - } - }, - .Enum => |info| @as(T, @enumFromInt(decode(extra, info.tag_type))), - .Union => @compileError("TODO"), - .Vector => |info| decode(extra, [info.len]info.child), - }; -} - -pub fn canEncodeAsBytes(comptime T: type) bool { - return switch (@typeInfo(T)) { - .Void, .Bool, .Int, .Float, .Enum, .Vector => true, - .Array => |info| canEncodeAsBytes(info.child), - .Struct => |info| info.layout != .Auto, - .Union => |info| info.layout != .Auto, - else => false, - }; -} - -/// forward aligns `extra` until it has the given alignment -pub fn alignForward(extra: []const u8, alignment: usize) []const u8 { - const unaligned = @intFromPtr(extra.ptr); - const offset = std.mem.alignForward(usize, unaligned, alignment) - unaligned; - const result = extra[offset..]; - std.debug.assert(std.mem.isAligned(@intFromPtr(result.ptr), alignment)); - return result; -} - -pub fn readBytes(extra: *[]const u8, n: usize) []const u8 { - defer extra.* = extra.*[n..]; - return extra.*[0..n]; -} - -pub fn readArray(extra: *[]const u8, comptime n: usize) *const [n]u8 { - defer extra.* = extra.*[n..]; - return extra.*[0..n]; -} From 8391e8fa9e975a9a912c3c760bf5bcb6bd216def Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Wed, 20 Dec 2023 08:20:14 +0100 Subject: [PATCH 27/30] add thread safety to InternPool --- src/analyser/InternPool.zig | 385 +++++++++++++++++++++++++++--------- 1 file changed, 292 insertions(+), 93 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 875cc539b..694a9f7c5 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -4,6 +4,7 @@ map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, items: std.MultiArrayList(Item) = .{}, extra: std.ArrayListUnmanaged(u32) = .{}, string_pool: StringPool = .{}, +lock: std.Thread.RwLock = .{}, limbs: std.ArrayListUnmanaged(usize) = .{}, @@ -179,12 +180,13 @@ pub const Key = union(enum) { } } - pub fn getConst(int: BigInt, ip: *const InternPool) std.math.big.int.Const { + /// TODO this should be thread-safe + pub fn getConst(int: BigInt, ip: *InternPool) std.math.big.int.Const { switch (int.storage) { .external => |external| return external, .internal => |internal| return .{ .positive = internal.positive, - .limbs = internal.limbs.get(ip), + .limbs = internal.limbs.getUnprotectedSlice(ip), }, } } @@ -279,17 +281,17 @@ pub const Key = union(enum) { limbs: LimbSlice, }; - pub fn hash32(key: Key, ip: *const InternPool) u32 { + pub fn hash32(key: Key, ip: *InternPool) u32 { return @truncate(key.hash64(ip)); } - pub fn hash64(key: Key, ip: *const InternPool) u64 { + pub fn hash64(key: Key, ip: *InternPool) u64 { var hasher = std.hash.Wyhash.init(0); key.hashWithHasher(&hasher, ip); return hasher.final(); } - pub fn hashWithHasher(key: Key, hasher: anytype, ip: *const InternPool) void { + pub fn hashWithHasher(key: Key, hasher: anytype, ip: *InternPool) void { std.hash.autoHash(hasher, std.meta.activeTag(key)); switch (key) { inline .simple_type, @@ -337,10 +339,7 @@ pub const Key = union(enum) { .error_set_type => |error_set_type| { std.hash.autoHash(hasher, error_set_type.owner_decl); - std.hash.autoHash(hasher, error_set_type.names.len); - for (0..error_set_type.names.len) |i| { - std.hash.autoHash(hasher, error_set_type.names.at(@intCast(i), ip)); - } + error_set_type.names.hashWithHasher(hasher, ip); }, .function_type => |function_type| { std.hash.autoHash(hasher, function_type.args_is_comptime); @@ -348,39 +347,41 @@ pub const Key = union(enum) { std.hash.autoHash(hasher, function_type.args_is_noalias); std.hash.autoHash(hasher, function_type.return_type); - std.hash.autoHash(hasher, function_type.args.len); - for (0..function_type.args.len) |i| { - std.hash.autoHash(hasher, function_type.args.at(@intCast(i), ip)); - } + function_type.args.hashWithHasher(hasher, ip); }, .tuple_type => |tuple_type| { - std.debug.assert(tuple_type.types.len == tuple_type.values.len); - std.hash.autoHash(hasher, tuple_type.types.len); - for (0..tuple_type.types.len) |i| { - std.hash.autoHash(hasher, tuple_type.types.at(@intCast(i), ip)); - std.hash.autoHash(hasher, tuple_type.values.at(@intCast(i), ip)); - } + assert(tuple_type.types.len == tuple_type.values.len); + tuple_type.types.hashWithHasher(hasher, ip); + tuple_type.values.hashWithHasher(hasher, ip); }, .int_big_value => |int_big_value| { std.hash.autoHash(hasher, int_big_value.ty); std.hash.autoHash(hasher, int_big_value.isPositive()); - const limbs = switch (int_big_value.storage) { - .external => |int| int.limbs, - .internal => |int| int.limbs.get(ip), - }; - hasher.update(std.mem.sliceAsBytes(limbs)); + switch (int_big_value.storage) { + .external => |int| { + hasher.update(std.mem.sliceAsBytes(int.limbs)); + }, + .internal => |int| { + int.limbs.hashWithHasher(hasher, ip); + }, + } }, .aggregate => |aggregate| { std.hash.autoHash(hasher, aggregate.ty); - std.hash.autoHash(hasher, aggregate.values.len); - for (0..aggregate.values.len) |i| { - std.hash.autoHash(hasher, aggregate.values.at(@intCast(i), ip)); - } + aggregate.values.hashWithHasher(hasher, ip); }, } } - pub fn eql(a: Key, b: Key, ip: *const InternPool) bool { + pub fn eql(a: Key, b: Key, ip: *InternPool) bool { + return eqlCustom(a, b, ip, true); + } + + fn eqlNoLock(a: Key, b: Key, ip: *const InternPool) bool { + return eqlCustom(a, b, @constCast(ip), false); + } + + fn eqlCustom(a: Key, b: Key, ip: *InternPool, should_lock: bool) bool { const a_tag = std.meta.activeTag(a); const b_tag = std.meta.activeTag(b); if (a_tag != b_tag) return false; @@ -430,9 +431,14 @@ pub const Key = union(enum) { if (a_info.owner_decl != b_info.owner_decl) return false; if (a_info.names.len != b_info.names.len) return false; - for (0..a_info.names.len) |i| { - const a_name = a_info.names.at(@intCast(i), ip); - const b_name = b_info.names.at(@intCast(i), ip); + + if (should_lock) ip.lock.lockShared(); + defer if (should_lock) ip.lock.unlockShared(); + + for ( + a_info.names.getUnprotectedSlice(ip), + b_info.names.getUnprotectedSlice(ip), + ) |a_name, b_name| { if (a_name != b_name) return false; } @@ -452,9 +458,14 @@ pub const Key = union(enum) { if (!a_info.args_is_noalias.eql(b_info.args_is_noalias)) return false; if (a_info.args.len != b_info.args.len) return false; - for (0..a_info.args.len) |i| { - const a_arg = a_info.args.at(@intCast(i), ip); - const b_arg = b_info.args.at(@intCast(i), ip); + + if (should_lock) ip.lock.lockShared(); + defer if (should_lock) ip.lock.unlockShared(); + + for ( + a_info.args.getUnprotectedSlice(ip), + b_info.args.getUnprotectedSlice(ip), + ) |a_arg, b_arg| { if (a_arg != b_arg) return false; } @@ -463,17 +474,20 @@ pub const Key = union(enum) { .tuple_type => |a_info| { const b_info = b.tuple_type; - std.debug.assert(a_info.types.len == b_info.types.len); + assert(a_info.types.len == b_info.types.len); if (a_info.types.len != b_info.types.len) return false; if (a_info.values.len != b_info.values.len) return false; - for (0..a_info.types.len) |i| { - const a_ty = a_info.types.at(@intCast(i), ip); - const b_ty = b_info.types.at(@intCast(i), ip); - if (a_ty != b_ty) return false; + if (should_lock) ip.lock.lockShared(); + defer if (should_lock) ip.lock.unlockShared(); - const a_val = a_info.values.at(@intCast(i), ip); - const b_val = b_info.values.at(@intCast(i), ip); + for ( + a_info.types.getUnprotectedSlice(ip), + b_info.types.getUnprotectedSlice(ip), + a_info.values.getUnprotectedSlice(ip), + b_info.values.getUnprotectedSlice(ip), + ) |a_ty, b_ty, a_val, b_val| { + if (a_ty != b_ty) return false; if (a_val != b_val) return false; } return true; @@ -482,6 +496,10 @@ pub const Key = union(enum) { const b_info = b.int_big_value; if (a_info.ty != b_info.ty) return false; + + if (should_lock) ip.lock.lockShared(); + defer if (should_lock) ip.lock.unlockShared(); + if (!a_info.getConst(ip).eql(b_info.getConst(ip))) return false; return true; @@ -492,9 +510,14 @@ pub const Key = union(enum) { if (a_info.ty != b_info.ty) return false; if (a_info.values.len != b_info.values.len) return false; - for (0..a_info.values.len) |i| { - const a_val = a_info.values.at(@intCast(i), ip); - const b_val = b_info.values.at(@intCast(i), ip); + + if (should_lock) ip.lock.lockShared(); + defer if (should_lock) ip.lock.unlockShared(); + + for ( + a_info.values.getUnprotectedSlice(ip), + b_info.values.getUnprotectedSlice(ip), + ) |a_val, b_val| { if (a_val != b_val) return false; } @@ -632,14 +655,31 @@ pub const Index = enum(u32) { }; /// prefer using `dupe` when iterating over all elements. - pub fn at(slice: Slice, index: u32, ip: *const InternPool) Index { + pub fn at(slice: Slice, index: u32, ip: *InternPool) Index { assert(index < slice.len); + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return @enumFromInt(ip.extra.items[slice.start + index]); } - pub fn dupe(slice: Slice, gpa: Allocator, ip: *const InternPool) error{OutOfMemory}![]Index { + pub fn dupe(slice: Slice, gpa: Allocator, ip: *InternPool) error{OutOfMemory}![]Index { if (slice.len == 0) return &.{}; - return try gpa.dupe(Index, @ptrCast(ip.extra.items[slice.start..][0..slice.len])); + ip.lock.lockShared(); + defer ip.lock.unlockShared(); + return try gpa.dupe(Index, slice.getUnprotectedSlice(ip)); + } + + pub fn hashWithHasher(slice: Slice, hasher: anytype, ip: *InternPool) void { + std.hash.autoHash(hasher, slice.len); + if (slice.len == 0) return; + ip.lock.lockShared(); + defer ip.lock.unlockShared(); + hasher.update(std.mem.sliceAsBytes(slice.getUnprotectedSlice(ip))); + } + + fn getUnprotectedSlice(slice: Slice, ip: *const InternPool) []const Index { + if (slice.len == 0) return &.{}; + return @ptrCast(ip.extra.items[slice.start..][0..slice.len]); } }; @@ -673,14 +713,31 @@ pub const StringSlice = struct { }; /// prefer using `dupe` when iterating over all elements. - pub fn at(slice: StringSlice, index: u32, ip: *const InternPool) String { + pub fn at(slice: StringSlice, index: u32, ip: *InternPool) String { assert(index < slice.len); + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return @enumFromInt(ip.extra.items[slice.start + index]); } - pub fn dupe(slice: StringSlice, gpa: Allocator, ip: *const InternPool) error{OutOfMemory}![]String { + pub fn dupe(slice: StringSlice, gpa: Allocator, ip: *InternPool) error{OutOfMemory}![]String { if (slice.len == 0) return &.{}; - return try gpa.dupe(String, @ptrCast(ip.extra.items[slice.start..][0..slice.len])); + ip.lock.lockShared(); + defer ip.lock.unlockShared(); + return try gpa.dupe(String, slice.getUnprotectedSlice(ip)); + } + + pub fn hashWithHasher(slice: StringSlice, hasher: anytype, ip: *InternPool) void { + std.hash.autoHash(hasher, slice.len); + if (slice.len == 0) return; + ip.lock.lockShared(); + defer ip.lock.unlockShared(); + hasher.update(std.mem.sliceAsBytes(slice.getUnprotectedSlice(ip))); + } + + fn getUnprotectedSlice(slice: StringSlice, ip: *const InternPool) []const String { + if (slice.len == 0) return &.{}; + return @ptrCast(ip.extra.items[slice.start..][0..slice.len]); } }; @@ -693,7 +750,15 @@ pub const LimbSlice = struct { .len = 0, }; - pub fn get(limbs: LimbSlice, ip: *const InternPool) []std.math.big.Limb { + pub fn hashWithHasher(slice: LimbSlice, hasher: anytype, ip: *InternPool) void { + std.hash.autoHash(hasher, slice.len); + if (slice.len == 0) return; + ip.lock.lockShared(); + defer ip.lock.unlockShared(); + hasher.update(std.mem.sliceAsBytes(slice.getUnprotectedSlice(ip))); + } + + fn getUnprotectedSlice(limbs: LimbSlice, ip: *InternPool) []std.math.big.Limb { if (limbs.len == 0) return &.{}; return ip.limbs.items[limbs.start..][0..limbs.len]; } @@ -1110,7 +1175,15 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { ip.unions.deinit(gpa); } -pub fn indexToKey(ip: *const InternPool, index: Index) Key { +pub fn indexToKey(ip: *InternPool, index: Index) Key { + assert(index != .none); + + ip.lock.lockShared(); + defer ip.lock.unlockShared(); + return ip.indexToKeyNoLock(index); +} + +fn indexToKeyNoLock(ip: *const InternPool, index: Index) Key { assert(index != .none); const item = ip.items.get(@intFromEnum(index)); const data = item.data; @@ -1169,7 +1242,22 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key { } pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { - const adapter: KeyAdapter = .{ .ip = ip }; + const adapter: KeyAdapter = .{ + .ip = ip, + .precomputed_hash = key.hash32(ip), + }; + + not_found: { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); + + const index = ip.map.getIndexAdapted(key, adapter) orelse break :not_found; + return @enumFromInt(index); + } + + ip.lock.lock(); + defer ip.lock.unlock(); + const gop = try ip.map.getOrPutAdapted(gpa, key, adapter); if (gop.found_existing) return @enumFromInt(gop.index); @@ -1319,8 +1407,13 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { return @enumFromInt(ip.items.len - 1); } -pub fn contains(ip: *const InternPool, key: Key) ?Index { - const adapter: KeyAdapter = .{ .ip = &ip }; +pub fn contains(ip: *InternPool, key: Key) ?Index { + const adapter: KeyAdapter = .{ + .ip = ip, + .precomputed_hash = key.hash32(ip), + }; + ip.lock.lockShared(); + defer ip.lock.unlockShared(); const index = ip.map.getIndexAdapted(key, adapter) orelse return null; return @enumFromInt(index); } @@ -1328,6 +1421,9 @@ pub fn contains(ip: *const InternPool, key: Key) ?Index { pub fn getIndexSlice(ip: *InternPool, gpa: Allocator, data: []const Index) error{OutOfMemory}!Index.Slice { if (data.len == 0) return Index.Slice.empty; + ip.lock.lock(); + defer ip.lock.unlock(); + const start: u32 = @intCast(ip.extra.items.len); try ip.extra.appendSlice(gpa, @ptrCast(data)); @@ -1340,6 +1436,9 @@ pub fn getIndexSlice(ip: *InternPool, gpa: Allocator, data: []const Index) error pub fn getStringSlice(ip: *InternPool, gpa: Allocator, data: []const String) error{OutOfMemory}!StringSlice { if (data.len == 0) return StringSlice.empty; + ip.lock.lock(); + defer ip.lock.unlock(); + const start: u32 = @intCast(ip.extra.items.len); try ip.extra.appendSlice(gpa, @ptrCast(data)); @@ -1349,8 +1448,7 @@ pub fn getStringSlice(ip: *InternPool, gpa: Allocator, data: []const String) err }; } -/// prefer `getBigInt` when creating new big ints to allow for deduplication. -pub fn getLimbSlice(ip: *InternPool, gpa: Allocator, data: []const std.math.big.Limb) error{OutOfMemory}!LimbSlice { +fn getLimbSlice(ip: *InternPool, gpa: Allocator, data: []const std.math.big.Limb) error{OutOfMemory}!LimbSlice { if (data.len == 0) return LimbSlice.empty; const start: u32 = @intCast(ip.limbs.items.len); @@ -1362,44 +1460,68 @@ pub fn getLimbSlice(ip: *InternPool, gpa: Allocator, data: []const std.math.big. }; } -pub fn getDecl(ip: *const InternPool, index: InternPool.Decl.Index) *const InternPool.Decl { +pub fn getDecl(ip: *InternPool, index: InternPool.Decl.Index) *const InternPool.Decl { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return ip.decls.at(@intFromEnum(index)); } pub fn getDeclMut(ip: *InternPool, index: InternPool.Decl.Index) *InternPool.Decl { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return ip.decls.at(@intFromEnum(index)); } -pub fn getStruct(ip: *const InternPool, index: Struct.Index) *const Struct { +pub fn getStruct(ip: *InternPool, index: Struct.Index) *const Struct { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return ip.structs.at(@intFromEnum(index)); } pub fn getStructMut(ip: *InternPool, index: Struct.Index) *Struct { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return ip.structs.at(@intFromEnum(index)); } -pub fn getEnum(ip: *const InternPool, index: Enum.Index) *const Enum { +pub fn getEnum(ip: *InternPool, index: Enum.Index) *const Enum { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return ip.enums.at(@intFromEnum(index)); } pub fn getEnumMut(ip: *InternPool, index: Enum.Index) *Enum { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return ip.enums.at(@intFromEnum(index)); } -pub fn getUnion(ip: *const InternPool, index: Union.Index) *const Union { +pub fn getUnion(ip: *InternPool, index: Union.Index) *const Union { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return ip.unions.at(@intFromEnum(index)); } pub fn getUnionMut(ip: *InternPool, index: Union.Index) *Union { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return ip.unions.at(@intFromEnum(index)); } pub fn createDecl(ip: *InternPool, gpa: Allocator, decl: Decl) Allocator.Error!Decl.Index { + ip.lock.lock(); + defer ip.lock.unlock(); try ip.decls.append(gpa, decl); return @enumFromInt(ip.decls.count() - 1); } pub fn createStruct(ip: *InternPool, gpa: Allocator, struct_info: Struct) Allocator.Error!Struct.Index { + ip.lock.lock(); + defer ip.lock.unlock(); try ip.structs.append(gpa, struct_info); return @enumFromInt(ip.structs.count() - 1); } pub fn createEnum(ip: *InternPool, gpa: Allocator, enum_info: Enum) Allocator.Error!Enum.Index { + ip.lock.lock(); + defer ip.lock.unlock(); try ip.enums.append(gpa, enum_info); return @enumFromInt(ip.enums.count() - 1); } pub fn createUnion(ip: *InternPool, gpa: Allocator, union_info: Union) Allocator.Error!Union.Index { + ip.lock.lock(); + defer ip.lock.unlock(); try ip.unions.append(gpa, union_info); return @enumFromInt(ip.unions.count() - 1); } @@ -1494,16 +1616,19 @@ fn extraData(ip: *const InternPool, comptime T: type, index: u32) T { return result; } +/// assumes that the InternPool is already locked. const KeyAdapter = struct { ip: *const InternPool, + precomputed_hash: u32, pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool { _ = b_void; - return a.eql(ctx.ip.indexToKey(@enumFromInt(b_map_index)), ctx.ip); + return a.eqlNoLock(ctx.ip.indexToKeyNoLock(@enumFromInt(b_map_index)), ctx.ip); } pub fn hash(ctx: @This(), a: Key) u32 { - return a.hash32(ctx.ip); + _ = a; + return ctx.precomputed_hash; } }; @@ -2757,7 +2882,7 @@ fn coerceInMemoryAllowedPtrs( return .ok; } -fn optionalPtrTy(ip: *const InternPool, ty: Index) Index { +fn optionalPtrTy(ip: *InternPool, ty: Index) Index { switch (ip.indexToKey(ty)) { .optional_type => |optional_info| switch (ip.indexToKey(optional_info.payload_type)) { .pointer_type => |pointer_info| switch (pointer_info.flags.size) { @@ -2790,7 +2915,9 @@ inline fn panicOrElse(message: []const u8, value: anytype) @TypeOf(value) { // --------------------------------------------- /// TODO make the return type optional and return null on unknown type. -pub fn zigTypeTag(ip: *const InternPool, index: Index) std.builtin.TypeId { +pub fn zigTypeTag(ip: *InternPool, index: Index) std.builtin.TypeId { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return switch (ip.items.items(.tag)[@intFromEnum(index)]) { .simple_type => switch (@as(SimpleType, @enumFromInt(ip.items.items(.data)[@intFromEnum(index)]))) { .f16, @@ -2881,7 +3008,9 @@ pub fn zigTypeTag(ip: *const InternPool, index: Index) std.builtin.TypeId { }; } -pub fn typeOf(ip: *const InternPool, index: Index) Index { +pub fn typeOf(ip: *InternPool, index: Index) Index { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); const data = ip.items.items(.data)[@intFromEnum(index)]; return switch (ip.items.items(.tag)[@intFromEnum(index)]) { .simple_value => switch (@as(SimpleValue, @enumFromInt(data))) { @@ -2938,7 +3067,9 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index { }; } -pub fn isType(ip: *const InternPool, ty: Index) bool { +pub fn isType(ip: *InternPool, ty: Index) bool { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); return switch (ip.items.items(.tag)[@intFromEnum(ty)]) { .simple_type, .type_int_signed, @@ -2980,20 +3111,24 @@ pub fn isType(ip: *const InternPool, ty: Index) bool { }; } -pub fn isUnknown(ip: *const InternPool, index: Index) bool { +pub fn isUnknown(ip: *InternPool, index: Index) bool { switch (index) { .unknown_type, .unknown_unknown => return true, - else => return ip.items.items(.tag)[@intFromEnum(index)] == .unknown_value, + else => { + ip.lock.lockShared(); + defer ip.lock.unlockShared(); + return ip.items.items(.tag)[@intFromEnum(index)] == .unknown_value; + }, } } -pub fn isUnknownDeep(ip: *const InternPool, gpa: Allocator, index: Index) Allocator.Error!bool { +pub fn isUnknownDeep(ip: *InternPool, gpa: Allocator, index: Index) Allocator.Error!bool { var set = std.AutoHashMap(Index, void).init(gpa); defer set.deinit(); return try ip.isUnknownDeepInternal(index, &set); } -fn isUnknownDeepInternal(ip: *const InternPool, index: Index, set: *std.AutoHashMap(Index, void)) Allocator.Error!bool { +fn isUnknownDeepInternal(ip: *InternPool, index: Index, set: *std.AutoHashMap(Index, void)) Allocator.Error!bool { const gop = try set.getOrPut(index); if (gop.found_existing) return false; return switch (ip.indexToKey(index)) { @@ -3083,7 +3218,7 @@ fn isUnknownDeepInternal(ip: *const InternPool, index: Index, set: *std.AutoHash } /// Asserts the type is an integer, enum, error set, packed struct, or vector of one of them. -pub fn intInfo(ip: *const InternPool, ty: Index, target: std.Target) std.builtin.Type.Int { +pub fn intInfo(ip: *InternPool, ty: Index, target: std.Target) std.builtin.Type.Int { var index = ty; while (true) switch (index) { .u1_type => return .{ .signedness = .unsigned, .bits = 1 }, @@ -3140,7 +3275,7 @@ pub fn intInfo(ip: *const InternPool, ty: Index, target: std.Target) std.builtin /// Asserts the type is a fixed-size float or comptime_float. /// Returns 128 for comptime_float types. -pub fn floatBits(ip: *const InternPool, ty: Index, target: std.Target) u16 { +pub fn floatBits(ip: *InternPool, ty: Index, target: std.Target) u16 { _ = ip; return switch (ty) { .f16_type => 16, @@ -3154,7 +3289,7 @@ pub fn floatBits(ip: *const InternPool, ty: Index, target: std.Target) u16 { }; } -pub fn isFloat(ip: *const InternPool, ty: Index) bool { +pub fn isFloat(ip: *InternPool, ty: Index) bool { _ = ip; return switch (ty) { .c_longdouble_type, @@ -3169,35 +3304,35 @@ pub fn isFloat(ip: *const InternPool, ty: Index) bool { }; } -pub fn isSinglePointer(ip: *const InternPool, ty: Index) bool { +pub fn isSinglePointer(ip: *InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .pointer_type => |pointer_info| pointer_info.flags.size == .One, else => false, }; } -pub fn isManyPointer(ip: *const InternPool, ty: Index) bool { +pub fn isManyPointer(ip: *InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .pointer_type => |pointer_info| pointer_info.flags.size == .Many, else => false, }; } -pub fn isSlicePointer(ip: *const InternPool, ty: Index) bool { +pub fn isSlicePointer(ip: *InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .pointer_type => |pointer_info| pointer_info.flags.size == .Slice, else => false, }; } -pub fn isCPointer(ip: *const InternPool, ty: Index) bool { +pub fn isCPointer(ip: *InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .pointer_type => |pointer_info| pointer_info.flags.size == .C, else => false, }; } -pub fn isConstPointer(ip: *const InternPool, ty: Index) bool { +pub fn isConstPointer(ip: *InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .pointer_type => |pointer_info| pointer_info.flags.is_const, else => false, @@ -3206,14 +3341,14 @@ pub fn isConstPointer(ip: *const InternPool, ty: Index) bool { /// For pointer-like optionals, returns true, otherwise returns the allowzero property /// of pointers. -pub fn ptrAllowsZero(ip: *const InternPool, ty: Index) bool { +pub fn ptrAllowsZero(ip: *InternPool, ty: Index) bool { if (ip.indexToKey(ty).pointer_type.flags.is_allowzero) return true; return ip.isPtrLikeOptional(ty); } /// Returns true if the type is optional and would be lowered to a single pointer /// address value, using 0 for null. Note that this returns true for C pointers. -pub fn isPtrLikeOptional(ip: *const InternPool, ty: Index) bool { +pub fn isPtrLikeOptional(ip: *InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .optional_type => |optional_info| switch (ip.indexToKey(optional_info.payload_type)) { .pointer_type => |pointer_info| switch (pointer_info.flags.size) { @@ -3227,7 +3362,7 @@ pub fn isPtrLikeOptional(ip: *const InternPool, ty: Index) bool { }; } -pub fn isPtrAtRuntime(ip: *const InternPool, ty: Index) bool { +pub fn isPtrAtRuntime(ip: *InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .pointer_type => |pointer_info| pointer_info.flags.size != .Slice, .optional_type => |optional_info| switch (ip.indexToKey(optional_info.payload_type)) { @@ -3249,7 +3384,7 @@ pub fn isPtrAtRuntime(ip: *const InternPool, ty: Index) bool { /// For ?T, returns T. /// For @vector(_, T), returns T. /// For anyframe->T, returns T. -pub fn childType(ip: *const InternPool, ty: Index) Index { +pub fn childType(ip: *InternPool, ty: Index) Index { return switch (ip.indexToKey(ty)) { .pointer_type => |pointer_info| pointer_info.elem_type, .array_type => |array_info| array_info.child, @@ -3272,7 +3407,7 @@ pub fn childType(ip: *const InternPool, ty: Index) Index { /// For ?T, returns T. /// For @vector(_, T), returns T. /// For anyframe->T, returns T. -pub fn elemType(ip: *const InternPool, ty: Index) Index { +pub fn elemType(ip: *InternPool, ty: Index) Index { return switch (ip.indexToKey(ty)) { .pointer_type => |pointer_info| switch (pointer_info.flags.size) { .One => ip.childType(pointer_info.elem_type), @@ -3317,7 +3452,7 @@ pub fn errorSetMerge(ip: *InternPool, gpa: Allocator, a_ty: Index, b_ty: Index) } /// Asserts the type is an array, pointer or vector. -pub fn sentinel(ip: *const InternPool, ty: Index) Index { +pub fn sentinel(ip: *InternPool, ty: Index) Index { return switch (ip.indexToKey(ty)) { .pointer_type => |pointer_info| pointer_info.sentinel, .array_type => |array_info| array_info.sentinel, @@ -3326,7 +3461,7 @@ pub fn sentinel(ip: *const InternPool, ty: Index) Index { }; } -pub fn getNamespace(ip: *const InternPool, ty: Index) NamespaceIndex { +pub fn getNamespace(ip: *InternPool, ty: Index) NamespaceIndex { return switch (ip.indexToKey(ty)) { .struct_type => |struct_index| ip.getStruct(struct_index).namespace, .enum_type => |enum_index| ip.getEnum(enum_index).namespace, @@ -3335,7 +3470,7 @@ pub fn getNamespace(ip: *const InternPool, ty: Index) NamespaceIndex { }; } -pub fn onePossibleValue(ip: *const InternPool, ty: Index) Index { +pub fn onePossibleValue(ip: *InternPool, ty: Index) Index { return switch (ip.indexToKey(ty)) { .simple_type => |simple| switch (simple) { .f16, @@ -3465,7 +3600,7 @@ pub fn onePossibleValue(ip: *const InternPool, ty: Index) Index { }; } -pub fn canHaveFields(ip: *const InternPool, ty: Index) bool { +pub fn canHaveFields(ip: *InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .simple_type => |simple| switch (simple) { .type => true, // TODO @@ -3514,7 +3649,7 @@ pub fn canHaveFields(ip: *const InternPool, ty: Index) bool { } /// see `std.meta.trait.isIndexable` -pub fn isIndexable(ip: *const InternPool, ty: Index) bool { +pub fn isIndexable(ip: *InternPool, ty: Index) bool { return switch (ip.indexToKey(ty)) { .array_type, .vector_type => true, .pointer_type => |pointer_info| switch (pointer_info.flags.size) { @@ -3526,7 +3661,7 @@ pub fn isIndexable(ip: *const InternPool, ty: Index) bool { }; } -pub fn isNull(ip: *const InternPool, val: Index) bool { +pub fn isNull(ip: *InternPool, val: Index) bool { return switch (ip.indexToKey(val)) { .simple_value => |simple| switch (simple) { .null_value => true, @@ -3538,7 +3673,7 @@ pub fn isNull(ip: *const InternPool, val: Index) bool { }; } -pub fn isZero(ip: *const InternPool, val: Index) bool { +pub fn isZero(ip: *InternPool, val: Index) bool { return switch (ip.indexToKey(val)) { .simple_value => |simple| switch (simple) { .null_value => true, @@ -3559,7 +3694,7 @@ pub fn isZero(ip: *const InternPool, val: Index) bool { } /// If the value fits in the given integer, return it, otherwise null. -pub fn toInt(ip: *const InternPool, val: Index, comptime T: type) !?T { +pub fn toInt(ip: *InternPool, val: Index, comptime T: type) !?T { comptime assert(std.meta.trait.isIntegral(T)); return switch (ip.indexToKey(val)) { .simple_value => |simple| switch (simple) { @@ -4528,6 +4663,70 @@ test StringSlice { try std.testing.expectEqualSlices(String, &.{}, empty_string); } +test "test thread safety of InternPool" { + if (builtin.single_threaded) return error.SkipZigTest; + + const gpa = std.testing.allocator; + var pool: std.Thread.Pool = undefined; + try std.Thread.Pool.init(&pool, .{ .allocator = gpa }); + defer pool.deinit(); + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const index_start = ip.map.count(); + + const size: usize = 100; + + const funcs = struct { + fn do( + intern_pool: *InternPool, + wait_group: *std.Thread.WaitGroup, + allocator: std.mem.Allocator, + count: usize, + ) void { + defer wait_group.finish(); + // insert float_32_value from 0 to count + random work + for (0..count) |i| { + _ = intern_pool.get(allocator, .{ .float_32_value = @floatFromInt(i) }) catch @panic("OOM"); + _ = intern_pool.indexToKey(@enumFromInt(i)); + } + for (0..count) |i| { + _ = intern_pool.indexToKey(@enumFromInt(i)); + } + } + }; + + var wait_group = std.Thread.WaitGroup{}; + for (0..pool.threads.len) |_| { + wait_group.start(); + try pool.spawn(funcs.do, .{ &ip, &wait_group, gpa, size }); + } + pool.waitAndWork(&wait_group); + + try std.testing.expectEqual(index_start + size, ip.map.count()); + + var found = try std.DynamicBitSetUnmanaged.initEmpty(gpa, size); + defer found.deinit(gpa); + + // test that every value is in the InternPool + for (0..size) |i| { + try std.testing.expect(ip.contains(.{ .float_32_value = @floatFromInt(i) }) != null); + } + + // test that every Index stores a unique float_32_value + for (0..size) |i| { + const index: Index = @enumFromInt(index_start + i); + const key = ip.indexToKey(index); + const value: usize = @intFromFloat(key.float_32_value); + try std.testing.expect(value < size); + try std.testing.expect(!found.isSet(value)); + found.set(value); + } + + try std.testing.expectEqual(found.capacity(), found.count()); +} + test "coerceInMemoryAllowed integers and floats" { const gpa = std.testing.allocator; From 140db6360319473fce14adcc27527493bc69873e Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Wed, 20 Dec 2023 23:11:21 +0100 Subject: [PATCH 28/30] add tests for intern pool completions + fixes covered: - primitives - optionals - pointers - arrays --- src/analyser/completions.zig | 209 +++++++++++++++++++++++++++++------ 1 file changed, 178 insertions(+), 31 deletions(-) diff --git a/src/analyser/completions.zig b/src/analyser/completions.zig index 13171cfbb..5477f1942 100644 --- a/src/analyser/completions.zig +++ b/src/analyser/completions.zig @@ -37,7 +37,6 @@ pub fn dotCompletions( switch (ip.indexToKey(val)) { .error_set_type => |error_set_info| { const names = try error_set_info.names.dupe(arena, ip); - try completions.ensureUnusedCapacity(arena, names.len); for (names) |name| { completions.appendAssumeCapacity(.{ @@ -50,8 +49,9 @@ pub fn dotCompletions( .union_type => {}, // TODO .enum_type => |enum_index| { const enum_info = ip.getEnum(enum_index); + try completions.ensureUnusedCapacity(arena, enum_info.fields.count()); for (enum_info.fields.keys()) |name| { - try completions.append(arena, .{ + completions.appendAssumeCapacity(.{ .label = try std.fmt.allocPrint(arena, "{}", .{name.fmt(&ip.string_pool)}), .kind = .EnumMember, // include field.val? @@ -64,40 +64,29 @@ pub fn dotCompletions( else => {}, }, .pointer_type => |pointer_info| { - switch (pointer_info.flags.size) { - .Many, .C => {}, - .One => { - switch (ip.indexToKey(pointer_info.elem_type)) { - .array_type => |array_info| { - try completions.append(arena, .{ - .label = "len", - .kind = .Field, - .detail = try std.fmt.allocPrint(arena, "const len: usize ({d})", .{array_info.len}), // TODO how should this be displayed - }); - }, - else => {}, - } + if (pointer_info.flags.size != .Slice) return; + + const formatted = try std.fmt.allocPrint(arena, "{}", .{inner_ty.fmt(ip)}); + std.debug.assert(std.mem.startsWith(u8, formatted, "[]")); + + try completions.appendSlice(arena, &.{ + .{ + .label = "ptr", + .kind = .Field, + .detail = try std.fmt.allocPrint(arena, "ptr: [*]{s}", .{formatted["[]".len..]}), }, - .Slice => { - try completions.append(arena, .{ - .label = "ptr", - .kind = .Field, - // TODO this discards pointer attributes - .detail = try std.fmt.allocPrint(arena, "ptr: [*]{}", .{pointer_info.elem_type.fmt(ip)}), - }); - try completions.append(arena, .{ - .label = "len", - .kind = .Field, - .detail = "len: usize", - }); + .{ + .label = "len", + .kind = .Field, + .detail = "len: usize", }, - } + }); }, .array_type => |array_info| { try completions.append(arena, .{ .label = "len", .kind = .Field, - .detail = try std.fmt.allocPrint(arena, "const len: usize ({d})", .{array_info.len}), // TODO how should this be displayed + .detail = try std.fmt.allocPrint(arena, "const len: usize = {d}", .{array_info.len}), }); }, .struct_type => |struct_index| { @@ -123,8 +112,9 @@ pub fn dotCompletions( }, .enum_type => |enum_index| { const enum_info = ip.getEnum(enum_index); + try completions.ensureUnusedCapacity(arena, enum_info.fields.count()); for (enum_info.fields.keys(), enum_info.values.keys()) |name, field_value| { - try completions.append(arena, .{ + completions.appendAssumeCapacity(.{ .label = try std.fmt.allocPrint(arena, "{}", .{ip.fmtId(name)}), .kind = .Field, .detail = try std.fmt.allocPrint(arena, "{}", .{field_value.fmt(ip)}), @@ -133,8 +123,9 @@ pub fn dotCompletions( }, .union_type => |union_index| { const union_info = ip.getUnion(union_index); + try completions.ensureUnusedCapacity(arena, union_info.fields.count()); for (union_info.fields.keys(), union_info.fields.values()) |name, field| { - try completions.append(arena, .{ + completions.appendAssumeCapacity(.{ .label = try std.fmt.allocPrint(arena, "{}", .{ip.fmtId(name)}), .kind = .Field, .detail = if (field.alignment != 0) @@ -221,3 +212,159 @@ fn formatFieldDetail( pub fn fmtFieldDetail(ip: *InternPool, field: InternPool.Struct.Field) std.fmt.Formatter(formatFieldDetail) { return .{ .data = .{ .ip = ip, .item = field } }; } + +test "dotCompletions - primitives" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + try testCompletion(&ip, .bool_type, &.{}); + try testCompletion(&ip, .bool_true, &.{}); + try testCompletion(&ip, .zero_comptime_int, &.{}); + try testCompletion(&ip, .unknown_type, &.{}); + try testCompletion(&ip, .unknown_unknown, &.{}); +} + +test "dotCompletions - optional types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"?u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .u32_type } }); + try testCompletion(&ip, try ip.getUnknown(gpa, @"?u32"), &.{ + .{ + .label = "?", + .kind = .Operator, + .detail = "u32", + }, + }); +} + +test "dotCompletions - array types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"[3]u32" = try ip.get(gpa, .{ .array_type = .{ .child = .u32_type, .len = 3 } }); + const @"[1]u8" = try ip.get(gpa, .{ .array_type = .{ .child = .u8_type, .len = 1 } }); + + try testCompletion(&ip, try ip.getUnknown(gpa, @"[3]u32"), &.{ + .{ + .label = "len", + .kind = .Field, + .detail = "const len: usize = 3", + }, + }); + try testCompletion(&ip, try ip.getUnknown(gpa, @"[1]u8"), &.{ + .{ + .label = "len", + .kind = .Field, + .detail = "const len: usize = 1", + }, + }); +} + +test "dotCompletions - pointer types" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .flags = .{ + .size = .One, + }, + } }); + const @"[]u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .flags = .{ + .size = .Slice, + }, + } }); + const @"[]const u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .flags = .{ + .size = .Slice, + .is_const = true, + }, + } }); + + try testCompletion(&ip, try ip.getUnknown(gpa, @"*u32"), &.{}); + try testCompletion(&ip, try ip.getUnknown(gpa, @"[]u32"), &.{ + .{ + .label = "ptr", + .kind = .Field, + .detail = "ptr: [*]u32", + }, + .{ + .label = "len", + .kind = .Field, + .detail = "len: usize", + }, + }); + try testCompletion(&ip, try ip.getUnknown(gpa, @"[]const u32"), &.{ + .{ + .label = "ptr", + .kind = .Field, + .detail = "ptr: [*]const u32", + }, + .{ + .label = "len", + .kind = .Field, + .detail = "len: usize", + }, + }); +} + +test "dotCompletions - single pointer indirection" { + const gpa = std.testing.allocator; + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"[1]u32" = try ip.get(gpa, .{ .array_type = .{ .child = .u32_type, .len = 1 } }); + const @"*[1]u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = @"[1]u32", + .flags = .{ + .size = .One, + }, + } }); + const @"**[1]u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = @"*[1]u32", + .flags = .{ + .size = .One, + }, + } }); + const @"[*][1]u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = @"[1]u32", + .flags = .{ + .size = .Many, + }, + } }); + + try testCompletion(&ip, try ip.getUnknown(gpa, @"*[1]u32"), &.{ + .{ + .label = "len", + .kind = .Field, + .detail = "const len: usize = 1", + }, + }); + try testCompletion(&ip, try ip.getUnknown(gpa, @"**[1]u32"), &.{}); + try testCompletion(&ip, try ip.getUnknown(gpa, @"[*][1]u32"), &.{}); +} + +fn testCompletion( + ip: *InternPool, + index: InternPool.Index, + expected: []const types.CompletionItem, +) !void { + const gpa = std.testing.allocator; + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + + const arena = arena_allocator.allocator(); + var completions = std.ArrayListUnmanaged(types.CompletionItem){}; + + try dotCompletions(arena, &completions, ip, index, null); + + try std.testing.expectEqualDeep(expected, completions.items); +} From 320cc300d71eef18634c32df6cc97f794a023daa Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 21 Dec 2023 06:00:37 +0100 Subject: [PATCH 29/30] fix compile errors on unused InternPool functions --- src/analyser/InternPool.zig | 16 ++++++++++++---- src/analyser/analyser.zig | 2 +- src/analyser/string_pool.zig | 29 +++++++++++++++++------------ 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 694a9f7c5..9e2c9b692 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -3170,7 +3170,8 @@ fn isUnknownDeepInternal(ip: *InternPool, index: Index, set: *std.AutoHashMap(In return false; }, .function_type => |function_info| { - for (function_info.args) |arg_ty| { + for (0..function_info.args.len) |i| { + const arg_ty = function_info.args.at(@intCast(i), ip); if (try ip.isUnknownDeepInternal(arg_ty, set)) return true; } if (try ip.isUnknownDeepInternal(function_info.return_type, set)) return true; @@ -3185,7 +3186,11 @@ fn isUnknownDeepInternal(ip: *InternPool, index: Index, set: *std.AutoHashMap(In return false; }, .tuple_type => |tuple_info| { - for (tuple_info.types, tuple_info.values) |ty, val| { + assert(tuple_info.types.len == tuple_info.values.len); + + for (0..tuple_info.types.len) |i| { + const ty = tuple_info.types.at(@intCast(i), ip); + const val = tuple_info.values.at(@intCast(i), ip); if (try ip.isUnknownDeepInternal(ty, set)) return true; if (try ip.isUnknownDeepInternal(val, set)) return true; } @@ -3447,7 +3452,10 @@ pub fn errorSetMerge(ip: *InternPool, gpa: Allocator, a_ty: Index, b_ty: Index) for (b_names) |name| set.putAssumeCapacity(name, {}); return try ip.get(gpa, .{ - .error_set_type = .{ .owner_decl = .none, .names = set.keys() }, + .error_set_type = .{ + .owner_decl = .none, + .names = try ip.getStringSlice(gpa, set.keys()), + }, }); } @@ -3684,7 +3692,7 @@ pub fn isZero(ip: *InternPool, val: Index) bool { }, .int_u64_value => |int_value| int_value.int == 0, .int_i64_value => |int_value| int_value.int == 0, - .int_big_value => |int_value| int_value.int.eqlZero(), + .int_big_value => |int_value| int_value.getConst(ip).eqlZero(), .null_value => true, .optional_value => false, diff --git a/src/analyser/analyser.zig b/src/analyser/analyser.zig index 726005b67..67b1228a1 100644 --- a/src/analyser/analyser.zig +++ b/src/analyser/analyser.zig @@ -5,5 +5,5 @@ pub const degibberish = @import("degibberish.zig"); comptime { const std = @import("std"); - std.testing.refAllDecls(@This()); + std.testing.refAllDeclsRecursive(@This()); } diff --git a/src/analyser/string_pool.zig b/src/analyser/string_pool.zig index 29aef5767..9d7814394 100644 --- a/src/analyser/string_pool.zig +++ b/src/analyser/string_pool.zig @@ -152,14 +152,17 @@ pub fn StringPool(comptime config: Config) type { return .{ .slice = pool.stringToSliceUnsafe(index) }; } - /// returns the underlying slice from an interned string - /// equal strings are guaranteed to share the same storage - /// - /// only callable when thread safety is disabled. - pub fn stringToSlice(pool: *Pool, index: String) [:0]const u8 { - if (config.thread_safe) @compileError("use stringToSliceLock instead"); - return pool.stringToSliceUnsafe(index); - } + // usingnamespace is used here instead of doing a @compileError if + // `config.thread_safe` so that `std.testing.refAllDeclsRecursive` works. + usingnamespace if (config.thread_safe) struct {} else struct { + /// returns the underlying slice from an interned string + /// equal strings are guaranteed to share the same storage + /// + /// only callable when thread safety is disabled. + pub fn stringToSlice(pool: *Pool, index: String) [:0]const u8 { + return pool.stringToSliceUnsafe(index); + } + }; /// returns the underlying slice from an interned string /// equal strings are guaranteed to share the same storage @@ -230,16 +233,18 @@ const PrecomputedStringIndexAdapter = struct { test StringPool { const gpa = std.testing.allocator; - var pool = StringPool(.{ .thread_safe = false }){}; + var pool = StringPool(.{}){}; defer pool.deinit(gpa); const str = "All Your Codebase Are Belong To Us"; const index = try pool.getOrPutString(gpa, str); - const locked_string = pool.stringToSliceLock(index); - defer locked_string.release(&pool); + { + const locked_string = pool.stringToSliceLock(index); + defer locked_string.release(&pool); - try std.testing.expectEqualStrings(str, locked_string.slice); + try std.testing.expectEqualStrings(str, locked_string.slice); + } try std.testing.expectFmt(str, "{}", .{index.fmt(&pool)}); } From 7162c330ee850fd9d789c65f24d48b8c5bdeec8f Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sat, 23 Dec 2023 04:43:06 +0100 Subject: [PATCH 30/30] don't use `std.Thread.RwLock.PthreadRwLock` --- src/analyser/InternPool.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 9e2c9b692..cda220dd8 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -4,7 +4,7 @@ map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, items: std.MultiArrayList(Item) = .{}, extra: std.ArrayListUnmanaged(u32) = .{}, string_pool: StringPool = .{}, -lock: std.Thread.RwLock = .{}, +lock: RwLock = .{}, limbs: std.ArrayListUnmanaged(usize) = .{}, @@ -25,6 +25,11 @@ pub const StringPool = @import("string_pool.zig").StringPool(.{}); pub const String = StringPool.String; const ErrorMsg = @import("error_msg.zig").ErrorMsg; +pub const RwLock = if (builtin.single_threaded) + std.Thread.RwLock.SingleThreadedRwLock +else + std.Thread.RwLock.DefaultRwLock; + pub const Key = union(enum) { simple_type: SimpleType, simple_value: SimpleValue,