commit c6e0df6213af1ecf5734bc6bcb4d7c29a4b41351 (tree)
parent 41a8b6f57b3bd50b2ed6fdced74fba9130eac3d3
Author: Andrew Kelley <andrew@ziglang.org>
Date: Tue, 4 Aug 2020 17:09:40 -0700
Merge remote-tracking branch 'origin/master' into llvm11
Diffstat:
69 files changed, 6722 insertions(+), 3221 deletions(-)
diff --git a/build.zig b/build.zig
@@ -77,6 +77,9 @@ pub fn build(b: *Builder) !void {
const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false;
if (link_libc) exe.linkLibC();
+ const log_scopes = b.option([]const []const u8, "log", "Which log scopes to enable") orelse &[0][]const u8{};
+
+ exe.addBuildOption([]const []const u8, "log_scopes", log_scopes);
exe.addBuildOption(bool, "enable_tracy", tracy != null);
if (tracy) |tracy_path| {
const client_cpp = fs.path.join(
@@ -104,6 +107,12 @@ pub fn build(b: *Builder) !void {
const is_wasmtime_enabled = b.option(bool, "enable-wasmtime", "Use Wasmtime to enable and run WASI libstd tests") orelse false;
const glibc_multi_dir = b.option([]const u8, "enable-foreign-glibc", "Provide directory with glibc installations to run cross compiled tests that link glibc");
+
+ test_stage2.addBuildOption(bool, "enable_qemu", is_qemu_enabled);
+ test_stage2.addBuildOption(bool, "enable_wine", is_wine_enabled);
+ test_stage2.addBuildOption(bool, "enable_wasmtime", is_wasmtime_enabled);
+ test_stage2.addBuildOption(?[]const u8, "glibc_multi_install_dir", glibc_multi_dir);
+
const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests");
test_stage2_step.dependOn(&test_stage2.step);
test_step.dependOn(test_stage2_step);
diff --git a/ci/azure/pipelines.yml b/ci/azure/pipelines.yml
@@ -41,7 +41,7 @@ jobs:
steps:
- powershell: |
- (New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2020-06-02/msys2-base-x86_64-20200602.sfx.exe", "sfx.exe")
+ (New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2020-07-20/msys2-base-x86_64-20200720.sfx.exe", "sfx.exe")
.\sfx.exe -y -o\
del sfx.exe
displayName: Download/Extract/Install MSYS2
diff --git a/doc/langref.html.in b/doc/langref.html.in
@@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documentation - The Zig Programming Language</title>
<link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgklEQVR4AWMYWuD7EllJIM4G4g4g5oIJ/odhOJ8wToOxSTXgNxDHoeiBMfA4+wGShjyYOCkG/IGqWQziEzYAoUAeiF9D5U+DxEg14DRU7jWIT5IBIOdCxf+A+CQZAAoopEB7QJwBCBwHiip8UYmRdrAlDpIMgApwQZNnNii5Dq0MBgCxxycBnwEd+wAAAABJRU5ErkJggg=="/>
<style>
@@ -7473,7 +7474,7 @@ export fn @"A function name that is a complete sentence."() void {}
<p>
When looking at the resulting object, you can see the symbol is used verbatim:
</p>
- <pre>00000000000001f0 T A function name that is a complete sentence.</pre>
+ <pre><code>00000000000001f0 T A function name that is a complete sentence.</code></pre>
{#see_also|Exporting a C Library#}
{#header_close#}
diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig
@@ -108,6 +108,33 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
mem.copy(T, self.items[i .. i + items.len], items);
}
+ /// Replace range of elements `list[start..start+len]` with `new_items`
+ /// grows list if `len < new_items.len`. may allocate
+ /// shrinks list if `len > new_items.len`
+ pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: SliceConst) !void {
+ const after_range = start + len;
+ const range = self.items[start..after_range];
+
+ if (range.len == new_items.len)
+ mem.copy(T, range, new_items)
+ else if (range.len < new_items.len) {
+ const first = new_items[0..range.len];
+ const rest = new_items[range.len..];
+
+ mem.copy(T, range, first);
+ try self.insertSlice(after_range, rest);
+ } else {
+ mem.copy(T, range, new_items);
+ const after_subrange = start + new_items.len;
+
+ for (self.items[after_range..]) |item, i| {
+ self.items[after_subrange..][i] = item;
+ }
+
+ self.items.len -= len - new_items.len;
+ }
+ }
+
/// Extend the list by 1 element. Allocates more memory as necessary.
pub fn append(self: *Self, item: T) !void {
const new_item_ptr = try self.addOne();
@@ -189,6 +216,15 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
mem.set(T, self.items[old_len..self.items.len], value);
}
+ /// Append a value to the list `n` times.
+ /// Asserts the capacity is enough.
+ pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {
+ const new_len = self.items.len + n;
+ assert(new_len <= self.capacity);
+ mem.set(T, self.items.ptr[self.items.len..new_len], value);
+ self.items.len = new_len;
+ }
+
/// Adjust the list's length to `new_len`.
/// Does not initialize added items if any.
pub fn resize(self: *Self, new_len: usize) !void {
@@ -366,6 +402,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
mem.copy(T, self.items[i .. i + items.len], items);
}
+ /// Replace range of elements `list[start..start+len]` with `new_items`
+ /// grows list if `len < new_items.len`. may allocate
+ /// shrinks list if `len > new_items.len`
+ pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: SliceConst) !void {
+ var managed = self.toManaged(allocator);
+ try managed.replaceRange(start, len, new_items);
+ self.* = managed.toUnmanaged();
+ }
+
/// Extend the list by 1 element. Allocates more memory as necessary.
pub fn append(self: *Self, allocator: *Allocator, item: T) !void {
const new_item_ptr = try self.addOne(allocator);
@@ -437,6 +482,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
mem.set(T, self.items[old_len..self.items.len], value);
}
+ /// Append a value to the list `n` times.
+ /// Asserts the capacity is enough.
+ pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {
+ const new_len = self.items.len + n;
+ assert(new_len <= self.capacity);
+ mem.set(T, self.items.ptr[self.items.len..new_len], value);
+ self.items.len = new_len;
+ }
+
/// Adjust the list's length to `new_len`.
/// Does not initialize added items if any.
pub fn resize(self: *Self, allocator: *Allocator, new_len: usize) !void {
@@ -714,6 +768,38 @@ test "std.ArrayList.insertSlice" {
testing.expect(list.items[0] == 1);
}
+test "std.ArrayList.replaceRange" {
+ var arena = std.heap.ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+
+ const alloc = &arena.allocator;
+ const init = [_]i32{ 1, 2, 3, 4, 5 };
+ const new = [_]i32{ 0, 0, 0 };
+
+ var list_zero = ArrayList(i32).init(alloc);
+ var list_eq = ArrayList(i32).init(alloc);
+ var list_lt = ArrayList(i32).init(alloc);
+ var list_gt = ArrayList(i32).init(alloc);
+
+ try list_zero.appendSlice(&init);
+ try list_eq.appendSlice(&init);
+ try list_lt.appendSlice(&init);
+ try list_gt.appendSlice(&init);
+
+ try list_zero.replaceRange(1, 0, &new);
+ try list_eq.replaceRange(1, 3, &new);
+ try list_lt.replaceRange(1, 2, &new);
+
+ // after_range > new_items.len in function body
+ testing.expect(1 + 4 > new.len);
+ try list_gt.replaceRange(1, 4, &new);
+
+ testing.expectEqualSlices(i32, list_zero.items, &[_]i32{ 1, 0, 0, 0, 2, 3, 4, 5 });
+ testing.expectEqualSlices(i32, list_eq.items, &[_]i32{ 1, 0, 0, 0, 5 });
+ testing.expectEqualSlices(i32, list_lt.items, &[_]i32{ 1, 0, 0, 0, 4, 5 });
+ testing.expectEqualSlices(i32, list_gt.items, &[_]i32{ 1, 0, 0, 0 });
+}
+
const Item = struct {
integer: i32,
sub_items: ArrayList(Item),
diff --git a/lib/std/build.zig b/lib/std/build.zig
@@ -430,9 +430,9 @@ pub const Builder = struct {
const entry = self.user_input_options.getEntry(name) orelse return null;
entry.value.used = true;
switch (type_id) {
- TypeId.Bool => switch (entry.value.value) {
- UserValue.Flag => return true,
- UserValue.Scalar => |s| {
+ .Bool => switch (entry.value.value) {
+ .Flag => return true,
+ .Scalar => |s| {
if (mem.eql(u8, s, "true")) {
return true;
} else if (mem.eql(u8, s, "false")) {
@@ -443,21 +443,21 @@ pub const Builder = struct {
return null;
}
},
- UserValue.List => {
+ .List => {
warn("Expected -D{} to be a boolean, but received a list.\n", .{name});
self.markInvalidUserInput();
return null;
},
},
- TypeId.Int => panic("TODO integer options to build script", .{}),
- TypeId.Float => panic("TODO float options to build script", .{}),
- TypeId.Enum => switch (entry.value.value) {
- UserValue.Flag => {
+ .Int => panic("TODO integer options to build script", .{}),
+ .Float => panic("TODO float options to build script", .{}),
+ .Enum => switch (entry.value.value) {
+ .Flag => {
warn("Expected -D{} to be a string, but received a boolean.\n", .{name});
self.markInvalidUserInput();
return null;
},
- UserValue.Scalar => |s| {
+ .Scalar => |s| {
if (std.meta.stringToEnum(T, s)) |enum_lit| {
return enum_lit;
} else {
@@ -466,33 +466,35 @@ pub const Builder = struct {
return null;
}
},
- UserValue.List => {
+ .List => {
warn("Expected -D{} to be a string, but received a list.\n", .{name});
self.markInvalidUserInput();
return null;
},
},
- TypeId.String => switch (entry.value.value) {
- UserValue.Flag => {
+ .String => switch (entry.value.value) {
+ .Flag => {
warn("Expected -D{} to be a string, but received a boolean.\n", .{name});
self.markInvalidUserInput();
return null;
},
- UserValue.List => {
+ .List => {
warn("Expected -D{} to be a string, but received a list.\n", .{name});
self.markInvalidUserInput();
return null;
},
- UserValue.Scalar => |s| return s,
+ .Scalar => |s| return s,
},
- TypeId.List => switch (entry.value.value) {
- UserValue.Flag => {
+ .List => switch (entry.value.value) {
+ .Flag => {
warn("Expected -D{} to be a list, but received a boolean.\n", .{name});
self.markInvalidUserInput();
return null;
},
- UserValue.Scalar => |s| return &[_][]const u8{s},
- UserValue.List => |lst| return lst.span(),
+ .Scalar => |s| {
+ return self.allocator.dupe([]const u8, &[_][]const u8{s}) catch unreachable;
+ },
+ .List => |lst| return lst.span(),
},
}
}
@@ -1151,6 +1153,7 @@ pub const LibExeObjStep = struct {
bundle_compiler_rt: bool,
disable_stack_probing: bool,
disable_sanitize_c: bool,
+ rdynamic: bool,
c_std: Builder.CStd,
override_lib_dir: ?[]const u8,
main_pkg_path: ?[]const u8,
@@ -1311,6 +1314,7 @@ pub const LibExeObjStep = struct {
.bundle_compiler_rt = false,
.disable_stack_probing = false,
.disable_sanitize_c = false,
+ .rdynamic = false,
.output_dir = null,
.single_threaded = false,
.installed_path = null,
@@ -1704,13 +1708,23 @@ pub const LibExeObjStep = struct {
pub fn addBuildOption(self: *LibExeObjStep, comptime T: type, name: []const u8, value: T) void {
const out = self.build_options_contents.outStream();
+ if (T == []const []const u8) {
+ out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{name}) catch unreachable;
+ for (value) |slice| {
+ out.writeAll(" ") catch unreachable;
+ std.zig.renderStringLiteral(slice, out) catch unreachable;
+ out.writeAll(",\n") catch unreachable;
+ }
+ out.writeAll("};\n") catch unreachable;
+ return;
+ }
switch (@typeInfo(T)) {
.Enum => |enum_info| {
- out.print("const {} = enum {{\n", .{@typeName(T)}) catch unreachable;
+ out.print("pub const {} = enum {{\n", .{@typeName(T)}) catch unreachable;
inline for (enum_info.fields) |field| {
out.print(" {},\n", .{field.name}) catch unreachable;
}
- out.print("}};\n", .{}) catch unreachable;
+ out.writeAll("};\n") catch unreachable;
},
else => {},
}
@@ -1843,10 +1857,10 @@ pub const LibExeObjStep = struct {
zig_args.append(builder.zig_exe) catch unreachable;
const cmd = switch (self.kind) {
- Kind.Lib => "build-lib",
- Kind.Exe => "build-exe",
- Kind.Obj => "build-obj",
- Kind.Test => "test",
+ .Lib => "build-lib",
+ .Exe => "build-exe",
+ .Obj => "build-obj",
+ .Test => "test",
};
zig_args.append(cmd) catch unreachable;
@@ -1994,6 +2008,9 @@ pub const LibExeObjStep = struct {
if (self.disable_sanitize_c) {
try zig_args.append("-fno-sanitize-c");
}
+ if (self.rdynamic) {
+ try zig_args.append("-rdynamic");
+ }
if (self.code_model != .default) {
try zig_args.append("-code-model");
diff --git a/lib/std/build/emit_raw.zig b/lib/std/build/emit_raw.zig
@@ -46,9 +46,10 @@ const BinaryElfOutput = struct {
.segments = ArrayList(*BinaryElfSegment).init(allocator),
.sections = ArrayList(*BinaryElfSection).init(allocator),
};
- const elf_hdrs = try std.elf.readAllHeaders(allocator, elf_file);
+ const elf_hdr = try std.elf.readHeader(elf_file);
- for (elf_hdrs.section_headers) |section, i| {
+ var section_headers = elf_hdr.section_header_iterator(elf_file);
+ while (try section_headers.next()) |section| {
if (sectionValidForOutput(section)) {
const newSection = try allocator.create(BinaryElfSection);
@@ -61,7 +62,8 @@ const BinaryElfOutput = struct {
}
}
- for (elf_hdrs.program_headers) |phdr, i| {
+ var program_headers = elf_hdr.program_header_iterator(elf_file);
+ while (try program_headers.next()) |phdr| {
if (phdr.p_type == elf.PT_LOAD) {
const newSegment = try allocator.create(BinaryElfSegment);
diff --git a/lib/std/build/run.zig b/lib/std/build/run.zig
@@ -4,6 +4,7 @@ const build = std.build;
const Step = build.Step;
const Builder = build.Builder;
const LibExeObjStep = build.LibExeObjStep;
+const WriteFileStep = build.WriteFileStep;
const fs = std.fs;
const mem = std.mem;
const process = std.process;
@@ -42,6 +43,10 @@ pub const RunStep = struct {
pub const Arg = union(enum) {
Artifact: *LibExeObjStep,
+ WriteFile: struct {
+ step: *WriteFileStep,
+ file_name: []const u8,
+ },
Bytes: []u8,
};
@@ -62,6 +67,16 @@ pub const RunStep = struct {
self.step.dependOn(&artifact.step);
}
+ pub fn addWriteFileArg(self: *RunStep, write_file: *WriteFileStep, file_name: []const u8) void {
+ self.argv.append(Arg{
+ .WriteFile = .{
+ .step = write_file,
+ .file_name = file_name,
+ },
+ }) catch unreachable;
+ self.step.dependOn(&write_file.step);
+ }
+
pub fn addArg(self: *RunStep, arg: []const u8) void {
self.argv.append(Arg{ .Bytes = self.builder.dupe(arg) }) catch unreachable;
}
@@ -142,6 +157,9 @@ pub const RunStep = struct {
for (self.argv.span()) |arg| {
switch (arg) {
Arg.Bytes => |bytes| try argv_list.append(bytes),
+ Arg.WriteFile => |file| {
+ try argv_list.append(file.step.getOutputPath(file.file_name));
+ },
Arg.Artifact => |artifact| {
if (artifact.target.isWindows()) {
// On Windows we don't have rpaths so we have to add .dll search paths to PATH
diff --git a/lib/std/c.zig b/lib/std/c.zig
@@ -8,6 +8,10 @@ pub const Tokenizer = tokenizer.Tokenizer;
pub const parse = @import("c/parse.zig").parse;
pub const ast = @import("c/ast.zig");
+test "" {
+ _ = tokenizer;
+}
+
pub usingnamespace @import("os/bits.zig");
pub usingnamespace switch (std.Target.current.os.tag) {
diff --git a/lib/std/c/tokenizer.zig b/lib/std/c/tokenizer.zig
@@ -1,19 +1,10 @@
const std = @import("std");
const mem = std.mem;
-pub const Source = struct {
- buffer: []const u8,
- file_name: []const u8,
- tokens: TokenList,
-
- pub const TokenList = std.SegmentedList(Token, 64);
-};
-
pub const Token = struct {
id: Id,
start: usize,
end: usize,
- source: *Source,
pub const Id = union(enum) {
Invalid,
@@ -251,31 +242,6 @@ pub const Token = struct {
}
};
- pub fn eql(a: Token, b: Token) bool {
- // do we really need this cast here
- if (@as(@TagType(Id), a.id) != b.id) return false;
- return mem.eql(u8, a.slice(), b.slice());
- }
-
- pub fn slice(tok: Token) []const u8 {
- return tok.source.buffer[tok.start..tok.end];
- }
-
- pub const Keyword = struct {
- bytes: []const u8,
- id: Id,
- hash: u32,
-
- fn init(bytes: []const u8, id: Id) Keyword {
- @setEvalBranchQuota(2000);
- return .{
- .bytes = bytes,
- .id = id,
- .hash = std.hash_map.hashString(bytes),
- };
- }
- };
-
// TODO extensions
pub const keywords = std.ComptimeStringMap(Id, .{
.{ "auto", .Keyword_auto },
@@ -355,26 +321,26 @@ pub const Token = struct {
}
pub const NumSuffix = enum {
- None,
- F,
- L,
- U,
- LU,
- LL,
- LLU,
+ none,
+ f,
+ l,
+ u,
+ lu,
+ ll,
+ llu,
};
pub const StrKind = enum {
- None,
- Wide,
- Utf8,
- Utf16,
- Utf32,
+ none,
+ wide,
+ utf_8,
+ utf_16,
+ utf_32,
};
};
pub const Tokenizer = struct {
- source: *Source,
+ buffer: []const u8,
index: usize = 0,
prev_tok_id: @TagType(Token.Id) = .Invalid,
pp_directive: bool = false,
@@ -385,7 +351,6 @@ pub const Tokenizer = struct {
.id = .Eof,
.start = self.index,
.end = undefined,
- .source = self.source,
};
var state: enum {
Start,
@@ -446,8 +411,8 @@ pub const Tokenizer = struct {
} = .Start;
var string = false;
var counter: u32 = 0;
- while (self.index < self.source.buffer.len) : (self.index += 1) {
- const c = self.source.buffer[self.index];
+ while (self.index < self.buffer.len) : (self.index += 1) {
+ const c = self.buffer[self.index];
switch (state) {
.Start => switch (c) {
'\n' => {
@@ -460,11 +425,11 @@ pub const Tokenizer = struct {
state = .Cr;
},
'"' => {
- result.id = .{ .StringLiteral = .None };
+ result.id = .{ .StringLiteral = .none };
state = .StringLiteral;
},
'\'' => {
- result.id = .{ .CharLiteral = .None };
+ result.id = .{ .CharLiteral = .none };
state = .CharLiteralStart;
},
'u' => {
@@ -641,11 +606,11 @@ pub const Tokenizer = struct {
state = .u8;
},
'\'' => {
- result.id = .{ .CharLiteral = .Utf16 };
+ result.id = .{ .CharLiteral = .utf_16 };
state = .CharLiteralStart;
},
'\"' => {
- result.id = .{ .StringLiteral = .Utf16 };
+ result.id = .{ .StringLiteral = .utf_16 };
state = .StringLiteral;
},
else => {
@@ -655,7 +620,7 @@ pub const Tokenizer = struct {
},
.u8 => switch (c) {
'\"' => {
- result.id = .{ .StringLiteral = .Utf8 };
+ result.id = .{ .StringLiteral = .utf_8 };
state = .StringLiteral;
},
else => {
@@ -665,11 +630,11 @@ pub const Tokenizer = struct {
},
.U => switch (c) {
'\'' => {
- result.id = .{ .CharLiteral = .Utf32 };
+ result.id = .{ .CharLiteral = .utf_32 };
state = .CharLiteralStart;
},
'\"' => {
- result.id = .{ .StringLiteral = .Utf32 };
+ result.id = .{ .StringLiteral = .utf_32 };
state = .StringLiteral;
},
else => {
@@ -679,11 +644,11 @@ pub const Tokenizer = struct {
},
.L => switch (c) {
'\'' => {
- result.id = .{ .CharLiteral = .Wide };
+ result.id = .{ .CharLiteral = .wide };
state = .CharLiteralStart;
},
'\"' => {
- result.id = .{ .StringLiteral = .Wide };
+ result.id = .{ .StringLiteral = .wide };
state = .StringLiteral;
},
else => {
@@ -808,7 +773,7 @@ pub const Tokenizer = struct {
.Identifier => switch (c) {
'a'...'z', 'A'...'Z', '_', '0'...'9' => {},
else => {
- result.id = Token.getKeyword(self.source.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier;
+ result.id = Token.getKeyword(self.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier;
if (self.prev_tok_id == .Hash)
self.pp_directive = true;
break;
@@ -1137,7 +1102,7 @@ pub const Tokenizer = struct {
state = .IntegerSuffixL;
},
else => {
- result.id = .{ .IntegerLiteral = .None };
+ result.id = .{ .IntegerLiteral = .none };
break;
},
},
@@ -1146,7 +1111,7 @@ pub const Tokenizer = struct {
state = .IntegerSuffixUL;
},
else => {
- result.id = .{ .IntegerLiteral = .U };
+ result.id = .{ .IntegerLiteral = .u };
break;
},
},
@@ -1155,34 +1120,34 @@ pub const Tokenizer = struct {
state = .IntegerSuffixLL;
},
'u', 'U' => {
- result.id = .{ .IntegerLiteral = .LU };
+ result.id = .{ .IntegerLiteral = .lu };
self.index += 1;
break;
},
else => {
- result.id = .{ .IntegerLiteral = .L };
+ result.id = .{ .IntegerLiteral = .l };
break;
},
},
.IntegerSuffixLL => switch (c) {
'u', 'U' => {
- result.id = .{ .IntegerLiteral = .LLU };
+ result.id = .{ .IntegerLiteral = .llu };
self.index += 1;
break;
},
else => {
- result.id = .{ .IntegerLiteral = .LL };
+ result.id = .{ .IntegerLiteral = .ll };
break;
},
},
.IntegerSuffixUL => switch (c) {
'l', 'L' => {
- result.id = .{ .IntegerLiteral = .LLU };
+ result.id = .{ .IntegerLiteral = .llu };
self.index += 1;
break;
},
else => {
- result.id = .{ .IntegerLiteral = .LU };
+ result.id = .{ .IntegerLiteral = .lu };
break;
},
},
@@ -1230,26 +1195,26 @@ pub const Tokenizer = struct {
},
.FloatSuffix => switch (c) {
'l', 'L' => {
- result.id = .{ .FloatLiteral = .L };
+ result.id = .{ .FloatLiteral = .l };
self.index += 1;
break;
},
'f', 'F' => {
- result.id = .{ .FloatLiteral = .F };
+ result.id = .{ .FloatLiteral = .f };
self.index += 1;
break;
},
else => {
- result.id = .{ .FloatLiteral = .None };
+ result.id = .{ .FloatLiteral = .none };
break;
},
},
}
- } else if (self.index == self.source.buffer.len) {
+ } else if (self.index == self.buffer.len) {
switch (state) {
.Start => {},
.u, .u8, .U, .L, .Identifier => {
- result.id = Token.getKeyword(self.source.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier;
+ result.id = Token.getKeyword(self.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier;
},
.Cr,
@@ -1270,11 +1235,11 @@ pub const Tokenizer = struct {
.MacroString,
=> result.id = .Invalid,
- .FloatExponentDigits => result.id = if (counter == 0) .Invalid else .{ .FloatLiteral = .None },
+ .FloatExponentDigits => result.id = if (counter == 0) .Invalid else .{ .FloatLiteral = .none },
.FloatFraction,
.FloatFractionHex,
- => result.id = .{ .FloatLiteral = .None },
+ => result.id = .{ .FloatLiteral = .none },
.IntegerLiteralOct,
.IntegerLiteralBinary,
@@ -1282,13 +1247,13 @@ pub const Tokenizer = struct {
.IntegerLiteral,
.IntegerSuffix,
.Zero,
- => result.id = .{ .IntegerLiteral = .None },
- .IntegerSuffixU => result.id = .{ .IntegerLiteral = .U },
- .IntegerSuffixL => result.id = .{ .IntegerLiteral = .L },
- .IntegerSuffixLL => result.id = .{ .IntegerLiteral = .LL },
- .IntegerSuffixUL => result.id = .{ .IntegerLiteral = .LU },
+ => result.id = .{ .IntegerLiteral = .none },
+ .IntegerSuffixU => result.id = .{ .IntegerLiteral = .u },
+ .IntegerSuffixL => result.id = .{ .IntegerLiteral = .l },
+ .IntegerSuffixLL => result.id = .{ .IntegerLiteral = .ll },
+ .IntegerSuffixUL => result.id = .{ .IntegerLiteral = .lu },
- .FloatSuffix => result.id = .{ .FloatLiteral = .None },
+ .FloatSuffix => result.id = .{ .FloatLiteral = .none },
.Equal => result.id = .Equal,
.Bang => result.id = .Bang,
.Minus => result.id = .Minus,
@@ -1466,7 +1431,7 @@ test "preprocessor keywords" {
.Hash,
.Identifier,
.AngleBracketLeft,
- .{ .IntegerLiteral = .None },
+ .{ .IntegerLiteral = .none },
.Nl,
.Hash,
.Keyword_ifdef,
@@ -1499,18 +1464,18 @@ test "line continuation" {
.Identifier,
.Identifier,
.Nl,
- .{ .StringLiteral = .None },
+ .{ .StringLiteral = .none },
.Nl,
.Hash,
.Keyword_define,
- .{ .StringLiteral = .None },
+ .{ .StringLiteral = .none },
.Nl,
- .{ .StringLiteral = .None },
+ .{ .StringLiteral = .none },
.Nl,
.Hash,
.Keyword_define,
- .{ .StringLiteral = .None },
- .{ .StringLiteral = .None },
+ .{ .StringLiteral = .none },
+ .{ .StringLiteral = .none },
});
}
@@ -1527,23 +1492,23 @@ test "string prefix" {
\\L'foo'
\\
, &[_]Token.Id{
- .{ .StringLiteral = .None },
+ .{ .StringLiteral = .none },
.Nl,
- .{ .StringLiteral = .Utf16 },
+ .{ .StringLiteral = .utf_16 },
.Nl,
- .{ .StringLiteral = .Utf8 },
+ .{ .StringLiteral = .utf_8 },
.Nl,
- .{ .StringLiteral = .Utf32 },
+ .{ .StringLiteral = .utf_32 },
.Nl,
- .{ .StringLiteral = .Wide },
+ .{ .StringLiteral = .wide },
.Nl,
- .{ .CharLiteral = .None },
+ .{ .CharLiteral = .none },
.Nl,
- .{ .CharLiteral = .Utf16 },
+ .{ .CharLiteral = .utf_16 },
.Nl,
- .{ .CharLiteral = .Utf32 },
+ .{ .CharLiteral = .utf_32 },
.Nl,
- .{ .CharLiteral = .Wide },
+ .{ .CharLiteral = .wide },
.Nl,
});
}
@@ -1555,33 +1520,29 @@ test "num suffixes" {
\\ 1u 1ul 1ull 1
\\
, &[_]Token.Id{
- .{ .FloatLiteral = .F },
- .{ .FloatLiteral = .L },
- .{ .FloatLiteral = .None },
- .{ .FloatLiteral = .None },
- .{ .FloatLiteral = .None },
+ .{ .FloatLiteral = .f },
+ .{ .FloatLiteral = .l },
+ .{ .FloatLiteral = .none },
+ .{ .FloatLiteral = .none },
+ .{ .FloatLiteral = .none },
.Nl,
- .{ .IntegerLiteral = .L },
- .{ .IntegerLiteral = .LU },
- .{ .IntegerLiteral = .LL },
- .{ .IntegerLiteral = .LLU },
- .{ .IntegerLiteral = .None },
+ .{ .IntegerLiteral = .l },
+ .{ .IntegerLiteral = .lu },
+ .{ .IntegerLiteral = .ll },
+ .{ .IntegerLiteral = .llu },
+ .{ .IntegerLiteral = .none },
.Nl,
- .{ .IntegerLiteral = .U },
- .{ .IntegerLiteral = .LU },
- .{ .IntegerLiteral = .LLU },
- .{ .IntegerLiteral = .None },
+ .{ .IntegerLiteral = .u },
+ .{ .IntegerLiteral = .lu },
+ .{ .IntegerLiteral = .llu },
+ .{ .IntegerLiteral = .none },
.Nl,
});
}
fn expectTokens(source: []const u8, expected_tokens: []const Token.Id) void {
var tokenizer = Tokenizer{
- .source = &Source{
- .buffer = source,
- .file_name = undefined,
- .tokens = undefined,
- },
+ .buffer = source,
};
for (expected_tokens) |expected_token_id| {
const token = tokenizer.next();
diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig
@@ -364,6 +364,7 @@ pub const ChildProcess = struct {
error.FileTooBig => unreachable,
error.DeviceBusy => unreachable,
error.FileLocksNotSupported => unreachable,
+ error.BadPathName => unreachable, // Windows-only
else => |e| return e,
}
else
@@ -480,25 +481,20 @@ pub const ChildProcess = struct {
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
- // TODO use CreateFileW here since we are using a string literal for the path
const nul_handle = if (any_ignore)
- windows.CreateFile(
- "NUL",
- windows.GENERIC_READ,
- windows.FILE_SHARE_READ,
- null,
- windows.OPEN_EXISTING,
- windows.FILE_ATTRIBUTE_NORMAL,
- null,
- ) catch |err| switch (err) {
- error.SharingViolation => unreachable, // not possible for "NUL"
+ windows.OpenFile(&[_]u16{ 'N', 'U', 'L' }, .{
+ .dir = std.fs.cwd().fd,
+ .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
+ .share_access = windows.FILE_SHARE_READ,
+ .creation = windows.OPEN_EXISTING,
+ .io_mode = .blocking,
+ }) catch |err| switch (err) {
error.PathAlreadyExists => unreachable, // not possible for "NUL"
error.PipeBusy => unreachable, // not possible for "NUL"
- error.InvalidUtf8 => unreachable, // not possible for "NUL"
- error.BadPathName => unreachable, // not possible for "NUL"
error.FileNotFound => unreachable, // not possible for "NUL"
error.AccessDenied => unreachable, // not possible for "NUL"
error.NameTooLong => unreachable, // not possible for "NUL"
+ error.WouldBlock => unreachable, // not possible for "NUL"
else => |e| return e,
}
else
diff --git a/lib/std/crypto/blake2.zig b/lib/std/crypto/blake2.zig
@@ -1,8 +1,7 @@
const mem = @import("../mem.zig");
-const math = @import("../math.zig");
-const endian = @import("../endian.zig");
-const debug = @import("../debug.zig");
const builtin = @import("builtin");
+const debug = @import("../debug.zig");
+const math = @import("../math.zig");
const htest = @import("test.zig");
const RoundParam = struct {
@@ -31,7 +30,7 @@ fn Rp(a: usize, b: usize, c: usize, d: usize, x: usize, y: usize) RoundParam {
pub const Blake2s224 = Blake2s(224);
pub const Blake2s256 = Blake2s(256);
-fn Blake2s(comptime out_len: usize) type {
+pub fn Blake2s(comptime out_len: usize) type {
return struct {
const Self = @This();
pub const block_length = 64;
@@ -67,10 +66,17 @@ fn Blake2s(comptime out_len: usize) type {
buf: [64]u8,
buf_len: u8,
+ key: []const u8,
+
pub fn init() Self {
+ return init_keyed("");
+ }
+
+ pub fn init_keyed(key: []const u8) Self {
debug.assert(8 <= out_len and out_len <= 512);
var s: Self = undefined;
+ s.key = key;
s.reset();
return s;
}
@@ -78,14 +84,24 @@ fn Blake2s(comptime out_len: usize) type {
pub fn reset(d: *Self) void {
mem.copy(u32, d.h[0..], iv[0..]);
- // No key plus default parameters
- d.h[0] ^= 0x01010000 ^ @intCast(u32, out_len >> 3);
+ // default parameters
+ d.h[0] ^= 0x01010000 ^ @truncate(u32, d.key.len << 8) ^ @intCast(u32, out_len >> 3);
d.t = 0;
d.buf_len = 0;
+
+ if (d.key.len > 0) {
+ mem.set(u8, d.buf[d.key.len..], 0);
+ d.update(d.key);
+ d.buf_len = 64;
+ }
}
pub fn hash(b: []const u8, out: []u8) void {
- var d = Self.init();
+ Self.hash_keyed("", b, out);
+ }
+
+ pub fn hash_keyed(key: []const u8, b: []const u8, out: []u8) void {
+ var d = Self.init_keyed(key);
d.update(b);
d.final(out);
}
@@ -94,7 +110,7 @@ fn Blake2s(comptime out_len: usize) type {
var off: usize = 0;
// Partial buffer exists from previous update. Copy into buffer then hash.
- if (d.buf_len != 0 and d.buf_len + b.len >= 64) {
+ if (d.buf_len != 0 and d.buf_len + b.len > 64) {
off += 64 - d.buf_len;
mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
d.t += 64;
@@ -103,7 +119,7 @@ fn Blake2s(comptime out_len: usize) type {
}
// Full middle blocks.
- while (off + 64 <= b.len) : (off += 64) {
+ while (off + 64 < b.len) : (off += 64) {
d.t += 64;
d.round(b[off .. off + 64], false);
}
@@ -123,7 +139,7 @@ fn Blake2s(comptime out_len: usize) type {
const rr = d.h[0 .. out_len / 32];
for (rr) |s, j| {
- mem.writeIntLittle(u32, out[4 * j ..][0..4], s);
+ mem.writeIntSliceLittle(u32, out[4 * j ..], s);
}
}
@@ -188,6 +204,9 @@ test "blake2s224 single" {
const h3 = "e4e5cb6c7cae41982b397bf7b7d2d9d1949823ae78435326e8db4912";
htest.assertEqualHash(Blake2s224, h3, "The quick brown fox jumps over the lazy dog");
+
+ const h4 = "557381a78facd2b298640f4e32113e58967d61420af1aa939d0cfe01";
+ htest.assertEqualHash(Blake2s224, h4, "a" ** 32 ++ "b" ** 32);
}
test "blake2s224 streaming" {
@@ -212,6 +231,37 @@ test "blake2s224 streaming" {
h.update("c");
h.final(out[0..]);
htest.assertEqual(h2, out[0..]);
+
+ const h3 = "557381a78facd2b298640f4e32113e58967d61420af1aa939d0cfe01";
+
+ h.reset();
+ h.update("a" ** 32);
+ h.update("b" ** 32);
+ h.final(out[0..]);
+ htest.assertEqual(h3, out[0..]);
+
+ h.reset();
+ h.update("a" ** 32 ++ "b" ** 32);
+ h.final(out[0..]);
+ htest.assertEqual(h3, out[0..]);
+}
+
+test "comptime blake2s224" {
+ comptime {
+ @setEvalBranchQuota(6000);
+ var block = [_]u8{0} ** Blake2s224.block_length;
+ var out: [Blake2s224.digest_length]u8 = undefined;
+
+ const h1 = "86b7611563293f8c73627df7a6d6ba25ca0548c2a6481f7d116ee576";
+
+ htest.assertEqualHash(Blake2s224, h1, block[0..]);
+
+ var h = Blake2s224.init();
+ h.update(&block);
+ h.final(out[0..]);
+
+ htest.assertEqual(h1, out[0..]);
+ }
}
test "blake2s256 single" {
@@ -223,6 +273,9 @@ test "blake2s256 single" {
const h3 = "606beeec743ccbeff6cbcdf5d5302aa855c256c29b88c8ed331ea1a6bf3c8812";
htest.assertEqualHash(Blake2s256, h3, "The quick brown fox jumps over the lazy dog");
+
+ const h4 = "8d8711dade07a6b92b9a3ea1f40bee9b2c53ff3edd2a273dec170b0163568977";
+ htest.assertEqualHash(Blake2s256, h4, "a" ** 32 ++ "b" ** 32);
}
test "blake2s256 streaming" {
@@ -247,15 +300,60 @@ test "blake2s256 streaming" {
h.update("c");
h.final(out[0..]);
htest.assertEqual(h2, out[0..]);
+
+ const h3 = "8d8711dade07a6b92b9a3ea1f40bee9b2c53ff3edd2a273dec170b0163568977";
+
+ h.reset();
+ h.update("a" ** 32);
+ h.update("b" ** 32);
+ h.final(out[0..]);
+ htest.assertEqual(h3, out[0..]);
+
+ h.reset();
+ h.update("a" ** 32 ++ "b" ** 32);
+ h.final(out[0..]);
+ htest.assertEqual(h3, out[0..]);
}
-test "blake2s256 aligned final" {
- var block = [_]u8{0} ** Blake2s256.block_length;
- var out: [Blake2s256.digest_length]u8 = undefined;
+test "blake2s256 keyed" {
+ var out: [32]u8 = undefined;
+
+ const h1 = "10f918da4d74fab3302e48a5d67d03804b1ec95372a62a0f33b7c9fa28ba1ae6";
+ const key = "secret_key";
- var h = Blake2s256.init();
- h.update(&block);
+ Blake2s256.hash_keyed(key, "a" ** 64 ++ "b" ** 64, &out);
+ htest.assertEqual(h1, out[0..]);
+
+ var h = Blake2s256.init_keyed(key);
+ h.update("a" ** 64 ++ "b" ** 64);
+ h.final(out[0..]);
+
+ htest.assertEqual(h1, out[0..]);
+
+ h.reset();
+ h.update("a" ** 64);
+ h.update("b" ** 64);
h.final(out[0..]);
+
+ htest.assertEqual(h1, out[0..]);
+}
+
+test "comptime blake2s256" {
+ comptime {
+ @setEvalBranchQuota(6000);
+ var block = [_]u8{0} ** Blake2s256.block_length;
+ var out: [Blake2s256.digest_length]u8 = undefined;
+
+ const h1 = "ae09db7cd54f42b490ef09b6bc541af688e4959bb8c53f359a6f56e38ab454a3";
+
+ htest.assertEqualHash(Blake2s256, h1, block[0..]);
+
+ var h = Blake2s256.init();
+ h.update(&block);
+ h.final(out[0..]);
+
+ htest.assertEqual(h1, out[0..]);
+ }
}
/////////////////////
@@ -264,7 +362,7 @@ test "blake2s256 aligned final" {
pub const Blake2b384 = Blake2b(384);
pub const Blake2b512 = Blake2b(512);
-fn Blake2b(comptime out_len: usize) type {
+pub fn Blake2b(comptime out_len: usize) type {
return struct {
const Self = @This();
pub const block_length = 128;
@@ -302,10 +400,17 @@ fn Blake2b(comptime out_len: usize) type {
buf: [128]u8,
buf_len: u8,
+ key: []const u8,
+
pub fn init() Self {
+ return init_keyed("");
+ }
+
+ pub fn init_keyed(key: []const u8) Self {
debug.assert(8 <= out_len and out_len <= 512);
var s: Self = undefined;
+ s.key = key;
s.reset();
return s;
}
@@ -313,14 +418,24 @@ fn Blake2b(comptime out_len: usize) type {
pub fn reset(d: *Self) void {
mem.copy(u64, d.h[0..], iv[0..]);
- // No key plus default parameters
- d.h[0] ^= 0x01010000 ^ (out_len >> 3);
+ // default parameters
+ d.h[0] ^= 0x01010000 ^ (d.key.len << 8) ^ (out_len >> 3);
d.t = 0;
d.buf_len = 0;
+
+ if (d.key.len > 0) {
+ mem.set(u8, d.buf[d.key.len..], 0);
+ d.update(d.key);
+ d.buf_len = 128;
+ }
}
pub fn hash(b: []const u8, out: []u8) void {
- var d = Self.init();
+ Self.hash_keyed("", b, out);
+ }
+
+ pub fn hash_keyed(key: []const u8, b: []const u8, out: []u8) void {
+ var d = Self.init_keyed(key);
d.update(b);
d.final(out);
}
@@ -329,7 +444,7 @@ fn Blake2b(comptime out_len: usize) type {
var off: usize = 0;
// Partial buffer exists from previous update. Copy into buffer then hash.
- if (d.buf_len != 0 and d.buf_len + b.len >= 128) {
+ if (d.buf_len != 0 and d.buf_len + b.len > 128) {
off += 128 - d.buf_len;
mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
d.t += 128;
@@ -338,7 +453,7 @@ fn Blake2b(comptime out_len: usize) type {
}
// Full middle blocks.
- while (off + 128 <= b.len) : (off += 128) {
+ while (off + 128 < b.len) : (off += 128) {
d.t += 128;
d.round(b[off .. off + 128], false);
}
@@ -356,7 +471,7 @@ fn Blake2b(comptime out_len: usize) type {
const rr = d.h[0 .. out_len / 64];
for (rr) |s, j| {
- mem.writeIntLittle(u64, out[8 * j ..][0..8], s);
+ mem.writeIntSliceLittle(u64, out[8 * j ..], s);
}
}
@@ -421,6 +536,9 @@ test "blake2b384 single" {
const h3 = "b7c81b228b6bd912930e8f0b5387989691c1cee1e65aade4da3b86a3c9f678fc8018f6ed9e2906720c8d2a3aeda9c03d";
htest.assertEqualHash(Blake2b384, h3, "The quick brown fox jumps over the lazy dog");
+
+ const h4 = "b7283f0172fecbbd7eca32ce10d8a6c06b453cb3cf675b33eb4246f0da2bb94a6c0bdd6eec0b5fd71ec4fd51be80bf4c";
+ htest.assertEqualHash(Blake2b384, h4, "a" ** 64 ++ "b" ** 64);
}
test "blake2b384 streaming" {
@@ -445,6 +563,37 @@ test "blake2b384 streaming" {
h.update("c");
h.final(out[0..]);
htest.assertEqual(h2, out[0..]);
+
+ const h3 = "b7283f0172fecbbd7eca32ce10d8a6c06b453cb3cf675b33eb4246f0da2bb94a6c0bdd6eec0b5fd71ec4fd51be80bf4c";
+
+ h.reset();
+ h.update("a" ** 64 ++ "b" ** 64);
+ h.final(out[0..]);
+ htest.assertEqual(h3, out[0..]);
+
+ h.reset();
+ h.update("a" ** 64);
+ h.update("b" ** 64);
+ h.final(out[0..]);
+ htest.assertEqual(h3, out[0..]);
+}
+
+test "comptime blake2b384" {
+ comptime {
+ @setEvalBranchQuota(7000);
+ var block = [_]u8{0} ** Blake2b384.block_length;
+ var out: [Blake2b384.digest_length]u8 = undefined;
+
+ const h1 = "e8aa1931ea0422e4446fecdd25c16cf35c240b10cb4659dd5c776eddcaa4d922397a589404b46eb2e53d78132d05fd7d";
+
+ htest.assertEqualHash(Blake2b384, h1, block[0..]);
+
+ var h = Blake2b384.init();
+ h.update(&block);
+ h.final(out[0..]);
+
+ htest.assertEqual(h1, out[0..]);
+ }
}
test "blake2b512 single" {
@@ -456,6 +605,9 @@ test "blake2b512 single" {
const h3 = "a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918";
htest.assertEqualHash(Blake2b512, h3, "The quick brown fox jumps over the lazy dog");
+
+ const h4 = "049980af04d6a2cf16b4b49793c3ed7e40732073788806f2c989ebe9547bda0541d63abe298ec8955d08af48ae731f2e8a0bd6d201655a5473b4aa79d211b920";
+ htest.assertEqualHash(Blake2b512, h4, "a" ** 64 ++ "b" ** 64);
}
test "blake2b512 streaming" {
@@ -480,13 +632,58 @@ test "blake2b512 streaming" {
h.update("c");
h.final(out[0..]);
htest.assertEqual(h2, out[0..]);
+
+ const h3 = "049980af04d6a2cf16b4b49793c3ed7e40732073788806f2c989ebe9547bda0541d63abe298ec8955d08af48ae731f2e8a0bd6d201655a5473b4aa79d211b920";
+
+ h.reset();
+ h.update("a" ** 64 ++ "b" ** 64);
+ h.final(out[0..]);
+ htest.assertEqual(h3, out[0..]);
+
+ h.reset();
+ h.update("a" ** 64);
+ h.update("b" ** 64);
+ h.final(out[0..]);
+ htest.assertEqual(h3, out[0..]);
}
-test "blake2b512 aligned final" {
- var block = [_]u8{0} ** Blake2b512.block_length;
- var out: [Blake2b512.digest_length]u8 = undefined;
+test "blake2b512 keyed" {
+ var out: [64]u8 = undefined;
- var h = Blake2b512.init();
- h.update(&block);
+ const h1 = "8a978060ccaf582f388f37454363071ac9a67e3a704585fd879fb8a419a447e389c7c6de790faa20a7a7dccf197de736bc5b40b98a930b36df5bee7555750c4d";
+ const key = "secret_key";
+
+ Blake2b512.hash_keyed(key, "a" ** 64 ++ "b" ** 64, &out);
+ htest.assertEqual(h1, out[0..]);
+
+ var h = Blake2b512.init_keyed(key);
+ h.update("a" ** 64 ++ "b" ** 64);
+ h.final(out[0..]);
+
+ htest.assertEqual(h1, out[0..]);
+
+ h.reset();
+ h.update("a" ** 64);
+ h.update("b" ** 64);
h.final(out[0..]);
+
+ htest.assertEqual(h1, out[0..]);
+}
+
+test "comptime blake2b512" {
+ comptime {
+ @setEvalBranchQuota(8000);
+ var block = [_]u8{0} ** Blake2b512.block_length;
+ var out: [Blake2b512.digest_length]u8 = undefined;
+
+ const h1 = "865939e120e6805438478841afb739ae4250cf372653078a065cdcfffca4caf798e6d462b65d658fc165782640eded70963449ae1500fb0f24981d7727e22c41";
+
+ htest.assertEqualHash(Blake2b512, h1, block[0..]);
+
+ var h = Blake2b512.init();
+ h.update(&block);
+ h.final(out[0..]);
+
+ htest.assertEqual(h1, out[0..]);
+ }
}
diff --git a/lib/std/debug/leb128.zig b/lib/std/debug/leb128.zig
@@ -159,6 +159,50 @@ pub fn writeILEB128Mem(ptr: []u8, int_value: anytype) !usize {
return buf.pos;
}
+/// This is an "advanced" function. It allows one to use a fixed amount of memory to store a
+/// ULEB128. This defeats the entire purpose of using this data encoding; it will no longer use
+/// fewer bytes to store smaller numbers. The advantage of using a fixed width is that it makes
+/// fields have a predictable size and so depending on the use case this tradeoff can be worthwhile.
+/// An example use case of this is in emitting DWARF info where one wants to make a ULEB128 field
+/// "relocatable", meaning that it becomes possible to later go back and patch the number to be a
+/// different value without shifting all the following code.
+pub fn writeUnsignedFixed(comptime l: usize, ptr: *[l]u8, int: std.meta.Int(false, l * 7)) void {
+ const T = @TypeOf(int);
+ const U = if (T.bit_count < 8) u8 else T;
+ var value = @intCast(U, int);
+
+ comptime var i = 0;
+ inline while (i < (l - 1)) : (i += 1) {
+ const byte = @truncate(u8, value) | 0b1000_0000;
+ value >>= 7;
+ ptr[i] = byte;
+ }
+ ptr[i] = @truncate(u8, value);
+}
+
+test "writeUnsignedFixed" {
+ {
+ var buf: [4]u8 = undefined;
+ writeUnsignedFixed(4, &buf, 0);
+ testing.expect((try test_read_uleb128(u64, &buf)) == 0);
+ }
+ {
+ var buf: [4]u8 = undefined;
+ writeUnsignedFixed(4, &buf, 1);
+ testing.expect((try test_read_uleb128(u64, &buf)) == 1);
+ }
+ {
+ var buf: [4]u8 = undefined;
+ writeUnsignedFixed(4, &buf, 1000);
+ testing.expect((try test_read_uleb128(u64, &buf)) == 1000);
+ }
+ {
+ var buf: [4]u8 = undefined;
+ writeUnsignedFixed(4, &buf, 10000000);
+ testing.expect((try test_read_uleb128(u64, &buf)) == 10000000);
+ }
+}
+
// tests
fn test_read_stream_ileb128(comptime T: type, encoded: []const u8) !T {
var reader = std.io.fixedBufferStream(encoded);
diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig
@@ -9,7 +9,7 @@ const leb = @import("debug/leb128.zig");
const ArrayList = std.ArrayList;
-usingnamespace @import("dwarf_bits.zig");
+pub usingnamespace @import("dwarf_bits.zig");
const PcRange = struct {
start: u64,
diff --git a/lib/std/dwarf_bits.zig b/lib/std/dwarf_bits.zig
@@ -680,3 +680,20 @@ pub const LANG_HP_Basic91 = 0x8004;
pub const LANG_HP_Pascal91 = 0x8005;
pub const LANG_HP_IMacro = 0x8006;
pub const LANG_HP_Assembler = 0x8007;
+
+pub const UT_compile = 0x01;
+pub const UT_type = 0x02;
+pub const UT_partial = 0x03;
+pub const UT_skeleton = 0x04;
+pub const UT_split_compile = 0x05;
+pub const UT_split_type = 0x06;
+pub const UT_lo_user = 0x80;
+pub const UT_hi_user = 0xff;
+
+pub const LNCT_path = 0x1;
+pub const LNCT_directory_index = 0x2;
+pub const LNCT_timestamp = 0x3;
+pub const LNCT_size = 0x4;
+pub const LNCT_MD5 = 0x5;
+pub const LNCT_lo_user = 0x2000;
+pub const LNCT_hi_user = 0x3fff;
diff --git a/lib/std/elf.zig b/lib/std/elf.zig
@@ -341,6 +341,20 @@ const Header = struct {
shentsize: u16,
shnum: u16,
shstrndx: u16,
+
+ pub fn program_header_iterator(self: Header, file: File) ProgramHeaderIterator {
+ return .{
+ .elf_header = self,
+ .file = file,
+ };
+ }
+
+ pub fn section_header_iterator(self: Header, file: File) SectionHeaderIterator {
+ return .{
+ .elf_header = self,
+ .file = file,
+ };
+ }
};
pub fn readHeader(file: File) !Header {
@@ -378,144 +392,137 @@ pub fn readHeader(file: File) !Header {
});
}
-/// All integers are native endian.
-pub const AllHeaders = struct {
- header: Header,
- section_headers: []Elf64_Shdr,
- program_headers: []Elf64_Phdr,
- allocator: *mem.Allocator,
-};
-
-pub fn readAllHeaders(allocator: *mem.Allocator, file: File) !AllHeaders {
- var hdrs: AllHeaders = .{
- .allocator = allocator,
- .header = try readHeader(file),
- .section_headers = undefined,
- .program_headers = undefined,
- };
- const is_64 = hdrs.header.is_64;
- const need_bswap = hdrs.header.endian != std.builtin.endian;
-
- hdrs.section_headers = try allocator.alloc(Elf64_Shdr, hdrs.header.shnum);
- errdefer allocator.free(hdrs.section_headers);
-
- hdrs.program_headers = try allocator.alloc(Elf64_Phdr, hdrs.header.phnum);
- errdefer allocator.free(hdrs.program_headers);
-
- // If the ELF file is 64-bit and same-endianness, then all we have to do is
- // yeet the bytes into memory.
- // If only the endianness is different, they can be simply byte swapped.
- if (is_64) {
- const shdr_buf = std.mem.sliceAsBytes(hdrs.section_headers);
- const phdr_buf = std.mem.sliceAsBytes(hdrs.program_headers);
- try preadNoEof(file, shdr_buf, hdrs.header.shoff);
- try preadNoEof(file, phdr_buf, hdrs.header.phoff);
+pub const ProgramHeaderIterator = struct {
+ elf_header: Header,
+ file: File,
+ index: usize = 0,
+
+ pub fn next(self: *ProgramHeaderIterator) !?Elf64_Phdr {
+ if (self.index >= self.elf_header.phnum) return null;
+ defer self.index += 1;
+
+ if (self.elf_header.is_64) {
+ var phdr: Elf64_Phdr = undefined;
+ const offset = self.elf_header.phoff + @sizeOf(@TypeOf(phdr)) * self.index;
+ try preadNoEof(self.file, mem.asBytes(&phdr), offset);
+
+ // ELF endianness matches native endianness.
+ if (self.elf_header.endian == std.builtin.endian) return phdr;
+
+ // Convert fields to native endianness.
+ return Elf64_Phdr{
+ .p_type = @byteSwap(@TypeOf(phdr.p_type), phdr.p_type),
+ .p_offset = @byteSwap(@TypeOf(phdr.p_offset), phdr.p_offset),
+ .p_vaddr = @byteSwap(@TypeOf(phdr.p_vaddr), phdr.p_vaddr),
+ .p_paddr = @byteSwap(@TypeOf(phdr.p_paddr), phdr.p_paddr),
+ .p_filesz = @byteSwap(@TypeOf(phdr.p_filesz), phdr.p_filesz),
+ .p_memsz = @byteSwap(@TypeOf(phdr.p_memsz), phdr.p_memsz),
+ .p_flags = @byteSwap(@TypeOf(phdr.p_flags), phdr.p_flags),
+ .p_align = @byteSwap(@TypeOf(phdr.p_align), phdr.p_align),
+ };
+ }
- if (need_bswap) {
- for (hdrs.section_headers) |*shdr| {
- shdr.* = .{
- .sh_name = @byteSwap(@TypeOf(shdr.sh_name), shdr.sh_name),
- .sh_type = @byteSwap(@TypeOf(shdr.sh_type), shdr.sh_type),
- .sh_flags = @byteSwap(@TypeOf(shdr.sh_flags), shdr.sh_flags),
- .sh_addr = @byteSwap(@TypeOf(shdr.sh_addr), shdr.sh_addr),
- .sh_offset = @byteSwap(@TypeOf(shdr.sh_offset), shdr.sh_offset),
- .sh_size = @byteSwap(@TypeOf(shdr.sh_size), shdr.sh_size),
- .sh_link = @byteSwap(@TypeOf(shdr.sh_link), shdr.sh_link),
- .sh_info = @byteSwap(@TypeOf(shdr.sh_info), shdr.sh_info),
- .sh_addralign = @byteSwap(@TypeOf(shdr.sh_addralign), shdr.sh_addralign),
- .sh_entsize = @byteSwap(@TypeOf(shdr.sh_entsize), shdr.sh_entsize),
- };
- }
- for (hdrs.program_headers) |*phdr| {
- phdr.* = .{
- .p_type = @byteSwap(@TypeOf(phdr.p_type), phdr.p_type),
- .p_offset = @byteSwap(@TypeOf(phdr.p_offset), phdr.p_offset),
- .p_vaddr = @byteSwap(@TypeOf(phdr.p_vaddr), phdr.p_vaddr),
- .p_paddr = @byteSwap(@TypeOf(phdr.p_paddr), phdr.p_paddr),
- .p_filesz = @byteSwap(@TypeOf(phdr.p_filesz), phdr.p_filesz),
- .p_memsz = @byteSwap(@TypeOf(phdr.p_memsz), phdr.p_memsz),
- .p_flags = @byteSwap(@TypeOf(phdr.p_flags), phdr.p_flags),
- .p_align = @byteSwap(@TypeOf(phdr.p_align), phdr.p_align),
- };
- }
+ var phdr: Elf32_Phdr = undefined;
+ const offset = self.elf_header.phoff + @sizeOf(@TypeOf(phdr)) * self.index;
+ try preadNoEof(self.file, mem.asBytes(&phdr), offset);
+
+ // ELF endianness does NOT match native endianness.
+ if (self.elf_header.endian != std.builtin.endian) {
+ // Convert fields to native endianness.
+ phdr = .{
+ .p_type = @byteSwap(@TypeOf(phdr.p_type), phdr.p_type),
+ .p_offset = @byteSwap(@TypeOf(phdr.p_offset), phdr.p_offset),
+ .p_vaddr = @byteSwap(@TypeOf(phdr.p_vaddr), phdr.p_vaddr),
+ .p_paddr = @byteSwap(@TypeOf(phdr.p_paddr), phdr.p_paddr),
+ .p_filesz = @byteSwap(@TypeOf(phdr.p_filesz), phdr.p_filesz),
+ .p_memsz = @byteSwap(@TypeOf(phdr.p_memsz), phdr.p_memsz),
+ .p_flags = @byteSwap(@TypeOf(phdr.p_flags), phdr.p_flags),
+ .p_align = @byteSwap(@TypeOf(phdr.p_align), phdr.p_align),
+ };
}
- return hdrs;
+ // Convert 32-bit header to 64-bit.
+ return Elf64_Phdr{
+ .p_type = phdr.p_type,
+ .p_offset = phdr.p_offset,
+ .p_vaddr = phdr.p_vaddr,
+ .p_paddr = phdr.p_paddr,
+ .p_filesz = phdr.p_filesz,
+ .p_memsz = phdr.p_memsz,
+ .p_flags = phdr.p_flags,
+ .p_align = phdr.p_align,
+ };
}
+};
- const shdrs_32 = try allocator.alloc(Elf32_Shdr, hdrs.header.shnum);
- defer allocator.free(shdrs_32);
-
- const phdrs_32 = try allocator.alloc(Elf32_Phdr, hdrs.header.phnum);
- defer allocator.free(phdrs_32);
-
- const shdr_buf = std.mem.sliceAsBytes(shdrs_32);
- const phdr_buf = std.mem.sliceAsBytes(phdrs_32);
- try preadNoEof(file, shdr_buf, hdrs.header.shoff);
- try preadNoEof(file, phdr_buf, hdrs.header.phoff);
-
- if (need_bswap) {
- for (hdrs.section_headers) |*shdr, i| {
- const o = shdrs_32[i];
- shdr.* = .{
- .sh_name = @byteSwap(@TypeOf(o.sh_name), o.sh_name),
- .sh_type = @byteSwap(@TypeOf(o.sh_type), o.sh_type),
- .sh_flags = @byteSwap(@TypeOf(o.sh_flags), o.sh_flags),
- .sh_addr = @byteSwap(@TypeOf(o.sh_addr), o.sh_addr),
- .sh_offset = @byteSwap(@TypeOf(o.sh_offset), o.sh_offset),
- .sh_size = @byteSwap(@TypeOf(o.sh_size), o.sh_size),
- .sh_link = @byteSwap(@TypeOf(o.sh_link), o.sh_link),
- .sh_info = @byteSwap(@TypeOf(o.sh_info), o.sh_info),
- .sh_addralign = @byteSwap(@TypeOf(o.sh_addralign), o.sh_addralign),
- .sh_entsize = @byteSwap(@TypeOf(o.sh_entsize), o.sh_entsize),
- };
- }
- for (hdrs.program_headers) |*phdr, i| {
- const o = phdrs_32[i];
- phdr.* = .{
- .p_type = @byteSwap(@TypeOf(o.p_type), o.p_type),
- .p_offset = @byteSwap(@TypeOf(o.p_offset), o.p_offset),
- .p_vaddr = @byteSwap(@TypeOf(o.p_vaddr), o.p_vaddr),
- .p_paddr = @byteSwap(@TypeOf(o.p_paddr), o.p_paddr),
- .p_filesz = @byteSwap(@TypeOf(o.p_filesz), o.p_filesz),
- .p_memsz = @byteSwap(@TypeOf(o.p_memsz), o.p_memsz),
- .p_flags = @byteSwap(@TypeOf(o.p_flags), o.p_flags),
- .p_align = @byteSwap(@TypeOf(o.p_align), o.p_align),
+pub const SectionHeaderIterator = struct {
+ elf_header: Header,
+ file: File,
+ index: usize = 0,
+
+ pub fn next(self: *SectionHeaderIterator) !?Elf64_Shdr {
+ if (self.index >= self.elf_header.shnum) return null;
+ defer self.index += 1;
+
+ if (self.elf_header.is_64) {
+ var shdr: Elf64_Shdr = undefined;
+ const offset = self.elf_header.phoff + @sizeOf(@TypeOf(shdr)) * self.index;
+ try preadNoEof(self.file, mem.asBytes(&shdr), offset);
+
+ // ELF endianness matches native endianness.
+ if (self.elf_header.endian == std.builtin.endian) return shdr;
+
+ // Convert fields to native endianness.
+ return Elf64_Shdr{
+ .sh_name = @byteSwap(@TypeOf(shdr.sh_name), shdr.sh_name),
+ .sh_type = @byteSwap(@TypeOf(shdr.sh_type), shdr.sh_type),
+ .sh_flags = @byteSwap(@TypeOf(shdr.sh_flags), shdr.sh_flags),
+ .sh_addr = @byteSwap(@TypeOf(shdr.sh_addr), shdr.sh_addr),
+ .sh_offset = @byteSwap(@TypeOf(shdr.sh_offset), shdr.sh_offset),
+ .sh_size = @byteSwap(@TypeOf(shdr.sh_size), shdr.sh_size),
+ .sh_link = @byteSwap(@TypeOf(shdr.sh_link), shdr.sh_link),
+ .sh_info = @byteSwap(@TypeOf(shdr.sh_info), shdr.sh_info),
+ .sh_addralign = @byteSwap(@TypeOf(shdr.sh_addralign), shdr.sh_addralign),
+ .sh_entsize = @byteSwap(@TypeOf(shdr.sh_entsize), shdr.sh_entsize),
};
}
- } else {
- for (hdrs.section_headers) |*shdr, i| {
- const o = shdrs_32[i];
- shdr.* = .{
- .sh_name = o.sh_name,
- .sh_type = o.sh_type,
- .sh_flags = o.sh_flags,
- .sh_addr = o.sh_addr,
- .sh_offset = o.sh_offset,
- .sh_size = o.sh_size,
- .sh_link = o.sh_link,
- .sh_info = o.sh_info,
- .sh_addralign = o.sh_addralign,
- .sh_entsize = o.sh_entsize,
- };
- }
- for (hdrs.program_headers) |*phdr, i| {
- const o = phdrs_32[i];
- phdr.* = .{
- .p_type = o.p_type,
- .p_offset = o.p_offset,
- .p_vaddr = o.p_vaddr,
- .p_paddr = o.p_paddr,
- .p_filesz = o.p_filesz,
- .p_memsz = o.p_memsz,
- .p_flags = o.p_flags,
- .p_align = o.p_align,
+
+ var shdr: Elf32_Shdr = undefined;
+ const offset = self.elf_header.shoff + @sizeOf(@TypeOf(shdr)) * self.index;
+ try preadNoEof(self.file, mem.asBytes(&shdr), offset);
+
+ // ELF endianness does NOT match native endianness.
+ if (self.elf_header.endian != std.builtin.endian) {
+ // Convert fields to native endianness.
+ shdr = .{
+ .sh_name = @byteSwap(@TypeOf(shdr.sh_name), shdr.sh_name),
+ .sh_type = @byteSwap(@TypeOf(shdr.sh_type), shdr.sh_type),
+ .sh_flags = @byteSwap(@TypeOf(shdr.sh_flags), shdr.sh_flags),
+ .sh_addr = @byteSwap(@TypeOf(shdr.sh_addr), shdr.sh_addr),
+ .sh_offset = @byteSwap(@TypeOf(shdr.sh_offset), shdr.sh_offset),
+ .sh_size = @byteSwap(@TypeOf(shdr.sh_size), shdr.sh_size),
+ .sh_link = @byteSwap(@TypeOf(shdr.sh_link), shdr.sh_link),
+ .sh_info = @byteSwap(@TypeOf(shdr.sh_info), shdr.sh_info),
+ .sh_addralign = @byteSwap(@TypeOf(shdr.sh_addralign), shdr.sh_addralign),
+ .sh_entsize = @byteSwap(@TypeOf(shdr.sh_entsize), shdr.sh_entsize),
};
}
- }
- return hdrs;
-}
+ // Convert 32-bit header to 64-bit.
+ return Elf64_Shdr{
+ .sh_name = shdr.sh_name,
+ .sh_type = shdr.sh_type,
+ .sh_flags = shdr.sh_flags,
+ .sh_addr = shdr.sh_addr,
+ .sh_offset = shdr.sh_offset,
+ .sh_size = shdr.sh_size,
+ .sh_link = shdr.sh_link,
+ .sh_info = shdr.sh_info,
+ .sh_addralign = shdr.sh_addralign,
+ .sh_entsize = shdr.sh_entsize,
+ };
+ }
+};
pub fn int(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
if (is_64) {
@@ -538,7 +545,7 @@ pub fn int32(need_bswap: bool, int_32: anytype, comptime Int64: anytype) Int64 {
}
fn preadNoEof(file: std.fs.File, buf: []u8, offset: u64) !void {
- var i: u64 = 0;
+ var i: usize = 0;
while (i < buf.len) {
const len = file.pread(buf[i .. buf.len - i], offset + i) catch |err| switch (err) {
error.SystemResources => return error.SystemResources,
diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig
@@ -224,6 +224,7 @@ pub fn LinearFifo(
pub fn reader(self: *Self) std.io.Reader(*Self, error{}, readFn) {
return .{ .context = self };
}
+
/// Deprecated: `use reader`
pub fn inStream(self: *Self) std.io.InStream(*Self, error{}, readFn) {
return .{ .context = self };
@@ -315,6 +316,11 @@ pub fn LinearFifo(
return bytes.len;
}
+ pub fn writer(self: *Self) std.io.Writer(*Self, error{OutOfMemory}, appendWrite) {
+ return .{ .context = self };
+ }
+
+ /// Deprecated: `use writer`
pub fn outStream(self: *Self) std.io.OutStream(*Self, error{OutOfMemory}, appendWrite) {
return .{ .context = self };
}
@@ -426,14 +432,14 @@ test "LinearFifo(u8, .Dynamic)" {
fifo.shrink(0);
{
- try fifo.outStream().print("{}, {}!", .{ "Hello", "World" });
+ try fifo.writer().print("{}, {}!", .{ "Hello", "World" });
var result: [30]u8 = undefined;
testing.expectEqualSlices(u8, "Hello, World!", result[0..fifo.read(&result)]);
testing.expectEqual(@as(usize, 0), fifo.readableLength());
}
{
- try fifo.outStream().writeAll("This is a test");
+ try fifo.writer().writeAll("This is a test");
var result: [30]u8 = undefined;
testing.expectEqualSlices(u8, "This", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
testing.expectEqualSlices(u8, "is", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig
@@ -88,6 +88,8 @@ pub fn format(
if (args.len > ArgSetType.bit_count) {
@compileError("32 arguments max are supported per format call");
}
+ if (args.len == 0)
+ return writer.writeAll(fmt);
const State = enum {
Start,
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
@@ -84,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
try crypto.randomBytes(rand_buf[0..]);
base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
- if (cwd().symLink(existing_path, new_path, .{})) {
+ if (cwd().symLink(existing_path, tmp_path, .{})) {
return rename(tmp_path, new_path);
} else |err| switch (err) {
error.PathAlreadyExists => continue,
@@ -225,8 +225,7 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string.
pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
assert(path.isAbsoluteWindowsW(absolute_path_w));
- const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null);
- os.windows.CloseHandle(handle);
+ return os.mkdirW(absolute_path_w, default_new_dir_mode);
}
pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute");
@@ -881,8 +880,7 @@ pub const Dir = struct {
}
pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
- const handle = try os.windows.CreateDirectoryW(self.fd, sub_path, null);
- os.windows.CloseHandle(handle);
+ try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);
}
/// Calls makeDir recursively to make an entire path. Returns success if the path
@@ -1119,7 +1117,7 @@ pub const Dir = struct {
pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
- return self.deleteFileW(sub_path_w.span().ptr);
+ return self.deleteFileW(sub_path_w.span());
} else if (builtin.os.tag == .wasi) {
os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
@@ -1153,7 +1151,7 @@ pub const Dir = struct {
}
/// Same as `deleteFile` except the parameter is WTF-16 encoded.
- pub fn deleteFileW(self: Dir, sub_path_w: [*:0]const u16) DeleteFileError!void {
+ pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
else => |e| return e,
@@ -1182,7 +1180,7 @@ pub const Dir = struct {
pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
- return self.deleteDirW(sub_path_w.span().ptr);
+ return self.deleteDirW(sub_path_w.span());
} else if (builtin.os.tag == .wasi) {
os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
@@ -1204,7 +1202,7 @@ pub const Dir = struct {
/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
/// This function is Windows-only.
- pub fn deleteDirW(self: Dir, sub_path_w: [*:0]const u16) DeleteDirError!void {
+ pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
else => |e| return e,
@@ -1263,11 +1261,11 @@ pub const Dir = struct {
/// are null-terminated, WTF16 encoded.
pub fn symLinkW(
self: Dir,
- target_path_w: [:0]const u16,
- sym_link_path_w: [:0]const u16,
+ target_path_w: []const u16,
+ sym_link_path_w: []const u16,
flags: SymLinkFlags,
) !void {
- return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
+ return os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
}
/// Read value of a symbolic link.
@@ -1278,7 +1276,8 @@ pub const Dir = struct {
return self.readLinkWasi(sub_path, buffer);
}
if (builtin.os.tag == .windows) {
- return os.windows.ReadLink(self.fd, sub_path, buffer);
+ const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
+ return self.readLinkW(sub_path_w.span(), buffer);
}
const sub_path_c = try os.toPosixPath(sub_path);
return self.readLinkZ(&sub_path_c, buffer);
@@ -1295,15 +1294,15 @@ pub const Dir = struct {
pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
- return self.readLinkW(sub_path_w, buffer);
+ return self.readLinkW(sub_path_w.span(), buffer);
}
return os.readlinkatZ(self.fd, sub_path_c, buffer);
}
/// Windows-only. Same as `readLink` except the pathname parameter
/// is null-terminated, WTF16 encoded.
- pub fn readLinkW(self: Dir, sub_path_w: [*:0]const u16, buffer: []u8) ![]u8 {
- return os.windows.ReadLinkW(self.fd, sub_path_w, buffer);
+ pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
+ return os.windows.ReadLink(self.fd, sub_path_w, buffer);
}
/// On success, caller owns returned buffer.
@@ -1813,7 +1812,9 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
assert(path.isAbsolute(target_path));
assert(path.isAbsolute(sym_link_path));
if (builtin.os.tag == .windows) {
- return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory);
+ const target_path_w = try os.windows.sliceToPrefixedFileW(target_path);
+ const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path);
+ return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
}
return os.symlink(target_path, sym_link_path);
}
@@ -1822,10 +1823,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
/// Note that this function will by default try creating a symbolic link to a file. If you would
/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
-pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void {
- assert(path.isAbsoluteWindowsW(target_path_w));
- assert(path.isAbsoluteWindowsW(sym_link_path_w));
- return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory);
+pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void {
+ assert(path.isAbsoluteWindowsWTF16(target_path_w));
+ assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));
+ return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);
}
/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
@@ -1836,7 +1837,7 @@ pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]cons
if (builtin.os.tag == .windows) {
const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
- return os.windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags.is_directory);
+ return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
}
return os.symlinkZ(target_path_c, sym_link_path_c);
}
@@ -1938,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
return walker;
}
-pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError || os.FlockError;
+pub const OpenSelfExeError = error{
+ SharingViolation,
+ PathAlreadyExists,
+ FileNotFound,
+ AccessDenied,
+ PipeBusy,
+ NameTooLong,
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+ /// On Windows, file paths cannot contain these characters:
+ /// '/', '*', '?', '"', '<', '>', '|'
+ BadPathName,
+ Unexpected,
+} || os.OpenError || SelfExePathError || os.FlockError;
pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
if (builtin.os.tag == .linux) {
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
@@ -47,7 +47,20 @@ pub const File = struct {
else => 0o666,
};
- pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError;
+ pub const OpenError = error{
+ SharingViolation,
+ PathAlreadyExists,
+ FileNotFound,
+ AccessDenied,
+ PipeBusy,
+ NameTooLong,
+ /// On Windows, file paths must be valid Unicode.
+ InvalidUtf8,
+ /// On Windows, file paths cannot contain these characters:
+ /// '/', '*', '?', '"', '<', '>', '|'
+ BadPathName,
+ Unexpected,
+ } || os.OpenError || os.FlockError;
pub const Lock = enum { None, Shared, Exclusive };
diff --git a/lib/std/fs/watch.zig b/lib/std/fs/watch.zig
@@ -374,15 +374,13 @@ pub fn Watch(comptime V: type) type {
defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
- const dir_handle = try windows.CreateFileW(
- dirname_utf16le.ptr,
- windows.FILE_LIST_DIRECTORY,
- windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
- null,
- windows.OPEN_EXISTING,
- windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
- null,
- );
+ const dir_handle = try windows.OpenFile(dirname_utf16le, .{
+ .dir = std.fs.cwd().fd,
+ .access_mask = windows.FILE_LIST_DIRECTORY,
+ .creation = windows.FILE_OPEN,
+ .io_mode = .blocking,
+ .open_dir = true,
+ });
var dir_handle_consumed = false;
defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
diff --git a/lib/std/hash/auto_hash.zig b/lib/std/hash/auto_hash.zig
@@ -56,9 +56,6 @@ pub fn hashPointer(hasher: anytype, key: anytype, comptime strat: HashStrategy)
pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
switch (strat) {
.Shallow => {
- // TODO detect via a trait when Key has no padding bits to
- // hash it as an array of bytes.
- // Otherwise, hash every element.
for (key) |element| {
hash(hasher, element, .Shallow);
}
@@ -75,30 +72,34 @@ pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) vo
/// Strategy is provided to determine if pointers should be followed or not.
pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
const Key = @TypeOf(key);
+
+ if (strat == .Shallow and comptime meta.trait.hasUniqueRepresentation(Key)) {
+ @call(.{ .modifier = .always_inline }, hasher.update, .{mem.asBytes(&key)});
+ return;
+ }
+
switch (@typeInfo(Key)) {
.NoReturn,
.Opaque,
.Undefined,
.Void,
.Null,
- .BoundFn,
.ComptimeFloat,
.ComptimeInt,
.Type,
.EnumLiteral,
.Frame,
+ .Float,
=> @compileError("cannot hash this type"),
// Help the optimizer see that hashing an int is easy by inlining!
// TODO Check if the situation is better after #561 is resolved.
.Int => @call(.{ .modifier = .always_inline }, hasher.update, .{std.mem.asBytes(&key)}),
- .Float => |info| hash(hasher, @bitCast(std.meta.Int(false, info.bits), key), strat),
-
.Bool => hash(hasher, @boolToInt(key), strat),
.Enum => hash(hasher, @enumToInt(key), strat),
.ErrorSet => hash(hasher, @errorToInt(key), strat),
- .AnyFrame, .Fn => hash(hasher, @ptrToInt(key), strat),
+ .AnyFrame, .BoundFn, .Fn => hash(hasher, @ptrToInt(key), strat),
.Pointer => @call(.{ .modifier = .always_inline }, hashPointer, .{ hasher, key, strat }),
@@ -121,9 +122,6 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
},
.Struct => |info| {
- // TODO detect via a trait when Key has no padding bits to
- // hash it as an array of bytes.
- // Otherwise, hash every field.
inline for (info.fields) |field| {
// We reuse the hash of the previous field as the seed for the
// next one so that they're dependant.
@@ -266,12 +264,12 @@ test "hash slice deep" {
test "hash struct deep" {
const Foo = struct {
a: u32,
- b: f64,
+ b: u16,
c: *bool,
const Self = @This();
- pub fn init(allocator: *mem.Allocator, a_: u32, b_: f64, c_: bool) !Self {
+ pub fn init(allocator: *mem.Allocator, a_: u32, b_: u16, c_: bool) !Self {
const ptr = try allocator.create(bool);
ptr.* = c_;
return Self{ .a = a_, .b = b_, .c = ptr };
@@ -279,9 +277,9 @@ test "hash struct deep" {
};
const allocator = std.testing.allocator;
- const foo = try Foo.init(allocator, 123, 1.0, true);
- const bar = try Foo.init(allocator, 123, 1.0, true);
- const baz = try Foo.init(allocator, 123, 1.0, false);
+ const foo = try Foo.init(allocator, 123, 10, true);
+ const bar = try Foo.init(allocator, 123, 10, true);
+ const baz = try Foo.init(allocator, 123, 10, false);
defer allocator.destroy(foo.c);
defer allocator.destroy(bar.c);
defer allocator.destroy(baz.c);
@@ -338,12 +336,12 @@ test "testHash struct" {
test "testHash union" {
const Foo = union(enum) {
A: u32,
- B: f32,
+ B: bool,
C: u32,
};
const a = Foo{ .A = 18 };
- var b = Foo{ .B = 12.34 };
+ var b = Foo{ .B = true };
const c = Foo{ .C = 18 };
testing.expect(testHash(a) == testHash(a));
testing.expect(testHash(a) != testHash(b));
diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig
@@ -5,6 +5,7 @@ const testing = std.testing;
const math = std.math;
const mem = std.mem;
const meta = std.meta;
+const trait = meta.trait;
const autoHash = std.hash.autoHash;
const Wyhash = std.hash.Wyhash;
const Allocator = mem.Allocator;
@@ -195,6 +196,10 @@ pub fn HashMap(
return self.unmanaged.getEntry(key);
}
+ pub fn getIndex(self: Self, key: K) ?usize {
+ return self.unmanaged.getIndex(key);
+ }
+
pub fn get(self: Self, key: K) ?V {
return self.unmanaged.get(key);
}
@@ -478,17 +483,21 @@ pub fn HashMapUnmanaged(
}
pub fn getEntry(self: Self, key: K) ?*Entry {
+ const index = self.getIndex(key) orelse return null;
+ return &self.entries.items[index];
+ }
+
+ pub fn getIndex(self: Self, key: K) ?usize {
const header = self.index_header orelse {
// Linear scan.
const h = if (store_hash) hash(key) else {};
- for (self.entries.items) |*item| {
+ for (self.entries.items) |*item, i| {
if (item.hash == h and eql(key, item.key)) {
- return item;
+ return i;
}
}
return null;
};
-
switch (header.capacityIndexType()) {
.u8 => return self.getInternal(key, header, u8),
.u16 => return self.getInternal(key, header, u16),
@@ -710,7 +719,7 @@ pub fn HashMapUnmanaged(
unreachable;
}
- fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry {
+ fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?usize {
const indexes = header.indexes(I);
const h = hash(key);
const start_index = header.constrainIndex(h);
@@ -724,7 +733,7 @@ pub fn HashMapUnmanaged(
const entry = &self.entries.items[index.entry_index];
const hash_match = if (store_hash) h == entry.hash else true;
if (hash_match and eql(key, entry.key))
- return entry;
+ return index.entry_index;
}
return null;
}
@@ -1023,9 +1032,13 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) {
pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
return struct {
fn hash(key: K) u32 {
- var hasher = Wyhash.init(0);
- autoHash(&hasher, key);
- return @truncate(u32, hasher.final());
+ if (comptime trait.hasUniqueRepresentation(K)) {
+ return @truncate(u32, Wyhash.hash(0, std.mem.asBytes(&key)));
+ } else {
+ var hasher = Wyhash.init(0);
+ autoHash(&hasher, key);
+ return @truncate(u32, hasher.final());
+ }
}
}.hash;
}
diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig
@@ -99,7 +99,7 @@ pub const Mutable = struct {
pub fn toManaged(self: Mutable, allocator: *Allocator) Managed {
return .{
.allocator = allocator,
- .limbs = limbs,
+ .limbs = self.limbs,
.metadata = if (self.positive)
self.len & ~Managed.sign_bit
else
diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig
@@ -2,6 +2,7 @@ const std = @import("../../std.zig");
const mem = std.mem;
const testing = std.testing;
const Managed = std.math.big.int.Managed;
+const Mutable = std.math.big.int.Mutable;
const Limb = std.math.big.Limb;
const DoubleLimb = std.math.big.DoubleLimb;
const maxInt = std.math.maxInt;
@@ -1453,3 +1454,24 @@ test "big.int gcd one large" {
testing.expect((try r.to(u64)) == 1);
}
+
+test "big.int mutable to managed" {
+ const allocator = testing.allocator;
+ var limbs_buf = try allocator.alloc(Limb, 8);
+ defer allocator.free(limbs_buf);
+
+ var a = Mutable.init(limbs_buf, 0xdeadbeef);
+ var a_managed = a.toManaged(allocator);
+
+ testing.expect(a.toConst().eq(a_managed.toConst()));
+}
+
+test "big.int const to managed" {
+ var a = try Managed.initSet(testing.allocator, 123423453456);
+ defer a.deinit();
+
+ var b = try a.toConst().toManaged(testing.allocator);
+ defer b.deinit();
+
+ testing.expect(a.toConst().eq(b.toConst()));
+}
diff --git a/lib/std/mem.zig b/lib/std/mem.zig
@@ -2030,6 +2030,79 @@ test "rotate" {
testing.expect(eql(i32, &arr, &[_]i32{ 1, 2, 4, 5, 3 }));
}
+/// Replace needle with replacement as many times as possible, writing to an output buffer which is assumed to be of
+/// appropriate size. Use replacementSize to calculate an appropriate buffer size.
+pub fn replace(comptime T: type, input: []const T, needle: []const T, replacement: []const T, output: []T) usize {
+ var i: usize = 0;
+ var slide: usize = 0;
+ var replacements: usize = 0;
+ while (slide < input.len) {
+ if (mem.indexOf(T, input[slide..], needle) == @as(usize, 0)) {
+ mem.copy(T, output[i..i + replacement.len], replacement);
+ i += replacement.len;
+ slide += needle.len;
+ replacements += 1;
+ } else {
+ output[i] = input[slide];
+ i += 1;
+ slide += 1;
+ }
+ }
+
+ return replacements;
+}
+
+test "replace" {
+ var output: [29]u8 = undefined;
+ var replacements = replace(u8, "All your base are belong to us", "base", "Zig", output[0..]);
+ testing.expect(replacements == 1);
+ testing.expect(eql(u8, output[0..], "All your Zig are belong to us"));
+
+ replacements = replace(u8, "Favor reading code over writing code.", "code", "", output[0..]);
+ testing.expect(replacements == 2);
+ testing.expect(eql(u8, output[0..], "Favor reading over writing ."));
+}
+
+/// Calculate the size needed in an output buffer to perform a replacement.
+pub fn replacementSize(comptime T: type, input: []const T, needle: []const T, replacement: []const T) usize {
+ var i: usize = 0;
+ var size: usize = input.len;
+ while (i < input.len) : (i += 1) {
+ if (mem.indexOf(T, input[i..], needle) == @as(usize, 0)) {
+ size = size - needle.len + replacement.len;
+ i += needle.len;
+ }
+ }
+
+ return size;
+}
+
+test "replacementSize" {
+ testing.expect(replacementSize(u8, "All your base are belong to us", "base", "Zig") == 29);
+ testing.expect(replacementSize(u8, "", "", "") == 0);
+ testing.expect(replacementSize(u8, "Favor reading code over writing code.", "code", "") == 29);
+ testing.expect(replacementSize(u8, "Only one obvious way to do things.", "things.", "things in Zig.") == 41);
+}
+
+/// Perform a replacement on an allocated buffer of pre-determined size. Caller must free returned memory.
+pub fn replaceOwned(comptime T: type, allocator: *Allocator, input: []const T, needle: []const T, replacement: []const T) Allocator.Error![]T {
+ var output = try allocator.alloc(T, replacementSize(T, input, needle, replacement));
+ _ = replace(T, input, needle, replacement, output);
+ return output;
+}
+
+test "replaceOwned" {
+ const allocator = std.heap.page_allocator;
+
+ const base_replace = replaceOwned(u8, allocator, "All your base are belong to us", "base", "Zig") catch unreachable;
+ defer allocator.free(base_replace);
+ testing.expect(eql(u8, base_replace, "All your Zig are belong to us"));
+
+ const zen_replace = replaceOwned(u8, allocator, "Favor reading code over writing code.", " code", "") catch unreachable;
+ defer allocator.free(zen_replace);
+ testing.expect(eql(u8, zen_replace, "Favor reading over writing."));
+}
+
/// Converts a little-endian integer to host endianness.
pub fn littleToNative(comptime T: type, x: T) T {
return switch (builtin.endian) {
diff --git a/lib/std/meta/trait.zig b/lib/std/meta/trait.zig
@@ -429,3 +429,71 @@ test "std.meta.trait.hasFunctions" {
testing.expect(!hasFunctions(TestStruct2, .{ "a", "b", "c" }));
testing.expect(!hasFunctions(TestStruct2, tuple));
}
+
+/// True if every value of the type `T` has a unique bit pattern representing it.
+/// In other words, `T` has no unused bits and no padding.
+pub fn hasUniqueRepresentation(comptime T: type) bool {
+ switch (@typeInfo(T)) {
+ else => return false, // TODO can we know if it's true for some of these types ?
+
+ .AnyFrame,
+ .Bool,
+ .BoundFn,
+ .Enum,
+ .ErrorSet,
+ .Fn,
+ .Int, // TODO check that it is still true
+ .Pointer,
+ => return true,
+
+ .Array => |info| return comptime hasUniqueRepresentation(info.child),
+
+ .Struct => |info| {
+ var sum_size = @as(usize, 0);
+
+ inline for (info.fields) |field| {
+ const FieldType = field.field_type;
+ if (comptime !hasUniqueRepresentation(FieldType)) return false;
+ sum_size += @sizeOf(FieldType);
+ }
+
+ return @sizeOf(T) == sum_size;
+ },
+
+ .Vector => |info| return comptime hasUniqueRepresentation(info.child),
+ }
+}
+
+test "std.meta.trait.hasUniqueRepresentation" {
+ const TestStruct1 = struct {
+ a: u32,
+ b: u32,
+ };
+
+ testing.expect(hasUniqueRepresentation(TestStruct1));
+
+ const TestStruct2 = struct {
+ a: u32,
+ b: u16,
+ };
+
+ testing.expect(!hasUniqueRepresentation(TestStruct2));
+
+ const TestStruct3 = struct {
+ a: u32,
+ b: u32,
+ };
+
+ testing.expect(hasUniqueRepresentation(TestStruct3));
+
+ testing.expect(hasUniqueRepresentation(i1));
+ testing.expect(hasUniqueRepresentation(u2));
+ testing.expect(hasUniqueRepresentation(i3));
+ testing.expect(hasUniqueRepresentation(u4));
+ testing.expect(hasUniqueRepresentation(i5));
+ testing.expect(hasUniqueRepresentation(u6));
+ testing.expect(hasUniqueRepresentation(i7));
+ testing.expect(hasUniqueRepresentation(u8));
+ testing.expect(hasUniqueRepresentation(i9));
+ testing.expect(hasUniqueRepresentation(u10));
+}
diff --git a/lib/std/net.zig b/lib/std/net.zig
@@ -14,8 +14,8 @@ const has_unix_sockets = @hasDecl(os, "sockaddr_un");
pub const Address = extern union {
any: os.sockaddr,
- in: os.sockaddr_in,
- in6: os.sockaddr_in6,
+ in: Ip4Address,
+ in6: Ip6Address,
un: if (has_unix_sockets) os.sockaddr_un else void,
// TODO this crashed the compiler. https://github.com/ziglang/zig/issues/3512
@@ -76,19 +76,227 @@ pub const Address = extern union {
}
}
+ pub fn parseIp6(buf: []const u8, port: u16) !Address {
+ return Address{.in6 = try Ip6Address.parse(buf, port) };
+ }
+
+ pub fn resolveIp6(buf: []const u8, port: u16) !Address {
+ return Address{.in6 = try Ip6Address.resolve(buf, port) };
+ }
+
+ pub fn parseIp4(buf: []const u8, port: u16) !Address {
+ return Address {.in = try Ip4Address.parse(buf, port) };
+ }
+
+ pub fn initIp4(addr: [4]u8, port: u16) Address {
+ return Address{.in = Ip4Address.init(addr, port) };
+ }
+
+ pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address {
+ return Address{.in6 = Ip6Address.init(addr, port, flowinfo, scope_id) };
+ }
+
+ pub fn initUnix(path: []const u8) !Address {
+ var sock_addr = os.sockaddr_un{
+ .family = os.AF_UNIX,
+ .path = undefined,
+ };
+
+ // this enables us to have the proper length of the socket in getOsSockLen
+ mem.set(u8, &sock_addr.path, 0);
+
+ if (path.len > sock_addr.path.len) return error.NameTooLong;
+ mem.copy(u8, &sock_addr.path, path);
+
+ return Address{ .un = sock_addr };
+ }
+
+ /// Returns the port in native endian.
+ /// Asserts that the address is ip4 or ip6.
+ pub fn getPort(self: Address) u16 {
+ return switch (self.any.family) {
+ os.AF_INET => self.in.getPort(),
+ os.AF_INET6 => self.in6.getPort(),
+ else => unreachable,
+ };
+ }
+
+ /// `port` is native-endian.
+ /// Asserts that the address is ip4 or ip6.
+ pub fn setPort(self: *Address, port: u16) void {
+ switch (self.any.family) {
+ os.AF_INET => self.in.setPort(port),
+ os.AF_INET6 => self.in6.setPort(port),
+ else => unreachable,
+ }
+ }
+
+ /// Asserts that `addr` is an IP address.
+ /// This function will read past the end of the pointer, with a size depending
+ /// on the address family.
+ pub fn initPosix(addr: *align(4) const os.sockaddr) Address {
+ switch (addr.family) {
+ os.AF_INET => return Address{ .in = Ip4Address{ .sa = @ptrCast(*const os.sockaddr_in, addr).*} },
+ os.AF_INET6 => return Address{ .in6 = Ip6Address{ .sa = @ptrCast(*const os.sockaddr_in6, addr).*} },
+ else => unreachable,
+ }
+ }
+
+ pub fn format(
+ self: Address,
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ out_stream: anytype,
+ ) !void {
+ switch (self.any.family) {
+ os.AF_INET => try self.in.format(fmt, options, out_stream),
+ os.AF_INET6 => try self.in6.format(fmt, options, out_stream),
+ os.AF_UNIX => {
+ if (!has_unix_sockets) {
+ unreachable;
+ }
+
+ try std.fmt.format(out_stream, "{}", .{&self.un.path});
+ },
+ else => unreachable,
+ }
+ }
+
+ pub fn eql(a: Address, b: Address) bool {
+ const a_bytes = @ptrCast([*]const u8, &a.any)[0..a.getOsSockLen()];
+ const b_bytes = @ptrCast([*]const u8, &b.any)[0..b.getOsSockLen()];
+ return mem.eql(u8, a_bytes, b_bytes);
+ }
+
+ pub fn getOsSockLen(self: Address) os.socklen_t {
+ switch (self.any.family) {
+ os.AF_INET => return self.in.getOsSockLen(),
+ os.AF_INET6 => return self.in6.getOsSockLen(),
+ os.AF_UNIX => {
+ if (!has_unix_sockets) {
+ unreachable;
+ }
+
+ const path_len = std.mem.len(@ptrCast([*:0]const u8, &self.un.path));
+ return @intCast(os.socklen_t, @sizeOf(os.sockaddr_un) - self.un.path.len + path_len);
+ },
+ else => unreachable,
+ }
+ }
+};
+
+pub const Ip4Address = extern struct {
+ sa: os.sockaddr_in,
+
+ pub fn parse(buf: []const u8, port: u16) !Ip4Address {
+ var result = Ip4Address{
+ .sa = .{
+ .port = mem.nativeToBig(u16, port),
+ .addr = undefined,
+ }
+ };
+ const out_ptr = mem.sliceAsBytes(@as(*[1]u32, &result.sa.addr)[0..]);
+
+ var x: u8 = 0;
+ var index: u8 = 0;
+ var saw_any_digits = false;
+ for (buf) |c| {
+ if (c == '.') {
+ if (!saw_any_digits) {
+ return error.InvalidCharacter;
+ }
+ if (index == 3) {
+ return error.InvalidEnd;
+ }
+ out_ptr[index] = x;
+ index += 1;
+ x = 0;
+ saw_any_digits = false;
+ } else if (c >= '0' and c <= '9') {
+ saw_any_digits = true;
+ x = try std.math.mul(u8, x, 10);
+ x = try std.math.add(u8, x, c - '0');
+ } else {
+ return error.InvalidCharacter;
+ }
+ }
+ if (index == 3 and saw_any_digits) {
+ out_ptr[index] = x;
+ return result;
+ }
+
+ return error.Incomplete;
+ }
+
+ pub fn resolveIp(name: []const u8, port: u16) !Ip4Address {
+ if (parse(name, port)) |ip4| return ip4 else |err| switch (err) {
+ error.Overflow,
+ error.InvalidEnd,
+ error.InvalidCharacter,
+ error.Incomplete,
+ => {},
+ }
+ return error.InvalidIPAddressFormat;
+ }
+
+ pub fn init(addr: [4]u8, port: u16) Ip4Address {
+ return Ip4Address {
+ .sa = os.sockaddr_in{
+ .port = mem.nativeToBig(u16, port),
+ .addr = @ptrCast(*align(1) const u32, &addr).*,
+ },
+ };
+ }
+
+ /// Returns the port in native endian.
+ /// Asserts that the address is ip4 or ip6.
+ pub fn getPort(self: Ip4Address) u16 {
+ return mem.bigToNative(u16, self.sa.port);
+ }
+
+ /// `port` is native-endian.
+ /// Asserts that the address is ip4 or ip6.
+ pub fn setPort(self: *Ip4Address, port: u16) void {
+ self.sa.port = mem.nativeToBig(u16, port);
+ }
+
+ pub fn format(
+ self: Ip4Address,
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ out_stream: anytype,
+ ) !void {
+ const bytes = @ptrCast(*const [4]u8, &self.sa.addr);
+ try std.fmt.format(out_stream, "{}.{}.{}.{}:{}", .{
+ bytes[0],
+ bytes[1],
+ bytes[2],
+ bytes[3],
+ self.getPort(),
+ });
+ }
+
+ pub fn getOsSockLen(self: Ip4Address) os.socklen_t {
+ return @sizeOf(os.sockaddr_in);
+ }
+};
+
+pub const Ip6Address = extern struct {
+ sa: os.sockaddr_in6,
+
/// Parse a given IPv6 address string into an Address.
/// Assumes the Scope ID of the address is fully numeric.
/// For non-numeric addresses, see `resolveIp6`.
- pub fn parseIp6(buf: []const u8, port: u16) !Address {
- var result = Address{
- .in6 = os.sockaddr_in6{
+ pub fn parse(buf: []const u8, port: u16) !Ip6Address {
+ var result = Ip6Address{
+ .sa = os.sockaddr_in6{
.scope_id = 0,
.port = mem.nativeToBig(u16, port),
.flowinfo = 0,
.addr = undefined,
},
};
- var ip_slice = result.in6.addr[0..];
+ var ip_slice = result.sa.addr[0..];
var tail: [16]u8 = undefined;
@@ -101,10 +309,10 @@ pub const Address = extern union {
if (scope_id) {
if (c >= '0' and c <= '9') {
const digit = c - '0';
- if (@mulWithOverflow(u32, result.in6.scope_id, 10, &result.in6.scope_id)) {
+ if (@mulWithOverflow(u32, result.sa.scope_id, 10, &result.sa.scope_id)) {
return error.Overflow;
}
- if (@addWithOverflow(u32, result.in6.scope_id, digit, &result.in6.scope_id)) {
+ if (@addWithOverflow(u32, result.sa.scope_id, digit, &result.sa.scope_id)) {
return error.Overflow;
}
} else {
@@ -141,10 +349,10 @@ pub const Address = extern union {
return error.InvalidIpv4Mapping;
}
const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
- const addr = (parseIp4(buf[start_index..], 0) catch {
+ const addr = (Ip4Address.parse(buf[start_index..], 0) catch {
return error.InvalidIpv4Mapping;
- }).in.addr;
- ip_slice = result.in6.addr[0..];
+ }).sa.addr;
+ ip_slice = result.sa.addr[0..];
ip_slice[10] = 0xff;
ip_slice[11] = 0xff;
@@ -180,22 +388,22 @@ pub const Address = extern union {
index += 1;
ip_slice[index] = @truncate(u8, x);
index += 1;
- mem.copy(u8, result.in6.addr[16 - index ..], ip_slice[0..index]);
+ mem.copy(u8, result.sa.addr[16 - index ..], ip_slice[0..index]);
return result;
}
}
- pub fn resolveIp6(buf: []const u8, port: u16) !Address {
+ pub fn resolve(buf: []const u8, port: u16) !Ip6Address {
// TODO: Unify the implementations of resolveIp6 and parseIp6.
- var result = Address{
- .in6 = os.sockaddr_in6{
+ var result = Ip6Address{
+ .sa = os.sockaddr_in6{
.scope_id = 0,
.port = mem.nativeToBig(u16, port),
.flowinfo = 0,
.addr = undefined,
},
};
- var ip_slice = result.in6.addr[0..];
+ var ip_slice = result.sa.addr[0..];
var tail: [16]u8 = undefined;
@@ -256,10 +464,10 @@ pub const Address = extern union {
return error.InvalidIpv4Mapping;
}
const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
- const addr = (parseIp4(buf[start_index..], 0) catch {
+ const addr = (Ip4Address.parse(buf[start_index..], 0) catch {
return error.InvalidIpv4Mapping;
- }).in.addr;
- ip_slice = result.in6.addr[0..];
+ }).sa.addr;
+ ip_slice = result.sa.addr[0..];
ip_slice[10] = 0xff;
ip_slice[11] = 0xff;
@@ -299,7 +507,7 @@ pub const Address = extern union {
};
}
- result.in6.scope_id = resolved_scope_id;
+ result.sa.scope_id = resolved_scope_id;
if (index == 14) {
ip_slice[14] = @truncate(u8, x >> 8);
@@ -310,63 +518,14 @@ pub const Address = extern union {
index += 1;
ip_slice[index] = @truncate(u8, x);
index += 1;
- mem.copy(u8, result.in6.addr[16 - index ..], ip_slice[0..index]);
- return result;
- }
- }
-
- pub fn parseIp4(buf: []const u8, port: u16) !Address {
- var result = Address{
- .in = os.sockaddr_in{
- .port = mem.nativeToBig(u16, port),
- .addr = undefined,
- },
- };
- const out_ptr = mem.sliceAsBytes(@as(*[1]u32, &result.in.addr)[0..]);
-
- var x: u8 = 0;
- var index: u8 = 0;
- var saw_any_digits = false;
- for (buf) |c| {
- if (c == '.') {
- if (!saw_any_digits) {
- return error.InvalidCharacter;
- }
- if (index == 3) {
- return error.InvalidEnd;
- }
- out_ptr[index] = x;
- index += 1;
- x = 0;
- saw_any_digits = false;
- } else if (c >= '0' and c <= '9') {
- saw_any_digits = true;
- x = try std.math.mul(u8, x, 10);
- x = try std.math.add(u8, x, c - '0');
- } else {
- return error.InvalidCharacter;
- }
- }
- if (index == 3 and saw_any_digits) {
- out_ptr[index] = x;
+ mem.copy(u8, result.sa.addr[16 - index ..], ip_slice[0..index]);
return result;
}
-
- return error.Incomplete;
}
- pub fn initIp4(addr: [4]u8, port: u16) Address {
- return Address{
- .in = os.sockaddr_in{
- .port = mem.nativeToBig(u16, port),
- .addr = @ptrCast(*align(1) const u32, &addr).*,
- },
- };
- }
-
- pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address {
- return Address{
- .in6 = os.sockaddr_in6{
+ pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address {
+ return Ip6Address{
+ .sa = os.sockaddr_in6{
.addr = addr,
.port = mem.nativeToBig(u16, port),
.flowinfo = flowinfo,
@@ -375,147 +534,71 @@ pub const Address = extern union {
};
}
- pub fn initUnix(path: []const u8) !Address {
- var sock_addr = os.sockaddr_un{
- .family = os.AF_UNIX,
- .path = undefined,
- };
-
- // this enables us to have the proper length of the socket in getOsSockLen
- mem.set(u8, &sock_addr.path, 0);
-
- if (path.len > sock_addr.path.len) return error.NameTooLong;
- mem.copy(u8, &sock_addr.path, path);
-
- return Address{ .un = sock_addr };
- }
-
/// Returns the port in native endian.
/// Asserts that the address is ip4 or ip6.
- pub fn getPort(self: Address) u16 {
- const big_endian_port = switch (self.any.family) {
- os.AF_INET => self.in.port,
- os.AF_INET6 => self.in6.port,
- else => unreachable,
- };
- return mem.bigToNative(u16, big_endian_port);
+ pub fn getPort(self: Ip6Address) u16 {
+ return mem.bigToNative(u16, self.sa.port);
}
/// `port` is native-endian.
/// Asserts that the address is ip4 or ip6.
- pub fn setPort(self: *Address, port: u16) void {
- const ptr = switch (self.any.family) {
- os.AF_INET => &self.in.port,
- os.AF_INET6 => &self.in6.port,
- else => unreachable,
- };
- ptr.* = mem.nativeToBig(u16, port);
- }
-
- /// Asserts that `addr` is an IP address.
- /// This function will read past the end of the pointer, with a size depending
- /// on the address family.
- pub fn initPosix(addr: *align(4) const os.sockaddr) Address {
- switch (addr.family) {
- os.AF_INET => return Address{ .in = @ptrCast(*const os.sockaddr_in, addr).* },
- os.AF_INET6 => return Address{ .in6 = @ptrCast(*const os.sockaddr_in6, addr).* },
- else => unreachable,
- }
+ pub fn setPort(self: *Ip6Address, port: u16) void {
+ self.sa.port = mem.nativeToBig(u16, port);
}
pub fn format(
- self: Address,
+ self: Ip6Address,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
out_stream: anytype,
) !void {
- switch (self.any.family) {
- os.AF_INET => {
- const port = mem.bigToNative(u16, self.in.port);
- const bytes = @ptrCast(*const [4]u8, &self.in.addr);
- try std.fmt.format(out_stream, "{}.{}.{}.{}:{}", .{
- bytes[0],
- bytes[1],
- bytes[2],
- bytes[3],
- port,
- });
- },
- os.AF_INET6 => {
- const port = mem.bigToNative(u16, self.in6.port);
- if (mem.eql(u8, self.in6.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
- try std.fmt.format(out_stream, "[::ffff:{}.{}.{}.{}]:{}", .{
- self.in6.addr[12],
- self.in6.addr[13],
- self.in6.addr[14],
- self.in6.addr[15],
- port,
- });
- return;
- }
- const big_endian_parts = @ptrCast(*align(1) const [8]u16, &self.in6.addr);
- const native_endian_parts = switch (builtin.endian) {
- .Big => big_endian_parts.*,
- .Little => blk: {
- var buf: [8]u16 = undefined;
- for (big_endian_parts) |part, i| {
- buf[i] = mem.bigToNative(u16, part);
- }
- break :blk buf;
- },
- };
- try out_stream.writeAll("[");
- var i: usize = 0;
- var abbrv = false;
- while (i < native_endian_parts.len) : (i += 1) {
- if (native_endian_parts[i] == 0) {
- if (!abbrv) {
- try out_stream.writeAll(if (i == 0) "::" else ":");
- abbrv = true;
- }
- continue;
- }
- try std.fmt.format(out_stream, "{x}", .{native_endian_parts[i]});
- if (i != native_endian_parts.len - 1) {
- try out_stream.writeAll(":");
- }
+ const port = mem.bigToNative(u16, self.sa.port);
+ if (mem.eql(u8, self.sa.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
+ try std.fmt.format(out_stream, "[::ffff:{}.{}.{}.{}]:{}", .{
+ self.sa.addr[12],
+ self.sa.addr[13],
+ self.sa.addr[14],
+ self.sa.addr[15],
+ port,
+ });
+ return;
+ }
+ const big_endian_parts = @ptrCast(*align(1) const [8]u16, &self.sa.addr);
+ const native_endian_parts = switch (builtin.endian) {
+ .Big => big_endian_parts.*,
+ .Little => blk: {
+ var buf: [8]u16 = undefined;
+ for (big_endian_parts) |part, i| {
+ buf[i] = mem.bigToNative(u16, part);
}
- try std.fmt.format(out_stream, "]:{}", .{port});
+ break :blk buf;
},
- os.AF_UNIX => {
- if (!has_unix_sockets) {
- unreachable;
+ };
+ try out_stream.writeAll("[");
+ var i: usize = 0;
+ var abbrv = false;
+ while (i < native_endian_parts.len) : (i += 1) {
+ if (native_endian_parts[i] == 0) {
+ if (!abbrv) {
+ try out_stream.writeAll(if (i == 0) "::" else ":");
+ abbrv = true;
}
-
- try std.fmt.format(out_stream, "{}", .{&self.un.path});
- },
- else => unreachable,
+ continue;
+ }
+ try std.fmt.format(out_stream, "{x}", .{native_endian_parts[i]});
+ if (i != native_endian_parts.len - 1) {
+ try out_stream.writeAll(":");
+ }
}
+ try std.fmt.format(out_stream, "]:{}", .{port});
}
- pub fn eql(a: Address, b: Address) bool {
- const a_bytes = @ptrCast([*]const u8, &a.any)[0..a.getOsSockLen()];
- const b_bytes = @ptrCast([*]const u8, &b.any)[0..b.getOsSockLen()];
- return mem.eql(u8, a_bytes, b_bytes);
- }
-
- pub fn getOsSockLen(self: Address) os.socklen_t {
- switch (self.any.family) {
- os.AF_INET => return @sizeOf(os.sockaddr_in),
- os.AF_INET6 => return @sizeOf(os.sockaddr_in6),
- os.AF_UNIX => {
- if (!has_unix_sockets) {
- unreachable;
- }
-
- const path_len = std.mem.len(@ptrCast([*:0]const u8, &self.un.path));
- return @intCast(os.socklen_t, @sizeOf(os.sockaddr_un) - self.un.path.len + path_len);
- },
- else => unreachable,
- }
+ pub fn getOsSockLen(self: Ip6Address) os.socklen_t {
+ return @sizeOf(os.sockaddr_in6);
}
};
+
pub fn connectUnixSocket(path: []const u8) !fs.File {
const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
const sockfd = try os.socket(
@@ -777,7 +860,7 @@ fn linuxLookupName(
@memset(@ptrCast([*]u8, &sa6), 0, @sizeOf(os.sockaddr_in6));
var da6 = os.sockaddr_in6{
.family = os.AF_INET6,
- .scope_id = addr.addr.in6.scope_id,
+ .scope_id = addr.addr.in6.sa.scope_id,
.port = 65535,
.flowinfo = 0,
.addr = [1]u8{0} ** 16,
@@ -795,7 +878,7 @@ fn linuxLookupName(
var salen: os.socklen_t = undefined;
var dalen: os.socklen_t = undefined;
if (addr.addr.any.family == os.AF_INET6) {
- mem.copy(u8, &da6.addr, &addr.addr.in6.addr);
+ mem.copy(u8, &da6.addr, &addr.addr.in6.sa.addr);
da = @ptrCast(*os.sockaddr, &da6);
dalen = @sizeOf(os.sockaddr_in6);
sa = @ptrCast(*os.sockaddr, &sa6);
@@ -803,8 +886,8 @@ fn linuxLookupName(
} else {
mem.copy(u8, &sa6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff");
mem.copy(u8, &da6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff");
- mem.writeIntNative(u32, da6.addr[12..], addr.addr.in.addr);
- da4.addr = addr.addr.in.addr;
+ mem.writeIntNative(u32, da6.addr[12..], addr.addr.in.sa.addr);
+ da4.addr = addr.addr.in.sa.addr;
da = @ptrCast(*os.sockaddr, &da4);
dalen = @sizeOf(os.sockaddr_in);
sa = @ptrCast(*os.sockaddr, &sa4);
diff --git a/lib/std/os.zig b/lib/std/os.zig
@@ -1041,6 +1041,9 @@ pub const OpenError = error{
/// The underlying filesystem does not support file locks
FileLocksNotSupported,
+
+ BadPathName,
+ InvalidUtf8,
} || UnexpectedError;
/// Open and possibly create a file. Keeps trying if it gets interrupted.
@@ -1092,18 +1095,65 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
}
}
+fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
+ const w = windows;
+
+ var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
+ if (flags & O_RDWR != 0) {
+ access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
+ } else if (flags & O_WRONLY != 0) {
+ access_mask |= w.GENERIC_WRITE;
+ } else {
+ access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
+ }
+
+ const open_dir: bool = flags & O_DIRECTORY != 0;
+ const follow_symlinks: bool = flags & O_NOFOLLOW == 0;
+
+ const creation: w.ULONG = blk: {
+ if (flags & O_CREAT != 0) {
+ if (flags & O_EXCL != 0) {
+ break :blk w.FILE_CREATE;
+ }
+ }
+ break :blk w.FILE_OPEN;
+ };
+
+ return .{
+ .access_mask = access_mask,
+ .io_mode = .blocking,
+ .creation = creation,
+ .open_dir = open_dir,
+ .follow_symlinks = follow_symlinks,
+ };
+}
+
/// Windows-only. The path parameter is
/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
/// Translates the POSIX open API call to a Windows API call.
-pub fn openW(file_path_w: []const u16, flags: u32, perm: usize) OpenError!fd_t {
- @compileError("TODO implement openW for windows");
+/// TODO currently, this function does not handle all flag combinations
+/// or makes use of perm argument.
+pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {
+ var options = openOptionsFromFlags(flags);
+ options.dir = std.fs.cwd().fd;
+ return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
+ error.WouldBlock => unreachable,
+ error.PipeBusy => unreachable,
+ else => |e| return e,
+ };
}
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openatC`.
-/// TODO support windows
pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
+ if (builtin.os.tag == .wasi) {
+ @compileError("use openatWasi instead");
+ }
+ if (builtin.os.tag == .windows) {
+ const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+ return openatW(dir_fd, file_path_w.span(), flags, mode);
+ }
const file_path_c = try toPosixPath(file_path);
return openatZ(dir_fd, &file_path_c, flags, mode);
}
@@ -1145,8 +1195,11 @@ pub const openatC = @compileError("deprecated: renamed to openatZ");
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openat`.
-/// TODO support windows
pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
+ if (builtin.os.tag == .windows) {
+ const file_path_w = try windows.cStrToPrefixedFileW(file_path);
+ return openatW(dir_fd, file_path_w.span(), flags, mode);
+ }
while (true) {
const rc = system.openat(dir_fd, file_path, flags, mode);
switch (errno(rc)) {
@@ -1177,6 +1230,20 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
}
}
+/// Windows-only. Similar to `openat` but with pathname argument null-terminated
+/// WTF16 encoded.
+/// TODO currently, this function does not handle all flag combinations
+/// or makes use of perm argument.
+pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {
+ var options = openOptionsFromFlags(flags);
+ options.dir = dir_fd;
+ return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
+ error.WouldBlock => unreachable,
+ error.PipeBusy => unreachable,
+ else => |e| return e,
+ };
+}
+
pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
while (true) {
switch (errno(system.dup2(old_fd, new_fd))) {
@@ -1683,7 +1750,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
@compileError("unlink is not supported in WASI; use unlinkat instead");
} else if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
- return windows.DeleteFileW(file_path_w.span().ptr);
+ return unlinkW(file_path_w.span());
} else {
const file_path_c = try toPosixPath(file_path);
return unlinkZ(&file_path_c);
@@ -1696,7 +1763,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ");
pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
- return windows.DeleteFileW(file_path_w.span().ptr);
+ return unlinkW(file_path_w.span());
}
switch (errno(system.unlink(file_path))) {
0 => return,
@@ -1717,6 +1784,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
}
}
+/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.
+pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
+ return windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd });
+}
+
pub const UnlinkatError = UnlinkError || error{
/// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty.
DirNotEmpty,
@@ -1727,7 +1799,7 @@ pub const UnlinkatError = UnlinkError || error{
pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
- return unlinkatW(dirfd, file_path_w.span().ptr, flags);
+ return unlinkatW(dirfd, file_path_w.span(), flags);
} else if (builtin.os.tag == .wasi) {
return unlinkatWasi(dirfd, file_path, flags);
} else {
@@ -1774,7 +1846,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
- return unlinkatW(dirfd, file_path_w.span().ptr, flags);
+ return unlinkatW(dirfd, file_path_w.span(), flags);
}
switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
0 => return,
@@ -1800,67 +1872,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
}
/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
-pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatError!void {
- const w = windows;
-
- const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
- const create_options_flags = if (want_rmdir_behavior)
- @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT)
- else
- @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT); // would we ever want to delete the target instead?
-
- const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);
- var nt_name = w.UNICODE_STRING{
- .Length = path_len_bytes,
- .MaximumLength = path_len_bytes,
- // The Windows API makes this mutable, but it will not mutate here.
- .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
- };
-
- if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
- // Windows does not recognize this, but it does work with empty string.
- nt_name.Length = 0;
- }
- if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
- // Can't remove the parent directory with an open handle.
- return error.FileBusy;
- }
-
- var attr = w.OBJECT_ATTRIBUTES{
- .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
- .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- };
- var io: w.IO_STATUS_BLOCK = undefined;
- var tmp_handle: w.HANDLE = undefined;
- var rc = w.ntdll.NtCreateFile(
- &tmp_handle,
- w.SYNCHRONIZE | w.DELETE,
- &attr,
- &io,
- null,
- 0,
- w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
- w.FILE_OPEN,
- create_options_flags,
- null,
- 0,
- );
- if (rc == .SUCCESS) {
- rc = w.ntdll.NtClose(tmp_handle);
- }
- switch (rc) {
- .SUCCESS => return,
- .OBJECT_NAME_INVALID => unreachable,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .INVALID_PARAMETER => unreachable,
- .FILE_IS_A_DIRECTORY => return error.IsDir,
- .NOT_A_DIRECTORY => return error.NotDir,
- else => return w.unexpectedStatus(rc),
- }
+pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
+ const remove_dir = (flags & AT_REMOVEDIR) != 0;
+ return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
}
const RenameError = error{
@@ -2087,7 +2101,7 @@ pub fn renameatW(
pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
- return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
+ return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
} else if (builtin.os.tag == .wasi) {
return mkdiratWasi(dir_fd, sub_dir_path, mode);
} else {
@@ -2145,8 +2159,19 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
}
}
-pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void {
- const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null);
+pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
+ const sub_dir_handle = windows.OpenFile(sub_path_w, .{
+ .dir = dir_fd,
+ .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
+ .creation = windows.FILE_CREATE,
+ .io_mode = .blocking,
+ .open_dir = true,
+ }) catch |err| switch (err) {
+ error.IsDir => unreachable,
+ error.PipeBusy => unreachable,
+ error.WouldBlock => unreachable,
+ else => |e| return e,
+ };
windows.CloseHandle(sub_dir_handle);
}
@@ -2175,9 +2200,8 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .wasi) {
@compileError("mkdir is not supported in WASI; use mkdirat instead");
} else if (builtin.os.tag == .windows) {
- const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
- windows.CloseHandle(sub_dir_handle);
- return;
+ const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
+ return mkdirW(dir_path_w.span(), mode);
} else {
const dir_path_c = try toPosixPath(dir_path);
return mkdirZ(&dir_path_c, mode);
@@ -2188,9 +2212,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
- const sub_dir_handle = try windows.CreateDirectoryW(null, dir_path_w.span().ptr, null);
- windows.CloseHandle(sub_dir_handle);
- return;
+ return mkdirW(dir_path_w.span(), mode);
}
switch (errno(system.mkdir(dir_path, mode))) {
0 => return,
@@ -2211,6 +2233,23 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
}
}
+/// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded.
+pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
+ const sub_dir_handle = windows.OpenFile(dir_path_w, .{
+ .dir = std.fs.cwd().fd,
+ .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
+ .creation = windows.FILE_CREATE,
+ .io_mode = .blocking,
+ .open_dir = true,
+ }) catch |err| switch (err) {
+ error.IsDir => unreachable,
+ error.PipeBusy => unreachable,
+ error.WouldBlock => unreachable,
+ else => |e| return e,
+ };
+ windows.CloseHandle(sub_dir_handle);
+}
+
pub const DeleteDirError = error{
AccessDenied,
FileBusy,
@@ -2231,7 +2270,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
@compileError("rmdir is not supported in WASI; use unlinkat instead");
} else if (builtin.os.tag == .windows) {
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
- return windows.RemoveDirectoryW(dir_path_w.span().ptr);
+ return rmdirW(dir_path_w.span());
} else {
const dir_path_c = try toPosixPath(dir_path);
return rmdirZ(&dir_path_c);
@@ -2244,7 +2283,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ");
pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
- return windows.RemoveDirectoryW(dir_path_w.span().ptr);
+ return rmdirW(dir_path_w.span());
}
switch (errno(system.rmdir(dir_path))) {
0 => return,
@@ -2265,6 +2304,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
}
}
+/// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded.
+pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
+ return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
+ error.IsDir => unreachable,
+ else => |e| return e,
+ };
+}
+
pub const ChangeCurDirError = error{
AccessDenied,
FileSystem,
@@ -2354,7 +2401,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .wasi) {
@compileError("readlink is not supported in WASI; use readlinkat instead");
} else if (builtin.os.tag == .windows) {
- return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
+ const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+ return readlinkW(file_path_w.span(), out_buffer);
} else {
const file_path_c = try toPosixPath(file_path);
return readlinkZ(&file_path_c, out_buffer);
@@ -2363,17 +2411,17 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
-/// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded.
+/// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded.
/// See also `readlinkZ`.
-pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
- return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer);
+pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+ return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
}
/// Same as `readlink` except `file_path` is null-terminated.
pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
- return readlinkW(file_path_w.span().ptr, out_buffer);
+ return readlinkW(file_path_w.span(), out_buffer);
}
const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
switch (errno(rc)) {
@@ -2399,7 +2447,8 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink
return readlinkatWasi(dirfd, file_path, out_buffer);
}
if (builtin.os.tag == .windows) {
- return windows.ReadLink(dirfd, file_path, out_buffer);
+ const file_path_w = try windows.sliceToPrefixedFileW(file_path);
+ return readlinkatW(dirfd, file_path_w.span(), out_buffer);
}
const file_path_c = try toPosixPath(file_path);
return readlinkatZ(dirfd, &file_path_c, out_buffer);
@@ -2429,8 +2478,8 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.
/// See also `readlinkat`.
-pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
- return windows.ReadLinkW(dirfd, file_path, out_buffer);
+pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+ return windows.ReadLink(dirfd, file_path, out_buffer);
}
/// Same as `readlinkat` except `file_path` is null-terminated.
@@ -2438,7 +2487,7 @@ pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) Rea
pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
- return readlinkatW(dirfd, file_path_w.span().ptr, out_buffer);
+ return readlinkatW(dirfd, file_path_w.span(), out_buffer);
}
const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
switch (errno(rc)) {
@@ -3959,7 +4008,7 @@ pub const RealPathError = error{
pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
if (builtin.os.tag == .windows) {
const pathname_w = try windows.sliceToPrefixedFileW(pathname);
- return realpathW(pathname_w.span().ptr, out_buffer);
+ return realpathW(pathname_w.span(), out_buffer);
}
if (builtin.os.tag == .wasi) {
@compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths");
@@ -3974,7 +4023,7 @@ pub const realpathC = @compileError("deprecated: renamed realpathZ");
pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
if (builtin.os.tag == .windows) {
const pathname_w = try windows.cStrToPrefixedFileW(pathname);
- return realpathW(pathname_w.span().ptr, out_buffer);
+ return realpathW(pathname_w.span(), out_buffer);
}
if (builtin.os.tag == .linux and !builtin.link_libc) {
const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) {
@@ -4010,22 +4059,43 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
return mem.spanZ(result_path);
}
-/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
-/// TODO use ntdll for better semantics
-pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
- const h_file = try windows.CreateFileW(
- pathname,
- windows.GENERIC_READ,
- windows.FILE_SHARE_READ,
- null,
- windows.OPEN_EXISTING,
- windows.FILE_FLAG_BACKUP_SEMANTICS,
- null,
- );
- defer windows.CloseHandle(h_file);
+/// Same as `realpath` except `pathname` is UTF16LE-encoded.
+/// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine
+pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
+ const w = windows;
+
+ const dir = std.fs.cwd().fd;
+ const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
+ const share_access = w.FILE_SHARE_READ;
+ const creation = w.FILE_OPEN;
+ const h_file = blk: {
+ const res = w.OpenFile(pathname, .{
+ .dir = dir,
+ .access_mask = access_mask,
+ .share_access = share_access,
+ .creation = creation,
+ .io_mode = .blocking,
+ }) catch |err| switch (err) {
+ error.IsDir => break :blk w.OpenFile(pathname, .{
+ .dir = dir,
+ .access_mask = access_mask,
+ .share_access = share_access,
+ .creation = creation,
+ .io_mode = .blocking,
+ .open_dir = true,
+ }) catch |er| switch (er) {
+ error.WouldBlock => unreachable,
+ else => |e2| return e2,
+ },
+ error.WouldBlock => unreachable,
+ else => |e| return e,
+ };
+ break :blk res;
+ };
+ defer w.CloseHandle(h_file);
- var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
- const wide_slice = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS);
+ var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
+ const wide_slice = try w.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, w.VOLUME_NAME_DOS);
// Windows returns \\?\ prepended to the path.
// We strip it to make this function consistent across platforms.
diff --git a/lib/std/os/bits/windows.zig b/lib/std/os/bits/windows.zig
@@ -237,3 +237,28 @@ pub const IPPROTO_TCP = ws2_32.IPPROTO_TCP;
pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP;
pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6;
pub const IPPROTO_RM = ws2_32.IPPROTO_RM;
+
+pub const O_RDONLY = 0o0;
+pub const O_WRONLY = 0o1;
+pub const O_RDWR = 0o2;
+
+pub const O_CREAT = 0o100;
+pub const O_EXCL = 0o200;
+pub const O_NOCTTY = 0o400;
+pub const O_TRUNC = 0o1000;
+pub const O_APPEND = 0o2000;
+pub const O_NONBLOCK = 0o4000;
+pub const O_DSYNC = 0o10000;
+pub const O_SYNC = 0o4010000;
+pub const O_RSYNC = 0o4010000;
+pub const O_DIRECTORY = 0o200000;
+pub const O_NOFOLLOW = 0o400000;
+pub const O_CLOEXEC = 0o2000000;
+
+pub const O_ASYNC = 0o20000;
+pub const O_DIRECT = 0o40000;
+pub const O_LARGEFILE = 0;
+pub const O_NOATIME = 0o1000000;
+pub const O_PATH = 0o10000000;
+pub const O_TMPFILE = 0o20200000;
+pub const O_NDELAY = O_NONBLOCK;
+\ No newline at end of file
diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig
@@ -3,6 +3,7 @@ const os = std.os;
const testing = std.testing;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
+const expectError = testing.expectError;
const io = std.io;
const fs = std.fs;
const mem = std.mem;
@@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir;
const Dir = std.fs.Dir;
const ArenaAllocator = std.heap.ArenaAllocator;
+test "open smoke test" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+ // TODO verify file attributes using `fstat`
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ // Get base abs path
+ var arena = ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+
+ const base_path = blk: {
+ const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
+ break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
+ };
+
+ var file_path: []u8 = undefined;
+ var fd: os.fd_t = undefined;
+ const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666;
+
+ // Create some file using `open`.
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
+ fd = try os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode);
+ os.close(fd);
+
+ // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
+ expectError(error.PathAlreadyExists, os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode));
+
+ // Try opening without `O_EXCL` flag.
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
+ fd = try os.open(file_path, os.O_RDWR | os.O_CREAT, mode);
+ os.close(fd);
+
+ // Try opening as a directory which should fail.
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
+ expectError(error.NotDir, os.open(file_path, os.O_RDWR | os.O_DIRECTORY, mode));
+
+ // Create some directory
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
+ try os.mkdir(file_path, mode);
+
+ // Open dir using `open`
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
+ fd = try os.open(file_path, os.O_RDONLY | os.O_DIRECTORY, mode);
+ os.close(fd);
+
+ // Try opening as file which should fail.
+ file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
+ expectError(error.IsDir, os.open(file_path, os.O_RDWR, mode));
+}
+
+test "openat smoke test" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+ // TODO verify file attributes using `fstatat`
+
+ var tmp = tmpDir(.{});
+ defer tmp.cleanup();
+
+ var fd: os.fd_t = undefined;
+ const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666;
+
+ // Create some file using `openat`.
+ fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode);
+ os.close(fd);
+
+ // Try this again with the same flags. This op should fail with error.PathAlreadyExists.
+ expectError(error.PathAlreadyExists, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode));
+
+ // Try opening without `O_EXCL` flag.
+ fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT, mode);
+ os.close(fd);
+
+ // Try opening as a directory which should fail.
+ expectError(error.NotDir, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_DIRECTORY, mode));
+
+ // Create some directory
+ try os.mkdirat(tmp.dir.fd, "some_dir", mode);
+
+ // Open dir using `open`
+ fd = try os.openat(tmp.dir.fd, "some_dir", os.O_RDONLY | os.O_DIRECTORY, mode);
+ os.close(fd);
+
+ // Try opening as file which should fail.
+ expectError(error.IsDir, os.openat(tmp.dir.fd, "some_dir", os.O_RDWR, mode));
+}
+
test "symlink with relative paths" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
@@ -27,7 +117,7 @@ test "symlink with relative paths" {
try cwd.writeFile("file.txt", "nonsense");
if (builtin.os.tag == .windows) {
- try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false);
+ try os.windows.CreateSymbolicLink(cwd.fd, &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false);
} else {
try os.symlink("file.txt", "symlinked");
}
@@ -85,7 +175,7 @@ test "readlinkat" {
// create a symbolic link
if (builtin.os.tag == .windows) {
- try os.windows.CreateSymbolicLink(tmp.dir.fd, "link", "file.txt", false);
+ try os.windows.CreateSymbolicLink(tmp.dir.fd, &[_]u16{ 'l', 'i', 'n', 'k' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false);
} else {
try os.symlinkat("file.txt", tmp.dir.fd, "link");
}
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
@@ -25,76 +25,11 @@ pub usingnamespace @import("windows/bits.zig");
pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
-pub const CreateFileError = error{
- SharingViolation,
- PathAlreadyExists,
-
- /// When any of the path components can not be found or the file component can not
- /// be found. Some operating systems distinguish between path components not found and
- /// file components not found, but they are collapsed into FileNotFound to gain
- /// consistency across operating systems.
- FileNotFound,
-
- AccessDenied,
- PipeBusy,
- NameTooLong,
-
- /// On Windows, file paths must be valid Unicode.
- InvalidUtf8,
-
- /// On Windows, file paths cannot contain these characters:
- /// '/', '*', '?', '"', '<', '>', '|'
- BadPathName,
-
- Unexpected,
-};
-
-pub fn CreateFile(
- file_path: []const u8,
- desired_access: DWORD,
- share_mode: DWORD,
- lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
- creation_disposition: DWORD,
- flags_and_attrs: DWORD,
- hTemplateFile: ?HANDLE,
-) CreateFileError!HANDLE {
- const file_path_w = try sliceToPrefixedFileW(file_path);
- return CreateFileW(file_path_w.span().ptr, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile);
-}
-
-pub fn CreateFileW(
- file_path_w: [*:0]const u16,
- desired_access: DWORD,
- share_mode: DWORD,
- lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
- creation_disposition: DWORD,
- flags_and_attrs: DWORD,
- hTemplateFile: ?HANDLE,
-) CreateFileError!HANDLE {
- const result = kernel32.CreateFileW(file_path_w, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile);
-
- if (result == INVALID_HANDLE_VALUE) {
- switch (kernel32.GetLastError()) {
- .SHARING_VIOLATION => return error.SharingViolation,
- .ALREADY_EXISTS => return error.PathAlreadyExists,
- .FILE_EXISTS => return error.PathAlreadyExists,
- .FILE_NOT_FOUND => return error.FileNotFound,
- .PATH_NOT_FOUND => return error.FileNotFound,
- .ACCESS_DENIED => return error.AccessDenied,
- .PIPE_BUSY => return error.PipeBusy,
- .FILENAME_EXCED_RANGE => return error.NameTooLong,
- else => |err| return unexpectedError(err),
- }
- }
-
- return result;
-}
-
pub const OpenError = error{
IsDir,
+ NotDir,
FileNotFound,
NoDevice,
- SharingViolation,
AccessDenied,
PipeBusy,
PathAlreadyExists,
@@ -111,15 +46,21 @@ pub const OpenFileOptions = struct {
share_access_nonblocking: bool = false,
creation: ULONG,
io_mode: std.io.ModeOverride,
+ /// If true, tries to open path as a directory.
+ /// Defaults to false.
+ open_dir: bool = false,
+ /// If false, tries to open path as a reparse point without dereferencing it.
+ /// Defaults to true.
+ follow_symlinks: bool = true,
};
/// TODO when share_access_nonblocking is false, this implementation uses
/// untinterruptible sleep() to block. This is not the final iteration of the API.
pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {
- if (mem.eql(u16, sub_path_w, &[_]u16{'.'})) {
+ if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and !options.open_dir) {
return error.IsDir;
}
- if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' })) {
+ if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.open_dir) {
return error.IsDir;
}
@@ -142,11 +83,13 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
+ const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
+ const file_or_dir_flag: ULONG = if (options.open_dir) FILE_DIRECTORY_FILE else FILE_NON_DIRECTORY_FILE;
+ // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
+ const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT;
var delay: usize = 1;
while (true) {
- var flags: ULONG = undefined;
- const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
const rc = ntdll.NtCreateFile(
&result,
options.access_mask,
@@ -156,7 +99,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
FILE_ATTRIBUTE_NORMAL,
options.share_access,
options.creation,
- FILE_NON_DIRECTORY_FILE | blocking_flag,
+ flags,
null,
0,
);
@@ -184,6 +127,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
.OBJECT_PATH_SYNTAX_BAD => unreachable,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
.FILE_IS_A_DIRECTORY => return error.IsDir,
+ .NOT_A_DIRECTORY => return error.NotDir,
else => return unexpectedStatus(rc),
}
}
@@ -215,30 +159,61 @@ pub fn CreateEventExW(attributes: ?*SECURITY_ATTRIBUTES, nameW: [*:0]const u16,
}
}
+pub const DeviceIoControlError = error{Unexpected};
+
+/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
+/// It implements similar behavior to `DeviceIoControl` and is meant to serve
+/// as a direct substitute for that call.
+/// TODO work out if we need to expose other arguments to the underlying syscalls.
pub fn DeviceIoControl(
h: HANDLE,
- ioControlCode: DWORD,
+ ioControlCode: ULONG,
in: ?[]const u8,
out: ?[]u8,
- overlapped: ?*OVERLAPPED,
-) !DWORD {
- var bytes: DWORD = undefined;
- if (kernel32.DeviceIoControl(
- h,
- ioControlCode,
- if (in) |i| i.ptr else null,
- if (in) |i| @intCast(u32, i.len) else 0,
- if (out) |o| o.ptr else null,
- if (out) |o| @intCast(u32, o.len) else 0,
- &bytes,
- overlapped,
- ) == 0) {
- switch (kernel32.GetLastError()) {
- .IO_PENDING => if (overlapped == null) unreachable,
- else => |err| return unexpectedError(err),
+) DeviceIoControlError!void {
+ // Logic from: https://doxygen.reactos.org/d3/d74/deviceio_8c.html
+ const is_fsctl = (ioControlCode >> 16) == FILE_DEVICE_FILE_SYSTEM;
+
+ var io: IO_STATUS_BLOCK = undefined;
+ const in_ptr = if (in) |i| i.ptr else null;
+ const in_len = if (in) |i| @intCast(ULONG, i.len) else 0;
+ const out_ptr = if (out) |o| o.ptr else null;
+ const out_len = if (out) |o| @intCast(ULONG, o.len) else 0;
+
+ const rc = blk: {
+ if (is_fsctl) {
+ break :blk ntdll.NtFsControlFile(
+ h,
+ null,
+ null,
+ null,
+ &io,
+ ioControlCode,
+ in_ptr,
+ in_len,
+ out_ptr,
+ out_len,
+ );
+ } else {
+ break :blk ntdll.NtDeviceIoControlFile(
+ h,
+ null,
+ null,
+ null,
+ &io,
+ ioControlCode,
+ in_ptr,
+ in_len,
+ out_ptr,
+ out_len,
+ );
}
+ };
+ switch (rc) {
+ .SUCCESS => {},
+ .INVALID_PARAMETER => unreachable,
+ else => return unexpectedStatus(rc),
}
- return bytes;
}
pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWORD {
@@ -607,27 +582,14 @@ pub const CreateSymbolicLinkError = error{
PathAlreadyExists,
FileNotFound,
NameTooLong,
- InvalidUtf8,
- BadPathName,
NoDevice,
Unexpected,
};
pub fn CreateSymbolicLink(
dir: ?HANDLE,
- sym_link_path: []const u8,
- target_path: []const u8,
- is_directory: bool,
-) CreateSymbolicLinkError!void {
- const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);
- const target_path_w = try sliceToPrefixedFileW(target_path);
- return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory);
-}
-
-pub fn CreateSymbolicLinkW(
- dir: ?HANDLE,
- sym_link_path: [:0]const u16,
- target_path: [:0]const u16,
+ sym_link_path: []const u16,
+ target_path: []const u16,
is_directory: bool,
) CreateSymbolicLinkError!void {
const SYMLINK_DATA = extern struct {
@@ -641,71 +603,19 @@ pub fn CreateSymbolicLinkW(
Flags: ULONG,
};
- var symlink_handle: HANDLE = undefined;
- if (is_directory) {
- const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) {
- error.Overflow => return error.NameTooLong,
- };
- var nt_name = UNICODE_STRING{
- .Length = sym_link_len_bytes,
- .MaximumLength = sym_link_len_bytes,
- .Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)),
- };
-
- if (sym_link_path[0] == '.' and sym_link_path[1] == 0) {
- // Windows does not recognize this, but it does work with empty string.
- nt_name.Length = 0;
- }
-
- var attr = OBJECT_ATTRIBUTES{
- .Length = @sizeOf(OBJECT_ATTRIBUTES),
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sym_link_path)) null else dir,
- .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
- .ObjectName = &nt_name,
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- };
-
- var io: IO_STATUS_BLOCK = undefined;
- const rc = ntdll.NtCreateFile(
- &symlink_handle,
- GENERIC_READ | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES,
- &attr,
- &io,
- null,
- FILE_ATTRIBUTE_NORMAL,
- FILE_SHARE_READ,
- FILE_CREATE,
- FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT,
- null,
- 0,
- );
- switch (rc) {
- .SUCCESS => {},
- .OBJECT_NAME_INVALID => unreachable,
- .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NO_MEDIA_IN_DEVICE => return error.NoDevice,
- .INVALID_PARAMETER => unreachable,
- .ACCESS_DENIED => return error.AccessDenied,
- .OBJECT_PATH_SYNTAX_BAD => unreachable,
- .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
- else => return unexpectedStatus(rc),
- }
- } else {
- symlink_handle = OpenFile(sym_link_path, .{
- .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
- .dir = dir,
- .creation = FILE_CREATE,
- .io_mode = .blocking,
- }) catch |err| switch (err) {
- error.WouldBlock => unreachable,
- error.IsDir => return error.PathAlreadyExists,
- error.PipeBusy => unreachable,
- error.SharingViolation => return error.AccessDenied,
- else => |e| return e,
- };
- }
+ const symlink_handle = OpenFile(sym_link_path, .{
+ .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
+ .dir = dir,
+ .creation = FILE_CREATE,
+ .io_mode = .blocking,
+ .open_dir = is_directory,
+ }) catch |err| switch (err) {
+ error.IsDir => return error.PathAlreadyExists,
+ error.NotDir => unreachable,
+ error.WouldBlock => unreachable,
+ error.PipeBusy => unreachable,
+ else => |e| return e,
+ };
defer CloseHandle(symlink_handle);
// prepare reparse data buffer
@@ -727,8 +637,7 @@ pub fn CreateSymbolicLinkW(
@memcpy(buffer[@sizeOf(SYMLINK_DATA)..], @ptrCast([*]const u8, target_path), target_path.len * 2);
const paths_start = @sizeOf(SYMLINK_DATA) + target_path.len * 2;
@memcpy(buffer[paths_start..].ptr, @ptrCast([*]const u8, target_path), target_path.len * 2);
- // TODO replace with NtDeviceIoControl
- _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null);
+ _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null);
}
pub const ReadLinkError = error{
@@ -737,44 +646,32 @@ pub const ReadLinkError = error{
Unexpected,
NameTooLong,
UnsupportedReparsePointType,
- InvalidUtf8,
- BadPathName,
};
-pub fn ReadLink(
- dir: ?HANDLE,
- sub_path: []const u8,
- out_buffer: []u8,
-) ReadLinkError![]u8 {
- const sub_path_w = try sliceToPrefixedFileW(sub_path);
- return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer);
-}
-
-pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
- const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
+pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
+ // Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper.
+ // With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that
+ // failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible
+ // to open the symlink there and then.
+ const path_len_bytes = math.cast(u16, sub_path_w.len * 2) catch |err| switch (err) {
error.Overflow => return error.NameTooLong,
};
var nt_name = UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
- .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
+ .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
};
-
- if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
- // Windows does not recognize this, but it does work with empty string.
- nt_name.Length = 0;
- }
-
var attr = OBJECT_ATTRIBUTES{
.Length = @sizeOf(OBJECT_ATTRIBUTES),
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
+ .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else dir,
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
- var io: IO_STATUS_BLOCK = undefined;
var result_handle: HANDLE = undefined;
+ var io: IO_STATUS_BLOCK = undefined;
+
const rc = ntdll.NtCreateFile(
&result_handle,
FILE_READ_ATTRIBUTES,
@@ -806,7 +703,7 @@ pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) Rea
defer CloseHandle(result_handle);
var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
- _ = try DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null);
+ _ = try DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]);
const reparse_struct = @ptrCast(*const REPARSE_DATA_BUFFER, @alignCast(@alignOf(REPARSE_DATA_BUFFER), &reparse_buf[0]));
switch (reparse_struct.ReparseTag) {
@@ -848,135 +745,83 @@ pub const DeleteFileError = error{
NameTooLong,
FileBusy,
Unexpected,
+ NotDir,
+ IsDir,
};
-pub fn DeleteFile(filename: []const u8) DeleteFileError!void {
- const filename_w = try sliceToPrefixedFileW(filename);
- return DeleteFileW(filename_w.span().ptr);
-}
-
-pub fn DeleteFileW(filename: [*:0]const u16) DeleteFileError!void {
- if (kernel32.DeleteFileW(filename) == 0) {
- switch (kernel32.GetLastError()) {
- .FILE_NOT_FOUND => return error.FileNotFound,
- .PATH_NOT_FOUND => return error.FileNotFound,
- .ACCESS_DENIED => return error.AccessDenied,
- .FILENAME_EXCED_RANGE => return error.NameTooLong,
- .INVALID_PARAMETER => return error.NameTooLong,
- .SHARING_VIOLATION => return error.FileBusy,
- else => |err| return unexpectedError(err),
- }
- }
-}
-
-pub const MoveFileError = error{Unexpected};
-
-pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void {
- const old_path_w = try sliceToPrefixedFileW(old_path);
- const new_path_w = try sliceToPrefixedFileW(new_path);
- return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags);
-}
-
-pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void {
- if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) {
- switch (kernel32.GetLastError()) {
- else => |err| return unexpectedError(err),
- }
- }
-}
-
-pub const CreateDirectoryError = error{
- NameTooLong,
- PathAlreadyExists,
- FileNotFound,
- NoDevice,
- AccessDenied,
- InvalidUtf8,
- BadPathName,
- Unexpected,
+pub const DeleteFileOptions = struct {
+ dir: ?HANDLE,
+ remove_dir: bool = false,
};
-/// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`.
-pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE {
- const pathname_w = try sliceToPrefixedFileW(pathname);
- return CreateDirectoryW(dir, pathname_w.span().ptr, sa);
-}
+pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void {
+ const create_options_flags: ULONG = if (options.remove_dir)
+ FILE_DELETE_ON_CLOSE | FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT
+ else
+ FILE_DELETE_ON_CLOSE | FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead?
-/// Same as `CreateDirectory` except takes a WTF-16 encoded path.
-pub fn CreateDirectoryW(
- dir: ?HANDLE,
- sub_path_w: [*:0]const u16,
- sa: ?*SECURITY_ATTRIBUTES,
-) CreateDirectoryError!HANDLE {
- const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
- error.Overflow => return error.NameTooLong,
- };
+ const path_len_bytes = @intCast(u16, sub_path_w.len * 2);
var nt_name = UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
- .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
+ // The Windows API makes this mutable, but it will not mutate here.
+ .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
};
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
// Windows does not recognize this, but it does work with empty string.
nt_name.Length = 0;
}
+ if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
+ // Can't remove the parent directory with an open handle.
+ return error.FileBusy;
+ }
var attr = OBJECT_ATTRIBUTES{
.Length = @sizeOf(OBJECT_ATTRIBUTES),
- .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
+ .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir,
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
- .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null,
+ .SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
- var result_handle: HANDLE = undefined;
- const rc = ntdll.NtCreateFile(
- &result_handle,
- GENERIC_READ | SYNCHRONIZE,
+ var tmp_handle: HANDLE = undefined;
+ var rc = ntdll.NtCreateFile(
+ &tmp_handle,
+ SYNCHRONIZE | DELETE,
&attr,
&io,
null,
- FILE_ATTRIBUTE_NORMAL,
- FILE_SHARE_READ,
- FILE_CREATE,
- FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ FILE_OPEN,
+ create_options_flags,
null,
0,
);
switch (rc) {
- .SUCCESS => return result_handle,
+ .SUCCESS => return CloseHandle(tmp_handle),
.OBJECT_NAME_INVALID => unreachable,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
- .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NO_MEDIA_IN_DEVICE => return error.NoDevice,
.INVALID_PARAMETER => unreachable,
- .ACCESS_DENIED => return error.AccessDenied,
- .OBJECT_PATH_SYNTAX_BAD => unreachable,
- .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
+ .FILE_IS_A_DIRECTORY => return error.IsDir,
+ .NOT_A_DIRECTORY => return error.NotDir,
else => return unexpectedStatus(rc),
}
}
-pub const RemoveDirectoryError = error{
- FileNotFound,
- DirNotEmpty,
- Unexpected,
- NotDir,
-};
+pub const MoveFileError = error{Unexpected};
-pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void {
- const dir_path_w = try sliceToPrefixedFileW(dir_path);
- return RemoveDirectoryW(dir_path_w.span().ptr);
+pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void {
+ const old_path_w = try sliceToPrefixedFileW(old_path);
+ const new_path_w = try sliceToPrefixedFileW(new_path);
+ return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags);
}
-pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void {
- if (kernel32.RemoveDirectoryW(dir_path_w) == 0) {
+pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void {
+ if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) {
switch (kernel32.GetLastError()) {
- .PATH_NOT_FOUND => return error.FileNotFound,
- .DIR_NOT_EMPTY => return error.DirNotEmpty,
- .DIRECTORY => return error.NotDir,
else => |err| return unexpectedError(err),
}
}
@@ -1463,8 +1308,7 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace {
}
/// Converts the path `s` to WTF16, null-terminated. If the path is absolute,
-/// it will get NT-style prefix `\??\` prepended automatically. For prepending
-/// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead.
+/// it will get NT-style prefix `\??\` prepended automatically.
pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {
// TODO https://github.com/ziglang/zig/issues/2765
var path_space: PathSpace = undefined;
diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig
@@ -54,6 +54,18 @@ pub extern "NtDll" fn NtDeviceIoControlFile(
OutputBuffer: ?PVOID,
OutputBufferLength: ULONG,
) callconv(.Stdcall) NTSTATUS;
+pub extern "NtDll" fn NtFsControlFile(
+ FileHandle: HANDLE,
+ Event: ?HANDLE,
+ ApcRoutine: ?IO_APC_ROUTINE,
+ ApcContext: ?*c_void,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+ FsControlCode: ULONG,
+ InputBuffer: ?*const c_void,
+ InputBufferLength: ULONG,
+ OutputBuffer: ?PVOID,
+ OutputBufferLength: ULONG,
+) callconv(.Stdcall) NTSTATUS;
pub extern "NtDll" fn NtClose(Handle: HANDLE) callconv(.Stdcall) NTSTATUS;
pub extern "NtDll" fn RtlDosPathNameToNtPathName_U(
DosPathName: [*:0]const u16,
diff --git a/lib/std/special/compiler_rt.zig b/lib/std/special/compiler_rt.zig
@@ -92,6 +92,7 @@ comptime {
@export(@import("compiler_rt/floatunsidf.zig").__floatunsidf, .{ .name = "__floatunsidf", .linkage = linkage });
@export(@import("compiler_rt/floatundidf.zig").__floatundidf, .{ .name = "__floatundidf", .linkage = linkage });
+ @export(@import("compiler_rt/floatditf.zig").__floatditf, .{ .name = "__floatditf", .linkage = linkage });
@export(@import("compiler_rt/floattitf.zig").__floattitf, .{ .name = "__floattitf", .linkage = linkage });
@export(@import("compiler_rt/floattidf.zig").__floattidf, .{ .name = "__floattidf", .linkage = linkage });
@export(@import("compiler_rt/floattisf.zig").__floattisf, .{ .name = "__floattisf", .linkage = linkage });
diff --git a/lib/std/special/compiler_rt/floatditf.zig b/lib/std/special/compiler_rt/floatditf.zig
@@ -0,0 +1,38 @@
+const builtin = @import("builtin");
+const is_test = builtin.is_test;
+const std = @import("std");
+const maxInt = std.math.maxInt;
+
+const significandBits = 112;
+const exponentBias = 16383;
+const implicitBit = (@as(u128, 1) << significandBits);
+
+pub fn __floatditf(arg: i64) callconv(.C) f128 {
+ @setRuntimeSafety(is_test);
+
+ if (arg == 0)
+ return 0.0;
+
+ // All other cases begin by extracting the sign and absolute value of a
+ var sign: u128 = 0;
+ var aAbs = @bitCast(u64, arg);
+ if (arg < 0) {
+ sign = 1 << 127;
+ aAbs = ~@bitCast(u64, arg)+ 1;
+ }
+
+ // Exponent of (fp_t)a is the width of abs(a).
+ const exponent = 63 - @clz(u64, aAbs);
+ var result: u128 = undefined;
+
+ // Shift a into the significand field, rounding if it is a right-shift
+ const shift = significandBits - exponent;
+ result = @as(u128, aAbs) << shift ^ implicitBit;
+
+ result += (@as(u128, exponent) + exponentBias) << significandBits;
+ return @bitCast(f128, result | sign);
+}
+
+test "import floatditf" {
+ _ = @import("floatditf_test.zig");
+}
diff --git a/lib/std/special/compiler_rt/floatditf_test.zig b/lib/std/special/compiler_rt/floatditf_test.zig
@@ -0,0 +1,26 @@
+const __floatditf = @import("floatditf.zig").__floatditf;
+const testing = @import("std").testing;
+
+fn test__floatditf(a: i64, expected: f128) void {
+ const x = __floatditf(a);
+ testing.expect(x == expected);
+}
+
+test "floatditf" {
+ test__floatditf(0x7fffffffffffffff, make_ti(0x403dffffffffffff, 0xfffc000000000000));
+ test__floatditf(0x123456789abcdef1, make_ti(0x403b23456789abcd, 0xef10000000000000));
+ test__floatditf(0x2, make_ti(0x4000000000000000, 0x0));
+ test__floatditf(0x1, make_ti(0x3fff000000000000, 0x0));
+ test__floatditf(0x0, make_ti(0x0, 0x0));
+ test__floatditf(@bitCast(i64, @as(u64, 0xffffffffffffffff)), make_ti(0xbfff000000000000, 0x0));
+ test__floatditf(@bitCast(i64, @as(u64, 0xfffffffffffffffe)), make_ti(0xc000000000000000, 0x0));
+ test__floatditf(-0x123456789abcdef1, make_ti(0xc03b23456789abcd, 0xef10000000000000));
+ test__floatditf(@bitCast(i64, @as(u64, 0x8000000000000000)), make_ti(0xc03e000000000000, 0x0));
+}
+
+fn make_ti(high: u64, low: u64) f128 {
+ var result: u128 = high;
+ result <<= 64;
+ result |= low;
+ return @bitCast(f128, result);
+}
diff --git a/lib/std/testing.zig b/lib/std/testing.zig
@@ -171,6 +171,59 @@ test "expectEqual.union(enum)" {
expectEqual(a10, a10);
}
+/// This function is intended to be used only in tests. When the actual value is not
+/// within the margin of the expected value,
+/// prints diagnostics to stderr to show exactly how they are not equal, then aborts.
+/// The types must be floating point
+pub fn expectWithinMargin(expected: anytype, actual: @TypeOf(expected), margin: @TypeOf(expected)) void {
+ std.debug.assert(margin >= 0.0);
+
+ switch (@typeInfo(@TypeOf(actual))) {
+ .Float,
+ .ComptimeFloat,
+ => {
+ if (@fabs(expected - actual) > margin) {
+ std.debug.panic("actual {}, not within margin {} of expected {}", .{ actual, margin, expected });
+ }
+ },
+ else => @compileError("Unable to compare non floating point values"),
+ }
+}
+
+test "expectWithinMargin.f32" {
+ const x: f32 = 12.0;
+ const y: f32 = 12.06;
+
+ expectWithinMargin(x, y, 0.1);
+}
+
+/// This function is intended to be used only in tests. When the actual value is not
+/// within the epsilon of the expected value,
+/// prints diagnostics to stderr to show exactly how they are not equal, then aborts.
+/// The types must be floating point
+pub fn expectWithinEpsilon(expected: anytype, actual: @TypeOf(expected), epsilon: @TypeOf(expected)) void {
+ std.debug.assert(epsilon >= 0.0 and epsilon <= 1.0);
+
+ const margin = epsilon * expected;
+ switch (@typeInfo(@TypeOf(actual))) {
+ .Float,
+ .ComptimeFloat,
+ => {
+ if (@fabs(expected - actual) > margin) {
+ std.debug.panic("actual {}, not within epsilon {}, of expected {}", .{ actual, epsilon, expected });
+ }
+ },
+ else => @compileError("Unable to compare non floating point values"),
+ }
+}
+
+test "expectWithinEpsilon.f32" {
+ const x: f32 = 12.0;
+ const y: f32 = 13.2;
+
+ expectWithinEpsilon(x, y, 0.1);
+}
+
/// This function is intended to be used only in tests. When the two slices are not
/// equal, prints diagnostics to stderr to show exactly how they are not equal,
/// then aborts.
diff --git a/lib/std/zig.zig b/lib/std/zig.zig
@@ -43,6 +43,22 @@ pub fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usi
return .{ .line = line, .column = column };
}
+pub fn lineDelta(source: []const u8, start: usize, end: usize) isize {
+ var line: isize = 0;
+ if (end >= start) {
+ for (source[start..end]) |byte| switch (byte) {
+ '\n' => line += 1,
+ else => continue,
+ };
+ } else {
+ for (source[end..start]) |byte| switch (byte) {
+ '\n' => line -= 1,
+ else => continue,
+ };
+ }
+ return line;
+}
+
/// Returns the standard file system basename of a binary generated by the Zig compiler.
pub fn binNameAlloc(
allocator: *std.mem.Allocator,
diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig
@@ -1299,6 +1299,10 @@ pub const Node = struct {
});
}
+ pub fn body(self: *const FnProto) ?*Node {
+ return self.getTrailer("body_node");
+ }
+
pub fn getTrailer(self: *const FnProto, comptime name: []const u8) ?TrailerFlags.Field(name) {
const trailers_start = @alignCast(
@alignOf(ParamDecl),
@@ -1381,7 +1385,7 @@ pub const Node = struct {
.Invalid => {},
}
- if (self.getTrailer("body_node")) |body_node| {
+ if (self.body()) |body_node| {
if (i < 1) return body_node;
i -= 1;
}
@@ -1397,7 +1401,7 @@ pub const Node = struct {
}
pub fn lastToken(self: *const FnProto) TokenIndex {
- if (self.getTrailer("body_node")) |body_node| return body_node.lastToken();
+ if (self.body()) |body_node| return body_node.lastToken();
switch (self.return_type) {
.Explicit, .InferErrorSet => |node| return node.lastToken(),
.Invalid => |tok| return tok,
diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig
@@ -201,7 +201,16 @@ const Parser = struct {
p.findNextContainerMember();
const next = p.token_ids[p.tok_i];
switch (next) {
- .Eof => break,
+ .Eof => {
+ // no invalid tokens were found
+ if (index == p.tok_i) break;
+
+ // Invalid tokens, add error and exit
+ try p.errors.append(p.gpa, .{
+ .ExpectedToken = .{ .token = index, .expected_id = .Comma },
+ });
+ break;
+ },
else => {
if (next == .RBrace) {
if (!top_level) break;
diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig
@@ -293,6 +293,14 @@ test "zig fmt: decl between fields" {
});
}
+test "zig fmt: eof after missing comma" {
+ try testError(
+ \\foo()
+ , &[_]Error{
+ .ExpectedToken,
+ });
+}
+
test "zig fmt: errdefer with payload" {
try testCanonical(
\\pub fn main() anyerror!void {
diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig
@@ -104,7 +104,6 @@ pub fn parse(
return error.InvalidCharacter;
},
},
- else => unreachable,
}
}
unreachable;
diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig
@@ -6,6 +6,7 @@ const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
const TypedValue = @import("TypedValue.zig");
const assert = std.debug.assert;
+const log = std.log;
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const Target = std.Target;
@@ -20,6 +21,7 @@ const ast = std.zig.ast;
const trace = @import("tracy.zig").trace;
const liveness = @import("liveness.zig");
const astgen = @import("astgen.zig");
+const zir_sema = @import("zir_sema.zig");
/// General-purpose allocator. Used for both temporary and long-term storage.
gpa: *Allocator,
@@ -46,7 +48,6 @@ export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{},
/// Maps fully qualified namespaced names to the Decl struct for them.
decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{},
-optimize_mode: std.builtin.Mode,
link_error_flags: link.File.ErrorFlags = .{},
work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),
@@ -75,6 +76,8 @@ next_anon_name_index: usize = 0,
/// contains Decls that need to be deleted if they end up having no references to them.
deletion_set: std.ArrayListUnmanaged(*Decl) = .{},
+/// Owned by Module.
+root_name: []u8,
keep_source_files_loaded: bool,
pub const InnerError = error{ OutOfMemory, AnalysisFail };
@@ -86,6 +89,9 @@ const WorkItem = union(enum) {
/// It may have already be analyzed, or it may have been determined
/// to be outdated; in this case perform semantic analysis again.
analyze_decl: *Decl,
+ /// The source file containing the Decl has been updated, and so the
+ /// Decl may need its line number information updated in the debug info.
+ update_line_number: *Decl,
};
pub const Export = struct {
@@ -173,6 +179,13 @@ pub const Decl = struct {
/// This is populated regardless of semantic analysis and code generation.
link: link.File.Elf.TextBlock = link.File.Elf.TextBlock.empty,
+ /// Represents the function in the linked output file, if the `Decl` is a function.
+ /// This is stored here and not in `Fn` because `Decl` survives across updates but
+ /// `Fn` does not.
+ /// TODO Look into making `Fn` a longer lived structure and moving this field there
+ /// to save on memory usage.
+ fn_link: link.File.Elf.SrcFn = link.File.Elf.SrcFn.empty,
+
contents_hash: std.zig.SrcHash,
/// The shallow set of other decls whose typed_value could possibly change if this Decl's
@@ -233,7 +246,7 @@ pub const Decl = struct {
pub fn dump(self: *Decl) void {
const loc = std.zig.findLineColumn(self.scope.source.bytes, self.src);
- std.debug.warn("{}:{}:{} name={} status={}", .{
+ std.debug.print("{}:{}:{} name={} status={}", .{
self.scope.sub_file_path,
loc.line + 1,
loc.column + 1,
@@ -241,12 +254,12 @@ pub const Decl = struct {
@tagName(self.analysis),
});
if (self.typedValueManaged()) |tvm| {
- std.debug.warn(" ty={} val={}", .{ tvm.typed_value.ty, tvm.typed_value.val });
+ std.debug.print(" ty={} val={}", .{ tvm.typed_value.ty, tvm.typed_value.val });
}
- std.debug.warn("\n", .{});
+ std.debug.print("\n", .{});
}
- fn typedValueManaged(self: *Decl) ?*TypedValue.Managed {
+ pub fn typedValueManaged(self: *Decl) ?*TypedValue.Managed {
switch (self.typed_value) {
.most_recent => |*x| return x,
.never_succeeded => return null,
@@ -384,18 +397,6 @@ pub const Scope = struct {
};
}
- pub fn dumpInst(self: *Scope, inst: *Inst) void {
- const zir_module = self.namespace();
- const loc = std.zig.findLineColumn(zir_module.source.bytes, inst.src);
- std.debug.warn("{}:{}:{}: {}: ty={}\n", .{
- zir_module.sub_file_path,
- loc.line + 1,
- loc.column + 1,
- @tagName(inst.tag),
- inst.ty,
- });
- }
-
/// Asserts the scope has a parent which is a ZIRModule or File and
/// returns the sub_file_path field.
pub fn subFilePath(base: *Scope) []const u8 {
@@ -551,7 +552,7 @@ pub const Scope = struct {
pub fn dumpSrc(self: *File, src: usize) void {
const loc = std.zig.findLineColumn(self.source.bytes, src);
- std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
+ std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
}
pub fn getSource(self: *File, module: *Module) ![:0]const u8 {
@@ -653,7 +654,7 @@ pub const Scope = struct {
pub fn dumpSrc(self: *ZIRModule, src: usize) void {
const loc = std.zig.findLineColumn(self.source.bytes, src);
- std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
+ std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
}
pub fn getSource(self: *ZIRModule, module: *Module) ![:0]const u8 {
@@ -784,6 +785,7 @@ pub const AllErrors = struct {
pub const InitOptions = struct {
target: std.Target,
+ root_name: []const u8,
root_pkg: *Package,
output_mode: std.builtin.OutputMode,
bin_file_dir: ?std.fs.Dir = null,
@@ -795,12 +797,18 @@ pub const InitOptions = struct {
};
pub fn init(gpa: *Allocator, options: InitOptions) !Module {
+ const root_name = try gpa.dupe(u8, options.root_name);
+ errdefer gpa.free(root_name);
+
const bin_file_dir = options.bin_file_dir orelse std.fs.cwd();
- const bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{
+ const bin_file = try link.File.openPath(gpa, bin_file_dir, options.bin_file_path, .{
+ .root_name = root_name,
+ .root_pkg = options.root_pkg,
.target = options.target,
.output_mode = options.output_mode,
.link_mode = options.link_mode orelse .Static,
.object_format = options.object_format orelse options.target.getObjectFormat(),
+ .optimize_mode = options.optimize_mode,
});
errdefer bin_file.destroy();
@@ -832,12 +840,12 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
return Module{
.gpa = gpa,
+ .root_name = root_name,
.root_pkg = options.root_pkg,
.root_scope = root_scope,
.bin_file_dir = bin_file_dir,
.bin_file_path = options.bin_file_path,
.bin_file = bin_file,
- .optimize_mode = options.optimize_mode,
.work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa),
.keep_source_files_loaded = options.keep_source_files_loaded,
};
@@ -846,6 +854,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
pub fn deinit(self: *Module) void {
self.bin_file.destroy();
const gpa = self.gpa;
+ self.gpa.free(self.root_name);
self.deletion_set.deinit(gpa);
self.work_queue.deinit();
@@ -887,13 +896,18 @@ pub fn deinit(self: *Module) void {
fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
for (export_list) |exp| {
+ gpa.free(exp.options.name);
gpa.destroy(exp);
}
gpa.free(export_list);
}
pub fn target(self: Module) std.Target {
- return self.bin_file.options().target;
+ return self.bin_file.options.target;
+}
+
+pub fn optimizeMode(self: Module) std.builtin.Mode {
+ return self.bin_file.options.optimize_mode;
}
/// Detect changes to source files, perform semantic analysis, and update the output files.
@@ -941,7 +955,6 @@ pub fn update(self: *Module) !void {
}
self.link_error_flags = self.bin_file.errorFlags();
- std.log.debug(.module, "link_error_flags: {}\n", .{self.link_error_flags});
// If there are any errors, we anticipate the source files being loaded
// to report error messages. Otherwise we unload all source files to save memory.
@@ -1055,22 +1068,14 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
error.AnalysisFail => {
decl.analysis = .dependency_failure;
},
- error.CGenFailure => {
- // Error is handled by CBE, don't try adding it again
- },
else => {
try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1);
- const result = self.failed_decls.getOrPutAssumeCapacity(decl);
- if (result.found_existing) {
- std.debug.panic("Internal error: attempted to override error '{}' with 'unable to codegen: {}'", .{ result.entry.value.msg, @errorName(err) });
- } else {
- result.entry.value = try ErrorMsg.create(
- self.gpa,
- decl.src(),
- "unable to codegen: {}",
- .{@errorName(err)},
- );
- }
+ self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
+ self.gpa,
+ decl.src(),
+ "unable to codegen: {}",
+ .{@errorName(err)},
+ ));
decl.analysis = .codegen_failure_retryable;
},
};
@@ -1082,10 +1087,22 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
error.AnalysisFail => continue,
};
},
+ .update_line_number => |decl| {
+ self.bin_file.updateDeclLineNumber(self, decl) catch |err| {
+ try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1);
+ self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
+ self.gpa,
+ decl.src(),
+ "unable to update line number: {}",
+ .{@errorName(err)},
+ ));
+ decl.analysis = .codegen_failure_retryable;
+ };
+ },
};
}
-fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void {
+pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -1099,12 +1116,10 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void {
.codegen_failure_retryable,
=> return error.AnalysisFail,
- .complete, .outdated => blk: {
- if (decl.generation == self.generation) {
- assert(decl.analysis == .complete);
- return;
- }
- //std.debug.warn("re-analyzing {}\n", .{decl.name});
+ .complete => return,
+
+ .outdated => blk: {
+ log.debug(.module, "re-analyzing {}\n", .{decl.name});
// The exports this Decl performs will be re-discovered, so we remove them here
// prior to re-analysis.
@@ -1129,7 +1144,7 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void {
};
const type_changed = if (self.root_scope.cast(Scope.ZIRModule)) |zir_module|
- try self.analyzeZirDecl(decl, zir_module.contents.module.decls[decl.src_index])
+ try zir_sema.analyzeZirDecl(self, decl, zir_module.contents.module.decls[decl.src_index])
else
self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
@@ -1205,7 +1220,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
const param_types = try fn_type_scope.arena.alloc(*zir.Inst, param_decls.len);
const fn_src = tree.token_locs[fn_proto.fn_token].start;
- const type_type = try self.addZIRInstConst(&fn_type_scope.base, fn_src, .{
+ const type_type = try astgen.addZIRInstConst(self, &fn_type_scope.base, fn_src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.type_type),
});
@@ -1244,11 +1259,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
};
const return_type_inst = try astgen.expr(self, &fn_type_scope.base, type_type_rl, return_type_expr);
- const fn_type_inst = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.FnType, .{
+ const fn_type_inst = try astgen.addZIRInst(self, &fn_type_scope.base, fn_src, zir.Inst.FnType, .{
.return_type = return_type_inst,
.param_types = param_types,
}, .{});
- _ = try self.addZIRUnOp(&fn_type_scope.base, fn_src, .@"return", fn_type_inst);
+ _ = try astgen.addZIRUnOp(self, &fn_type_scope.base, fn_src, .@"return", fn_type_inst);
// We need the memory for the Type to go into the arena for the Decl
var decl_arena = std.heap.ArenaAllocator.init(self.gpa);
@@ -1264,7 +1279,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
};
defer block_scope.instructions.deinit(self.gpa);
- const fn_type = try self.analyzeBodyValueAsType(&block_scope, .{
+ const fn_type = try zir_sema.analyzeBodyValueAsType(self, &block_scope, .{
.instructions = fn_type_scope.instructions.items,
});
const new_func = try decl_arena.allocator.create(Fn);
@@ -1317,7 +1332,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
!gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn()))
{
const src = tree.token_locs[body_block.rbrace].start;
- _ = try self.addZIRNoOp(&gen_scope.base, src, .returnvoid);
+ _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .returnvoid);
}
const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR);
@@ -1387,19 +1402,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
}
}
-fn analyzeBodyValueAsType(self: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type {
- try self.analyzeBody(&block_scope.base, body);
- for (block_scope.instructions.items) |inst| {
- if (inst.castTag(.ret)) |ret| {
- const val = try self.resolveConstValue(&block_scope.base, ret.operand);
- return val.toType();
- } else {
- return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});
- }
- }
- unreachable;
-}
-
fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void {
try depender.dependencies.ensureCapacity(self.gpa, depender.dependencies.items().len + 1);
try dependee.dependants.ensureCapacity(self.gpa, dependee.dependants.items().len + 1);
@@ -1492,6 +1494,9 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree {
}
fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
// We may be analyzing it for the first time, or this may be
// an incremental update. This code handles both cases.
const tree = try self.getAstTree(root_scope);
@@ -1533,6 +1538,10 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
if (!srcHashEql(decl.contents_hash, contents_hash)) {
try self.markOutdatedDecl(decl);
decl.contents_hash = contents_hash;
+ } else if (decl.fn_link.len != 0) {
+ // TODO Look into detecting when this would be unnecessary by storing enough state
+ // in `Decl` to notice that the line number did not change.
+ self.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl });
}
}
} else {
@@ -1551,7 +1560,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
// Handle explicitly deleted decls from the source code. Not to be confused
// with when we delete decls because they are no longer referenced.
for (deleted_decls.items()) |entry| {
- //std.debug.warn("noticed '{}' deleted from source\n", .{entry.key.name});
+ log.debug(.module, "noticed '{}' deleted from source\n", .{entry.key.name});
try self.deleteDecl(entry.key);
}
}
@@ -1580,7 +1589,6 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void {
const name_hash = root_scope.fullyQualifiedNameHash(src_decl.name);
if (self.decl_table.get(name_hash)) |decl| {
deleted_decls.removeAssertDiscard(decl);
- //std.debug.warn("'{}' contents: '{}'\n", .{ src_decl.name, src_decl.contents });
if (!srcHashEql(src_decl.contents_hash, decl.contents_hash)) {
try self.markOutdatedDecl(decl);
decl.contents_hash = src_decl.contents_hash;
@@ -1600,12 +1608,12 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void {
}
}
for (exports_to_resolve.items) |export_decl| {
- _ = try self.resolveZirDecl(&root_scope.base, export_decl);
+ _ = try zir_sema.resolveZirDecl(self, &root_scope.base, export_decl);
}
// Handle explicitly deleted decls from the source code. Not to be confused
// with when we delete decls because they are no longer referenced.
for (deleted_decls.items()) |entry| {
- //std.debug.warn("noticed '{}' deleted from source\n", .{entry.key.name});
+ log.debug(.module, "noticed '{}' deleted from source\n", .{entry.key.name});
try self.deleteDecl(entry.key);
}
}
@@ -1617,7 +1625,7 @@ fn deleteDecl(self: *Module, decl: *Decl) !void {
// not be present in the set, and this does nothing.
decl.scope.removeDecl(decl);
- //std.debug.warn("deleting decl '{}'\n", .{decl.name});
+ log.debug(.module, "deleting decl '{}'\n", .{decl.name});
const name_hash = decl.fullyQualifiedNameHash();
self.decl_table.removeAssertDiscard(name_hash);
// Remove itself from its dependencies, because we are about to destroy the decl pointer.
@@ -1679,6 +1687,7 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void {
entry.value.destroy(self.gpa);
}
_ = self.symbol_exports.remove(exp.options.name);
+ self.gpa.free(exp.options.name);
self.gpa.destroy(exp);
}
self.gpa.free(kv.value);
@@ -1703,17 +1712,17 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
const fn_zir = func.analysis.queued;
defer fn_zir.arena.promote(self.gpa).deinit();
func.analysis = .{ .in_progress = {} };
- //std.debug.warn("set {} to in_progress\n", .{decl.name});
+ log.debug(.module, "set {} to in_progress\n", .{decl.name});
- try self.analyzeBody(&inner_block.base, fn_zir.body);
+ try zir_sema.analyzeBody(self, &inner_block.base, fn_zir.body);
const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items);
func.analysis = .{ .success = .{ .instructions = instructions } };
- //std.debug.warn("set {} to success\n", .{decl.name});
+ log.debug(.module, "set {} to success\n", .{decl.name});
}
fn markOutdatedDecl(self: *Module, decl: *Decl) !void {
- //std.debug.warn("mark {} outdated\n", .{decl.name});
+ log.debug(.module, "mark {} outdated\n", .{decl.name});
try self.work_queue.writeItem(.{ .analyze_decl = decl });
if (self.failed_decls.remove(decl)) |entry| {
entry.value.destroy(self.gpa);
@@ -1758,131 +1767,18 @@ fn createNewDecl(
return new_decl;
}
-fn analyzeZirDecl(self: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool {
- var decl_scope: Scope.DeclAnalysis = .{
- .decl = decl,
- .arena = std.heap.ArenaAllocator.init(self.gpa),
- };
- errdefer decl_scope.arena.deinit();
-
- decl.analysis = .in_progress;
-
- const typed_value = try self.analyzeConstInst(&decl_scope.base, src_decl.inst);
- const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State);
-
- var prev_type_has_bits = false;
- var type_changed = true;
-
- if (decl.typedValueManaged()) |tvm| {
- prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits();
- type_changed = !tvm.typed_value.ty.eql(typed_value.ty);
-
- tvm.deinit(self.gpa);
- }
-
- arena_state.* = decl_scope.arena.state;
- decl.typed_value = .{
- .most_recent = .{
- .typed_value = typed_value,
- .arena = arena_state,
- },
- };
- decl.analysis = .complete;
- decl.generation = self.generation;
- if (typed_value.ty.hasCodeGenBits()) {
- // We don't fully codegen the decl until later, but we do need to reserve a global
- // offset table index for it. This allows us to codegen decls out of dependency order,
- // increasing how many computations can be done in parallel.
- try self.bin_file.allocateDeclIndexes(decl);
- try self.work_queue.writeItem(.{ .codegen_decl = decl });
- } else if (prev_type_has_bits) {
- self.bin_file.freeDecl(decl);
- }
-
- return type_changed;
-}
-
-fn resolveZirDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl {
- const zir_module = self.root_scope.cast(Scope.ZIRModule).?;
- const entry = zir_module.contents.module.findDecl(src_decl.name).?;
- return self.resolveZirDeclHavingIndex(scope, src_decl, entry.index);
-}
-
-fn resolveZirDeclHavingIndex(self: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl {
- const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name);
- const decl = self.decl_table.get(name_hash).?;
- decl.src_index = src_index;
- try self.ensureDeclAnalyzed(decl);
- return decl;
-}
-
-/// Declares a dependency on the decl.
-fn resolveCompleteZirDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl {
- const decl = try self.resolveZirDecl(scope, src_decl);
- switch (decl.analysis) {
- .unreferenced => unreachable,
- .in_progress => unreachable,
- .outdated => unreachable,
-
- .dependency_failure,
- .sema_failure,
- .sema_failure_retryable,
- .codegen_failure,
- .codegen_failure_retryable,
- => return error.AnalysisFail,
-
- .complete => {},
- }
- return decl;
-}
-
-/// TODO Look into removing this function. The body is only needed for .zir files, not .zig files.
-fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst {
- if (old_inst.analyzed_inst) |inst| return inst;
-
- // If this assert trips, the instruction that was referenced did not get properly
- // analyzed before it was referenced.
- const zir_module = scope.namespace().cast(Scope.ZIRModule).?;
- const entry = if (old_inst.cast(zir.Inst.DeclVal)) |declval| blk: {
- const decl_name = declval.positionals.name;
- const entry = zir_module.contents.module.findDecl(decl_name) orelse
- return self.fail(scope, old_inst.src, "decl '{}' not found", .{decl_name});
- break :blk entry;
- } else blk: {
- // If this assert trips, the instruction that was referenced did not get
- // properly analyzed by a previous instruction analysis before it was
- // referenced by the current one.
- break :blk zir_module.contents.module.findInstDecl(old_inst).?;
- };
- const decl = try self.resolveCompleteZirDecl(scope, entry.decl);
- const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl);
- // Note: it would be tempting here to store the result into old_inst.analyzed_inst field,
- // but this would prevent the analyzeDeclRef from happening, which is needed to properly
- // detect Decl dependencies and dependency failures on updates.
- return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src);
-}
-
/// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites.
-fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
+pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
return scope.cast(Scope.Block) orelse
return self.fail(scope, src, "instruction illegal outside function body", .{});
}
-fn resolveInstConst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue {
- const new_inst = try self.resolveInst(scope, old_inst);
- const val = try self.resolveConstValue(scope, new_inst);
- return TypedValue{
- .ty = new_inst.ty,
- .val = val,
- };
-}
-
-fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value {
+pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value {
return (try self.resolveDefinedValue(scope, base)) orelse
return self.fail(scope, base.src, "unable to resolve comptime value", .{});
}
-fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value {
+pub fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value {
if (base.value()) |val| {
if (val.isUndef()) {
return self.fail(scope, base.src, "use of undefined value here causes undefined behavior", .{});
@@ -1892,23 +1788,7 @@ fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value {
return null;
}
-fn resolveConstString(self: *Module, scope: *Scope, old_inst: *zir.Inst) ![]u8 {
- const new_inst = try self.resolveInst(scope, old_inst);
- const wanted_type = Type.initTag(.const_slice_u8);
- const coerced_inst = try self.coerce(scope, wanted_type, new_inst);
- const val = try self.resolveConstValue(scope, coerced_inst);
- return val.toAllocatedBytes(scope.arena());
-}
-
-fn resolveType(self: *Module, scope: *Scope, old_inst: *zir.Inst) !Type {
- const new_inst = try self.resolveInst(scope, old_inst);
- const wanted_type = Type.initTag(.@"type");
- const coerced_inst = try self.coerce(scope, wanted_type, new_inst);
- const val = try self.resolveConstValue(scope, coerced_inst);
- return val.toType();
-}
-
-fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const u8, exported_decl: *Decl) !void {
+pub fn analyzeExport(self: *Module, scope: *Scope, src: usize, borrowed_symbol_name: []const u8, exported_decl: *Decl) !void {
try self.ensureDeclAnalyzed(exported_decl);
const typed_value = exported_decl.typed_value.most_recent.typed_value;
switch (typed_value.ty.zigTypeTag()) {
@@ -1922,6 +1802,9 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
const new_export = try self.gpa.create(Export);
errdefer self.gpa.destroy(new_export);
+ const symbol_name = try self.gpa.dupe(u8, borrowed_symbol_name);
+ errdefer self.gpa.free(symbol_name);
+
const owner_decl = scope.decl().?;
new_export.* = .{
@@ -1934,7 +1817,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
};
// Add to export_owners table.
- const eo_gop = self.export_owners.getOrPut(self.gpa, owner_decl) catch unreachable;
+ const eo_gop = self.export_owners.getOrPutAssumeCapacity(owner_decl);
if (!eo_gop.found_existing) {
eo_gop.entry.value = &[0]*Export{};
}
@@ -1943,7 +1826,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
errdefer eo_gop.entry.value = self.gpa.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1);
// Add to exported_decl table.
- const de_gop = self.decl_exports.getOrPut(self.gpa, exported_decl) catch unreachable;
+ const de_gop = self.decl_exports.getOrPutAssumeCapacity(exported_decl);
if (!de_gop.found_existing) {
de_gop.entry.value = &[0]*Export{};
}
@@ -1980,7 +1863,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
};
}
-fn addNoOp(
+pub fn addNoOp(
self: *Module,
block: *Scope.Block,
src: usize,
@@ -1999,7 +1882,7 @@ fn addNoOp(
return &inst.base;
}
-fn addUnOp(
+pub fn addUnOp(
self: *Module,
block: *Scope.Block,
src: usize,
@@ -2020,7 +1903,7 @@ fn addUnOp(
return &inst.base;
}
-fn addBinOp(
+pub fn addBinOp(
self: *Module,
block: *Scope.Block,
src: usize,
@@ -2043,7 +1926,7 @@ fn addBinOp(
return &inst.base;
}
-fn addBr(
+pub fn addBr(
self: *Module,
scope_block: *Scope.Block,
src: usize,
@@ -2064,7 +1947,7 @@ fn addBr(
return &inst.base;
}
-fn addCondBr(
+pub fn addCondBr(
self: *Module,
block: *Scope.Block,
src: usize,
@@ -2087,7 +1970,7 @@ fn addCondBr(
return &inst.base;
}
-fn addCall(
+pub fn addCall(
self: *Module,
block: *Scope.Block,
src: usize,
@@ -2109,138 +1992,7 @@ fn addCall(
return &inst.base;
}
-pub fn addZIRInstSpecial(
- self: *Module,
- scope: *Scope,
- src: usize,
- comptime T: type,
- positionals: std.meta.fieldInfo(T, "positionals").field_type,
- kw_args: std.meta.fieldInfo(T, "kw_args").field_type,
-) !*T {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(T);
- inst.* = .{
- .base = .{
- .tag = T.base_tag,
- .src = src,
- },
- .positionals = positionals,
- .kw_args = kw_args,
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return inst;
-}
-
-pub fn addZIRNoOpT(self: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst.NoOp {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(zir.Inst.NoOp);
- inst.* = .{
- .base = .{
- .tag = tag,
- .src = src,
- },
- .positionals = .{},
- .kw_args = .{},
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return inst;
-}
-
-pub fn addZIRNoOp(self: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst {
- const inst = try self.addZIRNoOpT(scope, src, tag);
- return &inst.base;
-}
-
-pub fn addZIRUnOp(
- self: *Module,
- scope: *Scope,
- src: usize,
- tag: zir.Inst.Tag,
- operand: *zir.Inst,
-) !*zir.Inst {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(zir.Inst.UnOp);
- inst.* = .{
- .base = .{
- .tag = tag,
- .src = src,
- },
- .positionals = .{
- .operand = operand,
- },
- .kw_args = .{},
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return &inst.base;
-}
-
-pub fn addZIRBinOp(
- self: *Module,
- scope: *Scope,
- src: usize,
- tag: zir.Inst.Tag,
- lhs: *zir.Inst,
- rhs: *zir.Inst,
-) !*zir.Inst {
- const gen_zir = scope.getGenZIR();
- try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1);
- const inst = try gen_zir.arena.create(zir.Inst.BinOp);
- inst.* = .{
- .base = .{
- .tag = tag,
- .src = src,
- },
- .positionals = .{
- .lhs = lhs,
- .rhs = rhs,
- },
- .kw_args = .{},
- };
- gen_zir.instructions.appendAssumeCapacity(&inst.base);
- return &inst.base;
-}
-
-pub fn addZIRInst(
- self: *Module,
- scope: *Scope,
- src: usize,
- comptime T: type,
- positionals: std.meta.fieldInfo(T, "positionals").field_type,
- kw_args: std.meta.fieldInfo(T, "kw_args").field_type,
-) !*zir.Inst {
- const inst_special = try self.addZIRInstSpecial(scope, src, T, positionals, kw_args);
- return &inst_special.base;
-}
-
-/// TODO The existence of this function is a workaround for a bug in stage1.
-pub fn addZIRInstConst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst {
- const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type;
- return self.addZIRInst(scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{});
-}
-
-/// TODO The existence of this function is a workaround for a bug in stage1.
-pub fn addZIRInstBlock(self: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block {
- const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type;
- return self.addZIRInstSpecial(scope, src, zir.Inst.Block, P{ .body = body }, .{});
-}
-
-fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime T: type) !*T {
- const inst = try block.arena.create(T);
- inst.* = .{
- .base = .{
- .tag = T.base_tag,
- .ty = ty,
- .src = src,
- },
- };
- try block.instructions.append(self.gpa, &inst.base);
- return inst;
-}
-
-fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst {
+pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst {
const const_inst = try scope.arena().create(Inst.Constant);
const_inst.* = .{
.base = .{
@@ -2253,42 +2005,42 @@ fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue)
return &const_inst.base;
}
-fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst {
+pub fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst {
return self.constInst(scope, src, .{
.ty = Type.initTag(.type),
.val = try ty.toValue(scope.arena()),
});
}
-fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst {
+pub fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst {
return self.constInst(scope, src, .{
.ty = Type.initTag(.void),
- .val = Value.initTag(.the_one_possible_value),
+ .val = Value.initTag(.void_value),
});
}
-fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst {
+pub fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst {
return self.constInst(scope, src, .{
.ty = Type.initTag(.noreturn),
- .val = Value.initTag(.the_one_possible_value),
+ .val = Value.initTag(.unreachable_value),
});
}
-fn constUndef(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst {
+pub fn constUndef(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst {
return self.constInst(scope, src, .{
.ty = ty,
.val = Value.initTag(.undef),
});
}
-fn constBool(self: *Module, scope: *Scope, src: usize, v: bool) !*Inst {
+pub fn constBool(self: *Module, scope: *Scope, src: usize, v: bool) !*Inst {
return self.constInst(scope, src, .{
.ty = Type.initTag(.bool),
.val = ([2]Value{ Value.initTag(.bool_false), Value.initTag(.bool_true) })[@boolToInt(v)],
});
}
-fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64) !*Inst {
+pub fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64) !*Inst {
const int_payload = try scope.arena().create(Value.Payload.Int_u64);
int_payload.* = .{ .int = int };
@@ -2298,7 +2050,7 @@ fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64
});
}
-fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) !*Inst {
+pub fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) !*Inst {
const int_payload = try scope.arena().create(Value.Payload.Int_i64);
int_payload.* = .{ .int = int };
@@ -2308,7 +2060,7 @@ fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64)
});
}
-fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst {
+pub fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst {
const val_payload = if (big_int.positive) blk: {
if (big_int.to(u64)) |x| {
return self.constIntUnsigned(scope, src, ty, x);
@@ -2337,191 +2089,7 @@ fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigI
});
}
-fn analyzeConstInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue {
- const new_inst = try self.analyzeInst(scope, old_inst);
- return TypedValue{
- .ty = new_inst.ty,
- .val = try self.resolveConstValue(scope, new_inst),
- };
-}
-
-fn analyzeInstConst(self: *Module, scope: *Scope, const_inst: *zir.Inst.Const) InnerError!*Inst {
- // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions
- // after analysis.
- const typed_value_copy = try const_inst.positionals.typed_value.copy(scope.arena());
- return self.constInst(scope, const_inst.base.src, typed_value_copy);
-}
-
-fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst {
- switch (old_inst.tag) {
- .alloc => return self.analyzeInstAlloc(scope, old_inst.castTag(.alloc).?),
- .alloc_inferred => return self.analyzeInstAllocInferred(scope, old_inst.castTag(.alloc_inferred).?),
- .arg => return self.analyzeInstArg(scope, old_inst.castTag(.arg).?),
- .bitcast_result_ptr => return self.analyzeInstBitCastResultPtr(scope, old_inst.castTag(.bitcast_result_ptr).?),
- .block => return self.analyzeInstBlock(scope, old_inst.castTag(.block).?),
- .@"break" => return self.analyzeInstBreak(scope, old_inst.castTag(.@"break").?),
- .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.castTag(.breakpoint).?),
- .breakvoid => return self.analyzeInstBreakVoid(scope, old_inst.castTag(.breakvoid).?),
- .call => return self.analyzeInstCall(scope, old_inst.castTag(.call).?),
- .coerce_result_block_ptr => return self.analyzeInstCoerceResultBlockPtr(scope, old_inst.castTag(.coerce_result_block_ptr).?),
- .coerce_result_ptr => return self.analyzeInstCoerceResultPtr(scope, old_inst.castTag(.coerce_result_ptr).?),
- .coerce_to_ptr_elem => return self.analyzeInstCoerceToPtrElem(scope, old_inst.castTag(.coerce_to_ptr_elem).?),
- .compileerror => return self.analyzeInstCompileError(scope, old_inst.castTag(.compileerror).?),
- .@"const" => return self.analyzeInstConst(scope, old_inst.castTag(.@"const").?),
- .declref => return self.analyzeInstDeclRef(scope, old_inst.castTag(.declref).?),
- .declref_str => return self.analyzeInstDeclRefStr(scope, old_inst.castTag(.declref_str).?),
- .declval => return self.analyzeInstDeclVal(scope, old_inst.castTag(.declval).?),
- .declval_in_module => return self.analyzeInstDeclValInModule(scope, old_inst.castTag(.declval_in_module).?),
- .ensure_result_used => return self.analyzeInstEnsureResultUsed(scope, old_inst.castTag(.ensure_result_used).?),
- .ensure_result_non_error => return self.analyzeInstEnsureResultNonError(scope, old_inst.castTag(.ensure_result_non_error).?),
- .ret_ptr => return self.analyzeInstRetPtr(scope, old_inst.castTag(.ret_ptr).?),
- .ret_type => return self.analyzeInstRetType(scope, old_inst.castTag(.ret_type).?),
- .store => return self.analyzeInstStore(scope, old_inst.castTag(.store).?),
- .str => return self.analyzeInstStr(scope, old_inst.castTag(.str).?),
- .int => {
- const big_int = old_inst.castTag(.int).?.positionals.int;
- return self.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int);
- },
- .inttype => return self.analyzeInstIntType(scope, old_inst.castTag(.inttype).?),
- .param_type => return self.analyzeInstParamType(scope, old_inst.castTag(.param_type).?),
- .ptrtoint => return self.analyzeInstPtrToInt(scope, old_inst.castTag(.ptrtoint).?),
- .fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.castTag(.fieldptr).?),
- .deref => return self.analyzeInstDeref(scope, old_inst.castTag(.deref).?),
- .as => return self.analyzeInstAs(scope, old_inst.castTag(.as).?),
- .@"asm" => return self.analyzeInstAsm(scope, old_inst.castTag(.@"asm").?),
- .@"unreachable" => return self.analyzeInstUnreachable(scope, old_inst.castTag(.@"unreachable").?),
- .@"return" => return self.analyzeInstRet(scope, old_inst.castTag(.@"return").?),
- .returnvoid => return self.analyzeInstRetVoid(scope, old_inst.castTag(.returnvoid).?),
- .@"fn" => return self.analyzeInstFn(scope, old_inst.castTag(.@"fn").?),
- .@"export" => return self.analyzeInstExport(scope, old_inst.castTag(.@"export").?),
- .primitive => return self.analyzeInstPrimitive(scope, old_inst.castTag(.primitive).?),
- .fntype => return self.analyzeInstFnType(scope, old_inst.castTag(.fntype).?),
- .intcast => return self.analyzeInstIntCast(scope, old_inst.castTag(.intcast).?),
- .bitcast => return self.analyzeInstBitCast(scope, old_inst.castTag(.bitcast).?),
- .floatcast => return self.analyzeInstFloatCast(scope, old_inst.castTag(.floatcast).?),
- .elemptr => return self.analyzeInstElemPtr(scope, old_inst.castTag(.elemptr).?),
- .add, .sub => return self.analyzeInstArithmetic(scope, old_inst.cast(zir.Inst.BinOp).?),
- .cmp_lt => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_lt).?, .lt),
- .cmp_lte => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_lte).?, .lte),
- .cmp_eq => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_eq).?, .eq),
- .cmp_gte => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_gte).?, .gte),
- .cmp_gt => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_gt).?, .gt),
- .cmp_neq => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_neq).?, .neq),
- .condbr => return self.analyzeInstCondBr(scope, old_inst.castTag(.condbr).?),
- .isnull => return self.analyzeInstIsNonNull(scope, old_inst.castTag(.isnull).?, true),
- .isnonnull => return self.analyzeInstIsNonNull(scope, old_inst.castTag(.isnonnull).?, false),
- .boolnot => return self.analyzeInstBoolNot(scope, old_inst.castTag(.boolnot).?),
- }
-}
-
-fn analyzeInstCoerceResultBlockPtr(
- self: *Module,
- scope: *Scope,
- inst: *zir.Inst.CoerceResultBlockPtr,
-) InnerError!*Inst {
- return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultBlockPtr", .{});
-}
-
-fn analyzeInstBitCastResultPtr(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- return self.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastResultPtr", .{});
-}
-
-fn analyzeInstCoerceResultPtr(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultPtr", .{});
-}
-
-fn analyzeInstCoerceToPtrElem(self: *Module, scope: *Scope, inst: *zir.Inst.CoerceToPtrElem) InnerError!*Inst {
- return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceToPtrElem", .{});
-}
-
-fn analyzeInstRetPtr(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- return self.fail(scope, inst.base.src, "TODO implement analyzeInstRetPtr", .{});
-}
-
-fn analyzeInstRetType(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
- const ret_type = fn_ty.fnReturnType();
- return self.constType(scope, inst.base.src, ret_type);
-}
-
-fn analyzeInstEnsureResultUsed(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const operand = try self.resolveInst(scope, inst.positionals.operand);
- switch (operand.ty.zigTypeTag()) {
- .Void, .NoReturn => return self.constVoid(scope, operand.src),
- else => return self.fail(scope, operand.src, "expression value is ignored", .{}),
- }
-}
-
-fn analyzeInstEnsureResultNonError(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const operand = try self.resolveInst(scope, inst.positionals.operand);
- switch (operand.ty.zigTypeTag()) {
- .ErrorSet, .ErrorUnion => return self.fail(scope, operand.src, "error is discarded", .{}),
- else => return self.constVoid(scope, operand.src),
- }
-}
-
-fn analyzeInstAlloc(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- return self.fail(scope, inst.base.src, "TODO implement analyzeInstAlloc", .{});
-}
-
-fn analyzeInstAllocInferred(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- return self.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{});
-}
-
-fn analyzeInstStore(self: *Module, scope: *Scope, inst: *zir.Inst.Store) InnerError!*Inst {
- return self.fail(scope, inst.base.src, "TODO implement analyzeInstStore", .{});
-}
-
-fn analyzeInstParamType(self: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerError!*Inst {
- const fn_inst = try self.resolveInst(scope, inst.positionals.func);
- const arg_index = inst.positionals.arg_index;
-
- const fn_ty: Type = switch (fn_inst.ty.zigTypeTag()) {
- .Fn => fn_inst.ty,
- .BoundFn => {
- return self.fail(scope, fn_inst.src, "TODO implement analyzeInstParamType for method call syntax", .{});
- },
- else => {
- return self.fail(scope, fn_inst.src, "expected function, found '{}'", .{fn_inst.ty});
- },
- };
-
- // TODO support C-style var args
- const param_count = fn_ty.fnParamLen();
- if (arg_index >= param_count) {
- return self.fail(scope, inst.base.src, "arg index {} out of bounds; '{}' has {} arguments", .{
- arg_index,
- fn_ty,
- param_count,
- });
- }
-
- // TODO support generic functions
- const param_type = fn_ty.fnParamType(arg_index);
- return self.constType(scope, inst.base.src, param_type);
-}
-
-fn analyzeInstStr(self: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst {
- // The bytes references memory inside the ZIR module, which can get deallocated
- // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena.
- var new_decl_arena = std.heap.ArenaAllocator.init(self.gpa);
- const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes);
-
- const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0);
- ty_payload.* = .{ .len = arena_bytes.len };
-
- const bytes_payload = try scope.arena().create(Value.Payload.Bytes);
- bytes_payload.* = .{ .data = arena_bytes };
-
- const new_decl = try self.createAnonymousDecl(scope, &new_decl_arena, .{
- .ty = Type.initPayload(&ty_payload.base),
- .val = Value.initPayload(&bytes_payload.base),
- });
- return self.analyzeDeclRef(scope, str_inst.base.src, new_decl);
-}
-
-fn createAnonymousDecl(
+pub fn createAnonymousDecl(
self: *Module,
scope: *Scope,
decl_arena: *std.heap.ArenaAllocator,
@@ -2567,151 +2135,7 @@ pub fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*De
return self.decl_table.get(name_hash);
}
-fn analyzeInstExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst {
- const symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name);
- const exported_decl = self.lookupDeclName(scope, export_inst.positionals.decl_name) orelse
- return self.fail(scope, export_inst.base.src, "decl '{}' not found", .{export_inst.positionals.decl_name});
- try self.analyzeExport(scope, export_inst.base.src, symbol_name, exported_decl);
- return self.constVoid(scope, export_inst.base.src);
-}
-
-fn analyzeInstCompileError(self: *Module, scope: *Scope, inst: *zir.Inst.CompileError) InnerError!*Inst {
- return self.fail(scope, inst.base.src, "{}", .{inst.positionals.msg});
-}
-
-fn analyzeInstArg(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
- const param_index = b.instructions.items.len;
- const param_count = fn_ty.fnParamLen();
- if (param_index >= param_count) {
- return self.fail(scope, inst.base.src, "parameter index {} outside list of length {}", .{
- param_index,
- param_count,
- });
- }
- const param_type = fn_ty.fnParamType(param_index);
- return self.addNoOp(b, inst.base.src, param_type, .arg);
-}
-
-fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst {
- const parent_block = scope.cast(Scope.Block).?;
-
- // Reserve space for a Block instruction so that generated Break instructions can
- // point to it, even if it doesn't end up getting used because the code ends up being
- // comptime evaluated.
- const block_inst = try parent_block.arena.create(Inst.Block);
- block_inst.* = .{
- .base = .{
- .tag = Inst.Block.base_tag,
- .ty = undefined, // Set after analysis.
- .src = inst.base.src,
- },
- .body = undefined,
- };
-
- var child_block: Scope.Block = .{
- .parent = parent_block,
- .func = parent_block.func,
- .decl = parent_block.decl,
- .instructions = .{},
- .arena = parent_block.arena,
- // TODO @as here is working around a miscompilation compiler bug :(
- .label = @as(?Scope.Block.Label, Scope.Block.Label{
- .zir_block = inst,
- .results = .{},
- .block_inst = block_inst,
- }),
- };
- const label = &child_block.label.?;
-
- defer child_block.instructions.deinit(self.gpa);
- defer label.results.deinit(self.gpa);
-
- try self.analyzeBody(&child_block.base, inst.positionals.body);
-
- // Blocks must terminate with noreturn instruction.
- assert(child_block.instructions.items.len != 0);
- assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn());
-
- // Need to set the type and emit the Block instruction. This allows machine code generation
- // to emit a jump instruction to after the block when it encounters the break.
- try parent_block.instructions.append(self.gpa, &block_inst.base);
- block_inst.base.ty = try self.resolvePeerTypes(scope, label.results.items);
- block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) };
- return &block_inst.base;
-}
-
-fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNoOp(b, inst.base.src, Type.initTag(.void), .breakpoint);
-}
-
-fn analyzeInstBreak(self: *Module, scope: *Scope, inst: *zir.Inst.Break) InnerError!*Inst {
- const operand = try self.resolveInst(scope, inst.positionals.operand);
- const block = inst.positionals.block;
- return self.analyzeBreak(scope, inst.base.src, block, operand);
-}
-
-fn analyzeInstBreakVoid(self: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) InnerError!*Inst {
- const block = inst.positionals.block;
- const void_inst = try self.constVoid(scope, inst.base.src);
- return self.analyzeBreak(scope, inst.base.src, block, void_inst);
-}
-
-fn analyzeBreak(
- self: *Module,
- scope: *Scope,
- src: usize,
- zir_block: *zir.Inst.Block,
- operand: *Inst,
-) InnerError!*Inst {
- var opt_block = scope.cast(Scope.Block);
- while (opt_block) |block| {
- if (block.label) |*label| {
- if (label.zir_block == zir_block) {
- try label.results.append(self.gpa, operand);
- const b = try self.requireRuntimeBlock(scope, src);
- return self.addBr(b, src, label.block_inst, operand);
- }
- }
- opt_block = block.parent;
- } else unreachable;
-}
-
-fn analyzeInstDeclRefStr(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst {
- const decl_name = try self.resolveConstString(scope, inst.positionals.name);
- return self.analyzeDeclRefByName(scope, inst.base.src, decl_name);
-}
-
-fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst {
- return self.analyzeDeclRefByName(scope, inst.base.src, inst.positionals.name);
-}
-
-fn analyzeDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl {
- const decl_name = inst.positionals.name;
- const zir_module = scope.namespace().cast(Scope.ZIRModule).?;
- const src_decl = zir_module.contents.module.findDecl(decl_name) orelse
- return self.fail(scope, inst.base.src, "use of undeclared identifier '{}'", .{decl_name});
-
- const decl = try self.resolveCompleteZirDecl(scope, src_decl.decl);
-
- return decl;
-}
-
-fn analyzeInstDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst {
- const decl = try self.analyzeDeclVal(scope, inst);
- const ptr = try self.analyzeDeclRef(scope, inst.base.src, decl);
- return self.analyzeDeref(scope, inst.base.src, ptr, inst.base.src);
-}
-
-fn analyzeInstDeclValInModule(self: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst {
- const decl = inst.positionals.decl;
- const ptr = try self.analyzeDeclRef(scope, inst.base.src, decl);
- return self.analyzeDeref(scope, inst.base.src, ptr, inst.base.src);
-}
-
-fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst {
+pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst {
const scope_decl = scope.decl().?;
try self.declareDeclDependency(scope_decl, decl);
self.ensureDeclAnalyzed(decl) catch |err| {
@@ -2739,410 +2163,7 @@ fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerEr
});
}
-fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst {
- const decl = self.lookupDeclName(scope, decl_name) orelse
- return self.fail(scope, src, "decl '{}' not found", .{decl_name});
- return self.analyzeDeclRef(scope, src, decl);
-}
-
-fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
- const func = try self.resolveInst(scope, inst.positionals.func);
- if (func.ty.zigTypeTag() != .Fn)
- return self.fail(scope, inst.positionals.func.src, "type '{}' not a function", .{func.ty});
-
- const cc = func.ty.fnCallingConvention();
- if (cc == .Naked) {
- // TODO add error note: declared here
- return self.fail(
- scope,
- inst.positionals.func.src,
- "unable to call function with naked calling convention",
- .{},
- );
- }
- const call_params_len = inst.positionals.args.len;
- const fn_params_len = func.ty.fnParamLen();
- if (func.ty.fnIsVarArgs()) {
- if (call_params_len < fn_params_len) {
- // TODO add error note: declared here
- return self.fail(
- scope,
- inst.positionals.func.src,
- "expected at least {} arguments, found {}",
- .{ fn_params_len, call_params_len },
- );
- }
- return self.fail(scope, inst.base.src, "TODO implement support for calling var args functions", .{});
- } else if (fn_params_len != call_params_len) {
- // TODO add error note: declared here
- return self.fail(
- scope,
- inst.positionals.func.src,
- "expected {} arguments, found {}",
- .{ fn_params_len, call_params_len },
- );
- }
-
- if (inst.kw_args.modifier == .compile_time) {
- return self.fail(scope, inst.base.src, "TODO implement comptime function calls", .{});
- }
- if (inst.kw_args.modifier != .auto) {
- return self.fail(scope, inst.base.src, "TODO implement call with modifier {}", .{inst.kw_args.modifier});
- }
-
- // TODO handle function calls of generic functions
-
- const fn_param_types = try self.gpa.alloc(Type, fn_params_len);
- defer self.gpa.free(fn_param_types);
- func.ty.fnParamTypes(fn_param_types);
-
- const casted_args = try scope.arena().alloc(*Inst, fn_params_len);
- for (inst.positionals.args) |src_arg, i| {
- const uncasted_arg = try self.resolveInst(scope, src_arg);
- casted_args[i] = try self.coerce(scope, fn_param_types[i], uncasted_arg);
- }
-
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addCall(b, inst.base.src, Type.initTag(.void), func, casted_args);
-}
-
-fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst {
- const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type);
- const fn_zir = blk: {
- var fn_arena = std.heap.ArenaAllocator.init(self.gpa);
- errdefer fn_arena.deinit();
-
- const fn_zir = try scope.arena().create(Fn.ZIR);
- fn_zir.* = .{
- .body = .{
- .instructions = fn_inst.positionals.body.instructions,
- },
- .arena = fn_arena.state,
- };
- break :blk fn_zir;
- };
- const new_func = try scope.arena().create(Fn);
- new_func.* = .{
- .analysis = .{ .queued = fn_zir },
- .owner_decl = scope.decl().?,
- };
- const fn_payload = try scope.arena().create(Value.Payload.Function);
- fn_payload.* = .{ .func = new_func };
- return self.constInst(scope, fn_inst.base.src, .{
- .ty = fn_type,
- .val = Value.initPayload(&fn_payload.base),
- });
-}
-
-fn analyzeInstIntType(self: *Module, scope: *Scope, inttype: *zir.Inst.IntType) InnerError!*Inst {
- return self.fail(scope, inttype.base.src, "TODO implement inttype", .{});
-}
-
-fn analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst {
- const return_type = try self.resolveType(scope, fntype.positionals.return_type);
-
- // Hot path for some common function types.
- if (fntype.positionals.param_types.len == 0) {
- if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Unspecified) {
- return self.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args));
- }
-
- if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .Unspecified) {
- return self.constType(scope, fntype.base.src, Type.initTag(.fn_void_no_args));
- }
-
- if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Naked) {
- return self.constType(scope, fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args));
- }
-
- if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .C) {
- return self.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args));
- }
- }
-
- const arena = scope.arena();
- const param_types = try arena.alloc(Type, fntype.positionals.param_types.len);
- for (fntype.positionals.param_types) |param_type, i| {
- param_types[i] = try self.resolveType(scope, param_type);
- }
-
- const payload = try arena.create(Type.Payload.Function);
- payload.* = .{
- .cc = fntype.kw_args.cc,
- .return_type = return_type,
- .param_types = param_types,
- };
- return self.constType(scope, fntype.base.src, Type.initPayload(&payload.base));
-}
-
-fn analyzeInstPrimitive(self: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst {
- return self.constInst(scope, primitive.base.src, primitive.positionals.tag.toTypedValue());
-}
-
-fn analyzeInstAs(self: *Module, scope: *Scope, as: *zir.Inst.BinOp) InnerError!*Inst {
- const dest_type = try self.resolveType(scope, as.positionals.lhs);
- const new_inst = try self.resolveInst(scope, as.positionals.rhs);
- return self.coerce(scope, dest_type, new_inst);
-}
-
-fn analyzeInstPtrToInt(self: *Module, scope: *Scope, ptrtoint: *zir.Inst.UnOp) InnerError!*Inst {
- const ptr = try self.resolveInst(scope, ptrtoint.positionals.operand);
- if (ptr.ty.zigTypeTag() != .Pointer) {
- return self.fail(scope, ptrtoint.positionals.operand.src, "expected pointer, found '{}'", .{ptr.ty});
- }
- // TODO handle known-pointer-address
- const b = try self.requireRuntimeBlock(scope, ptrtoint.base.src);
- const ty = Type.initTag(.usize);
- return self.addUnOp(b, ptrtoint.base.src, ty, .ptrtoint, ptr);
-}
-
-fn analyzeInstFieldPtr(self: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr) InnerError!*Inst {
- const object_ptr = try self.resolveInst(scope, fieldptr.positionals.object_ptr);
- const field_name = try self.resolveConstString(scope, fieldptr.positionals.field_name);
-
- const elem_ty = switch (object_ptr.ty.zigTypeTag()) {
- .Pointer => object_ptr.ty.elemType(),
- else => return self.fail(scope, fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
- };
- switch (elem_ty.zigTypeTag()) {
- .Array => {
- if (mem.eql(u8, field_name, "len")) {
- const len_payload = try scope.arena().create(Value.Payload.Int_u64);
- len_payload.* = .{ .int = elem_ty.arrayLen() };
-
- const ref_payload = try scope.arena().create(Value.Payload.RefVal);
- ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) };
-
- return self.constInst(scope, fieldptr.base.src, .{
- .ty = Type.initTag(.single_const_pointer_to_comptime_int),
- .val = Value.initPayload(&ref_payload.base),
- });
- } else {
- return self.fail(
- scope,
- fieldptr.positionals.field_name.src,
- "no member named '{}' in '{}'",
- .{ field_name, elem_ty },
- );
- }
- },
- else => return self.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}),
- }
-}
-
-fn analyzeInstIntCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const dest_type = try self.resolveType(scope, inst.positionals.lhs);
- const operand = try self.resolveInst(scope, inst.positionals.rhs);
-
- const dest_is_comptime_int = switch (dest_type.zigTypeTag()) {
- .ComptimeInt => true,
- .Int => false,
- else => return self.fail(
- scope,
- inst.positionals.lhs.src,
- "expected integer type, found '{}'",
- .{
- dest_type,
- },
- ),
- };
-
- switch (operand.ty.zigTypeTag()) {
- .ComptimeInt, .Int => {},
- else => return self.fail(
- scope,
- inst.positionals.rhs.src,
- "expected integer type, found '{}'",
- .{operand.ty},
- ),
- }
-
- if (operand.value() != null) {
- return self.coerce(scope, dest_type, operand);
- } else if (dest_is_comptime_int) {
- return self.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_int'", .{});
- }
-
- return self.fail(scope, inst.base.src, "TODO implement analyze widen or shorten int", .{});
-}
-
-fn analyzeInstBitCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const dest_type = try self.resolveType(scope, inst.positionals.lhs);
- const operand = try self.resolveInst(scope, inst.positionals.rhs);
- return self.bitcast(scope, dest_type, operand);
-}
-
-fn analyzeInstFloatCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const dest_type = try self.resolveType(scope, inst.positionals.lhs);
- const operand = try self.resolveInst(scope, inst.positionals.rhs);
-
- const dest_is_comptime_float = switch (dest_type.zigTypeTag()) {
- .ComptimeFloat => true,
- .Float => false,
- else => return self.fail(
- scope,
- inst.positionals.lhs.src,
- "expected float type, found '{}'",
- .{
- dest_type,
- },
- ),
- };
-
- switch (operand.ty.zigTypeTag()) {
- .ComptimeFloat, .Float, .ComptimeInt => {},
- else => return self.fail(
- scope,
- inst.positionals.rhs.src,
- "expected float type, found '{}'",
- .{operand.ty},
- ),
- }
-
- if (operand.value() != null) {
- return self.coerce(scope, dest_type, operand);
- } else if (dest_is_comptime_float) {
- return self.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_float'", .{});
- }
-
- return self.fail(scope, inst.base.src, "TODO implement analyze widen or shorten float", .{});
-}
-
-fn analyzeInstElemPtr(self: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) InnerError!*Inst {
- const array_ptr = try self.resolveInst(scope, inst.positionals.array_ptr);
- const uncasted_index = try self.resolveInst(scope, inst.positionals.index);
- const elem_index = try self.coerce(scope, Type.initTag(.usize), uncasted_index);
-
- if (array_ptr.ty.isSinglePointer() and array_ptr.ty.elemType().zigTypeTag() == .Array) {
- if (array_ptr.value()) |array_ptr_val| {
- if (elem_index.value()) |index_val| {
- // Both array pointer and index are compile-time known.
- const index_u64 = index_val.toUnsignedInt();
- // @intCast here because it would have been impossible to construct a value that
- // required a larger index.
- const elem_ptr = try array_ptr_val.elemPtr(scope.arena(), @intCast(usize, index_u64));
-
- const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer);
- type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() };
-
- return self.constInst(scope, inst.base.src, .{
- .ty = Type.initPayload(&type_payload.base),
- .val = elem_ptr,
- });
- }
- }
- }
-
- return self.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{});
-}
-
-fn floatOpAllowed(tag: zir.Inst.Tag) bool {
- // extend this swich as additional operators are implemented
- return switch (tag) {
- .add, .sub => true,
- else => false,
- };
-}
-
-fn analyzeInstArithmetic(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const lhs = try self.resolveInst(scope, inst.positionals.lhs);
- const rhs = try self.resolveInst(scope, inst.positionals.rhs);
-
- const instructions = &[_]*Inst{ lhs, rhs };
- const resolved_type = try self.resolvePeerTypes(scope, instructions);
- const casted_lhs = try self.coerce(scope, resolved_type, lhs);
- const casted_rhs = try self.coerce(scope, resolved_type, rhs);
-
- const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
- resolved_type.elemType()
- else
- resolved_type;
-
- const scalar_tag = scalar_type.zigTypeTag();
-
- if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) {
- if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
- return self.fail(scope, inst.base.src, "vector length mismatch: {} and {}", .{
- lhs.ty.arrayLen(),
- rhs.ty.arrayLen(),
- });
- }
- return self.fail(scope, inst.base.src, "TODO implement support for vectors in analyzeInstBinOp", .{});
- } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) {
- return self.fail(scope, inst.base.src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{
- lhs.ty,
- rhs.ty,
- });
- }
-
- const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
- const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat;
-
- if (!is_int and !(is_float and floatOpAllowed(inst.base.tag))) {
- return self.fail(scope, inst.base.src, "invalid operands to binary expression: '{}' and '{}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) });
- }
-
- if (casted_lhs.value()) |lhs_val| {
- if (casted_rhs.value()) |rhs_val| {
- return self.analyzeInstComptimeOp(scope, scalar_type, inst, lhs_val, rhs_val);
- }
- }
-
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- const ir_tag = switch (inst.base.tag) {
- .add => Inst.Tag.add,
- .sub => Inst.Tag.sub,
- else => return self.fail(scope, inst.base.src, "TODO implement arithmetic for operand '{}''", .{@tagName(inst.base.tag)}),
- };
-
- return self.addBinOp(b, inst.base.src, scalar_type, ir_tag, casted_lhs, casted_rhs);
-}
-
-/// Analyzes operands that are known at comptime
-fn analyzeInstComptimeOp(self: *Module, scope: *Scope, res_type: Type, inst: *zir.Inst.BinOp, lhs_val: Value, rhs_val: Value) InnerError!*Inst {
- // incase rhs is 0, simply return lhs without doing any calculations
- // TODO Once division is implemented we should throw an error when dividing by 0.
- if (rhs_val.tag() == .zero or rhs_val.tag() == .the_one_possible_value) {
- return self.constInst(scope, inst.base.src, .{
- .ty = res_type,
- .val = lhs_val,
- });
- }
- const is_int = res_type.isInt() or res_type.zigTypeTag() == .ComptimeInt;
-
- const value = try switch (inst.base.tag) {
- .add => blk: {
- const val = if (is_int)
- intAdd(scope.arena(), lhs_val, rhs_val)
- else
- self.floatAdd(scope, res_type, inst, lhs_val, rhs_val);
- break :blk val;
- },
- .sub => blk: {
- const val = if (is_int)
- intSub(scope.arena(), lhs_val, rhs_val)
- else
- self.floatSub(scope, res_type, inst, lhs_val, rhs_val);
- break :blk val;
- },
- else => return self.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{}'", .{@tagName(inst.base.tag)}),
- };
-
- return self.constInst(scope, inst.base.src, .{
- .ty = res_type,
- .val = value,
- });
-}
-
-fn analyzeInstDeref(self: *Module, scope: *Scope, deref: *zir.Inst.UnOp) InnerError!*Inst {
- const ptr = try self.resolveInst(scope, deref.positionals.operand);
- return self.analyzeDeref(scope, deref.base.src, ptr, deref.positionals.operand.src);
-}
-
-fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: usize) InnerError!*Inst {
+pub fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: usize) InnerError!*Inst {
const elem_ty = switch (ptr.ty.zigTypeTag()) {
.Pointer => ptr.ty.elemType(),
else => return self.fail(scope, ptr_src, "expected pointer, found '{}'", .{ptr.ty}),
@@ -3154,164 +2175,19 @@ fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: u
});
}
- return self.fail(scope, src, "TODO implement runtime deref", .{});
-}
-
-fn analyzeInstAsm(self: *Module, scope: *Scope, assembly: *zir.Inst.Asm) InnerError!*Inst {
- const return_type = try self.resolveType(scope, assembly.positionals.return_type);
- const asm_source = try self.resolveConstString(scope, assembly.positionals.asm_source);
- const output = if (assembly.kw_args.output) |o| try self.resolveConstString(scope, o) else null;
-
- const inputs = try scope.arena().alloc([]const u8, assembly.kw_args.inputs.len);
- const clobbers = try scope.arena().alloc([]const u8, assembly.kw_args.clobbers.len);
- const args = try scope.arena().alloc(*Inst, assembly.kw_args.args.len);
-
- for (inputs) |*elem, i| {
- elem.* = try self.resolveConstString(scope, assembly.kw_args.inputs[i]);
- }
- for (clobbers) |*elem, i| {
- elem.* = try self.resolveConstString(scope, assembly.kw_args.clobbers[i]);
- }
- for (args) |*elem, i| {
- const arg = try self.resolveInst(scope, assembly.kw_args.args[i]);
- elem.* = try self.coerce(scope, Type.initTag(.usize), arg);
- }
-
- const b = try self.requireRuntimeBlock(scope, assembly.base.src);
- const inst = try b.arena.create(Inst.Assembly);
- inst.* = .{
- .base = .{
- .tag = .assembly,
- .ty = return_type,
- .src = assembly.base.src,
- },
- .asm_source = asm_source,
- .is_volatile = assembly.kw_args.@"volatile",
- .output = output,
- .inputs = inputs,
- .clobbers = clobbers,
- .args = args,
- };
- try b.instructions.append(self.gpa, &inst.base);
- return &inst.base;
-}
-
-fn analyzeInstCmp(
- self: *Module,
- scope: *Scope,
- inst: *zir.Inst.BinOp,
- op: std.math.CompareOperator,
-) InnerError!*Inst {
- const lhs = try self.resolveInst(scope, inst.positionals.lhs);
- const rhs = try self.resolveInst(scope, inst.positionals.rhs);
-
- const is_equality_cmp = switch (op) {
- .eq, .neq => true,
- else => false,
- };
- const lhs_ty_tag = lhs.ty.zigTypeTag();
- const rhs_ty_tag = rhs.ty.zigTypeTag();
- if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {
- // null == null, null != null
- return self.constBool(scope, inst.base.src, op == .eq);
- } else if (is_equality_cmp and
- ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
- rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
- {
- // comparing null with optionals
- const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs;
- if (opt_operand.value()) |opt_val| {
- const is_null = opt_val.isNull();
- return self.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null);
- }
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- const inst_tag: Inst.Tag = switch (op) {
- .eq => .isnull,
- .neq => .isnonnull,
- else => unreachable,
- };
- return self.addUnOp(b, inst.base.src, Type.initTag(.bool), inst_tag, opt_operand);
- } else if (is_equality_cmp and
- ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))
- {
- return self.fail(scope, inst.base.src, "TODO implement C pointer cmp", .{});
- } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
- const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty;
- return self.fail(scope, inst.base.src, "comparison of '{}' with null", .{non_null_type});
- } else if (is_equality_cmp and
- ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or
- (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))
- {
- return self.fail(scope, inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
- } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
- if (!is_equality_cmp) {
- return self.fail(scope, inst.base.src, "{} operator not allowed for errors", .{@tagName(op)});
- }
- return self.fail(scope, inst.base.src, "TODO implement equality comparison between errors", .{});
- } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) {
- // This operation allows any combination of integer and float types, regardless of the
- // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
- // numeric types.
- return self.cmpNumeric(scope, inst.base.src, lhs, rhs, op);
- }
- return self.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{});
-}
-
-fn analyzeInstBoolNot(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const uncasted_operand = try self.resolveInst(scope, inst.positionals.operand);
- const bool_type = Type.initTag(.bool);
- const operand = try self.coerce(scope, bool_type, uncasted_operand);
- if (try self.resolveDefinedValue(scope, operand)) |val| {
- return self.constBool(scope, inst.base.src, !val.toBool());
- }
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addUnOp(b, inst.base.src, bool_type, .not, operand);
-}
-
-fn analyzeInstIsNonNull(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst {
- const operand = try self.resolveInst(scope, inst.positionals.operand);
- return self.analyzeIsNull(scope, inst.base.src, operand, invert_logic);
+ const b = try self.requireRuntimeBlock(scope, src);
+ return self.addUnOp(b, src, elem_ty, .load, ptr);
}
-fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst {
- const uncasted_cond = try self.resolveInst(scope, inst.positionals.condition);
- const cond = try self.coerce(scope, Type.initTag(.bool), uncasted_cond);
-
- if (try self.resolveDefinedValue(scope, cond)) |cond_val| {
- const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body;
- try self.analyzeBody(scope, body.*);
- return self.constVoid(scope, inst.base.src);
- }
-
- const parent_block = try self.requireRuntimeBlock(scope, inst.base.src);
-
- var true_block: Scope.Block = .{
- .parent = parent_block,
- .func = parent_block.func,
- .decl = parent_block.decl,
- .instructions = .{},
- .arena = parent_block.arena,
- };
- defer true_block.instructions.deinit(self.gpa);
- try self.analyzeBody(&true_block.base, inst.positionals.then_body);
-
- var false_block: Scope.Block = .{
- .parent = parent_block,
- .func = parent_block.func,
- .decl = parent_block.decl,
- .instructions = .{},
- .arena = parent_block.arena,
- };
- defer false_block.instructions.deinit(self.gpa);
- try self.analyzeBody(&false_block.base, inst.positionals.else_body);
-
- const then_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) };
- const else_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) };
- return self.addCondBr(parent_block, inst.base.src, cond, then_body, else_body);
+pub fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst {
+ const decl = self.lookupDeclName(scope, decl_name) orelse
+ return self.fail(scope, src, "decl '{}' not found", .{decl_name});
+ return self.analyzeDeclRef(scope, src, decl);
}
-fn wantSafety(self: *Module, scope: *Scope) bool {
- return switch (self.optimize_mode) {
+pub fn wantSafety(self: *Module, scope: *Scope) bool {
+ // TODO take into account scope's safety overrides
+ return switch (self.optimizeMode()) {
.Debug => true,
.ReleaseSafe => true,
.ReleaseFast => false,
@@ -3319,33 +2195,12 @@ fn wantSafety(self: *Module, scope: *Scope) bool {
};
}
-fn analyzeInstUnreachable(self: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst {
- const b = try self.requireRuntimeBlock(scope, unreach.base.src);
- if (self.wantSafety(scope)) {
- // TODO Once we have a panic function to call, call it here instead of this.
- _ = try self.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint);
- }
- return self.addNoOp(b, unreach.base.src, Type.initTag(.noreturn), .unreach);
-}
-
-fn analyzeInstRet(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
- const operand = try self.resolveInst(scope, inst.positionals.operand);
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand);
-}
-
-fn analyzeInstRetVoid(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid);
-}
-
-fn analyzeBody(self: *Module, scope: *Scope, body: zir.Module.Body) !void {
- for (body.instructions) |src_inst| {
- src_inst.analyzed_inst = try self.analyzeInst(scope, src_inst);
- }
+pub fn analyzeUnreach(self: *Module, scope: *Scope, src: usize) InnerError!*Inst {
+ const b = try self.requireRuntimeBlock(scope, src);
+ return self.addNoOp(b, src, Type.initTag(.noreturn), .unreach);
}
-fn analyzeIsNull(
+pub fn analyzeIsNull(
self: *Module,
scope: *Scope,
src: usize,
@@ -3356,7 +2211,7 @@ fn analyzeIsNull(
}
/// Asserts that lhs and rhs types are both numeric.
-fn cmpNumeric(
+pub fn cmpNumeric(
self: *Module,
scope: *Scope,
src: usize,
@@ -3539,7 +2394,7 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type {
}
}
-fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type {
+pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type {
if (instructions.len == 0)
return Type.initTag(.noreturn);
@@ -3579,7 +2434,7 @@ fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type {
return prev_inst.ty;
}
-fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
+pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
// If the types are the same, we can return the operand.
if (dest_type.eql(inst.ty))
return inst;
@@ -3599,7 +2454,7 @@ fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
// *[N]T to []T
if (inst.ty.isSinglePointer() and dest_type.isSlice() and
- (!inst.ty.pointerIsConst() or dest_type.pointerIsConst()))
+ (!inst.ty.isConstPtr() or dest_type.isConstPtr()))
{
const array_type = inst.ty.elemType();
const dst_elem_type = dest_type.elemType();
@@ -3675,7 +2530,23 @@ fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
return self.fail(scope, inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type });
}
-fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
+pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_value: *Inst) !*Inst {
+ if (ptr.ty.isConstPtr())
+ return self.fail(scope, src, "cannot assign to constant", .{});
+
+ const elem_ty = ptr.ty.elemType();
+ const value = try self.coerce(scope, elem_ty, uncasted_value);
+ if (elem_ty.onePossibleValue() != null)
+ return self.constVoid(scope, src);
+
+ // TODO handle comptime pointer writes
+ // TODO handle if the element type requires comptime
+
+ const b = try self.requireRuntimeBlock(scope, src);
+ return self.addBinOp(b, src, Type.initTag(.void), .store, ptr, value);
+}
+
+pub fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
if (inst.value()) |val| {
// Keep the comptime Value representation; take the new type.
return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
@@ -3822,7 +2693,7 @@ fn srcHashEql(a: std.zig.SrcHash, b: std.zig.SrcHash) bool {
return @bitCast(u128, a) == @bitCast(u128, b);
}
-fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
+pub fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
// TODO is this a performance issue? maybe we should try the operation without
// resorting to BigInt first.
var lhs_space: Value.BigIntSpace = undefined;
@@ -3850,7 +2721,7 @@ fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
return Value.initPayload(val_payload);
}
-fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
+pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
// TODO is this a performance issue? maybe we should try the operation without
// resorting to BigInt first.
var lhs_space: Value.BigIntSpace = undefined;
@@ -3878,7 +2749,7 @@ fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
return Value.initPayload(val_payload);
}
-fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinOp, lhs: Value, rhs: Value) !Value {
+pub fn floatAdd(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value {
var bit_count = switch (float_type.tag()) {
.comptime_float => 128,
else => float_type.floatBits(self.target()),
@@ -3887,7 +2758,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO
const allocator = scope.arena();
const val_payload = switch (bit_count) {
16 => {
- return self.fail(scope, inst.base.src, "TODO Implement addition for soft floats", .{});
+ return self.fail(scope, src, "TODO Implement addition for soft floats", .{});
},
32 => blk: {
const lhs_val = lhs.toFloat(f32);
@@ -3904,7 +2775,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO
break :blk &val_payload.base;
},
128 => blk: {
- return self.fail(scope, inst.base.src, "TODO Implement addition for big floats", .{});
+ return self.fail(scope, src, "TODO Implement addition for big floats", .{});
},
else => unreachable,
};
@@ -3912,7 +2783,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO
return Value.initPayload(val_payload);
}
-fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinOp, lhs: Value, rhs: Value) !Value {
+pub fn floatSub(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value {
var bit_count = switch (float_type.tag()) {
.comptime_float => 128,
else => float_type.floatBits(self.target()),
@@ -3921,7 +2792,7 @@ fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO
const allocator = scope.arena();
const val_payload = switch (bit_count) {
16 => {
- return self.fail(scope, inst.base.src, "TODO Implement substraction for soft floats", .{});
+ return self.fail(scope, src, "TODO Implement substraction for soft floats", .{});
},
32 => blk: {
const lhs_val = lhs.toFloat(f32);
@@ -3938,10 +2809,54 @@ fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO
break :blk &val_payload.base;
},
128 => blk: {
- return self.fail(scope, inst.base.src, "TODO Implement substraction for big floats", .{});
+ return self.fail(scope, src, "TODO Implement substraction for big floats", .{});
},
else => unreachable,
};
return Value.initPayload(val_payload);
}
+
+pub fn singleMutPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) error{OutOfMemory}!Type {
+ const type_payload = try scope.arena().create(Type.Payload.SingleMutPointer);
+ type_payload.* = .{ .pointee_type = elem_ty };
+ return Type.initPayload(&type_payload.base);
+}
+
+pub fn singleConstPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) error{OutOfMemory}!Type {
+ const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer);
+ type_payload.* = .{ .pointee_type = elem_ty };
+ return Type.initPayload(&type_payload.base);
+}
+
+pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void {
+ const zir_module = scope.namespace();
+ const source = zir_module.getSource(self) catch @panic("dumpInst failed to get source");
+ const loc = std.zig.findLineColumn(source, inst.src);
+ if (inst.tag == .constant) {
+ std.debug.print("constant ty={} val={} src={}:{}:{}\n", .{
+ inst.ty,
+ inst.castTag(.constant).?.val,
+ zir_module.subFilePath(),
+ loc.line + 1,
+ loc.column + 1,
+ });
+ } else if (inst.deaths == 0) {
+ std.debug.print("{} ty={} src={}:{}:{}\n", .{
+ @tagName(inst.tag),
+ inst.ty,
+ zir_module.subFilePath(),
+ loc.line + 1,
+ loc.column + 1,
+ });
+ } else {
+ std.debug.print("{} ty={} deaths={b} src={}:{}:{}\n", .{
+ @tagName(inst.tag),
+ inst.ty,
+ inst.deaths,
+ zir_module.subFilePath(),
+ loc.line + 1,
+ loc.column + 1,
+ });
+ }
+}
diff --git a/src-self-hosted/Package.zig b/src-self-hosted/Package.zig
@@ -1,8 +1,11 @@
pub const Table = std.StringHashMap(*Package);
+/// This should be used for file operations.
root_src_dir: std.fs.Dir,
-/// Relative to `root_src_dir`.
-root_src_path: []const u8,
+/// This is for metadata purposes, for example putting into debug information.
+root_src_dir_path: []u8,
+/// Relative to `root_src_dir` and `root_src_dir_path`.
+root_src_path: []u8,
table: Table,
/// No references to `root_src_dir` and `root_src_path` are kept.
@@ -18,8 +21,11 @@ pub fn create(
errdefer allocator.destroy(ptr);
const root_src_path_dupe = try mem.dupe(allocator, u8, root_src_path);
errdefer allocator.free(root_src_path_dupe);
+ const root_src_dir_path = try mem.dupe(allocator, u8, root_src_dir);
+ errdefer allocator.free(root_src_dir_path);
ptr.* = .{
.root_src_dir = try base_dir.openDir(root_src_dir, .{}),
+ .root_src_dir_path = root_src_dir_path,
.root_src_path = root_src_path_dupe,
.table = Table.init(allocator),
};
@@ -30,6 +36,7 @@ pub fn destroy(self: *Package) void {
const allocator = self.table.allocator;
self.root_src_dir.close();
allocator.free(self.root_src_path);
+ allocator.free(self.root_src_dir_path);
{
var it = self.table.iterator();
while (it.next()) |kv| {
@@ -41,10 +48,9 @@ pub fn destroy(self: *Package) void {
}
pub fn add(self: *Package, name: []const u8, package: *Package) !void {
+ try self.table.ensureCapacity(self.table.items().len + 1);
const name_dupe = try mem.dupe(self.table.allocator, u8, name);
- errdefer self.table.allocator.deinit(name_dupe);
- const entry = try self.table.put(name_dupe, package);
- assert(entry == null);
+ self.table.putAssumeCapacityNoClobber(name_dupe, package);
}
const std = @import("std");
diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig
@@ -17,6 +17,9 @@ pub const ResultLoc = union(enum) {
discard,
/// The expression has an inferred type, and it will be evaluated as an rvalue.
none,
+ /// The expression must generate a pointer rather than a value. For example, the left hand side
+ /// of an assignment uses an "LValue" result location.
+ lvalue,
/// The expression will be type coerced into this type, but it will be evaluated as an rvalue.
ty: *zir.Inst,
/// The expression must store its result into this typed pointer.
@@ -33,7 +36,7 @@ pub const ResultLoc = union(enum) {
pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*zir.Inst {
const type_src = scope.tree().token_locs[type_node.firstToken()].start;
- const type_type = try mod.addZIRInstConst(scope, type_src, .{
+ const type_type = try addZIRInstConst(mod, scope, type_src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.type_type),
});
@@ -46,18 +49,45 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
switch (node.tag) {
.VarDecl => unreachable, // Handled in `blockExpr`.
.Assign => unreachable, // Handled in `blockExpr`.
-
- .Add => return arithmetic(mod, scope, rl, node.castTag(.Add).?, .add),
- .Sub => return arithmetic(mod, scope, rl, node.castTag(.Sub).?, .sub),
-
- .BangEqual => return cmp(mod, scope, rl, node.castTag(.BangEqual).?, .cmp_neq),
- .EqualEqual => return cmp(mod, scope, rl, node.castTag(.EqualEqual).?, .cmp_eq),
- .GreaterThan => return cmp(mod, scope, rl, node.castTag(.GreaterThan).?, .cmp_gt),
- .GreaterOrEqual => return cmp(mod, scope, rl, node.castTag(.GreaterOrEqual).?, .cmp_gte),
- .LessThan => return cmp(mod, scope, rl, node.castTag(.LessThan).?, .cmp_lt),
- .LessOrEqual => return cmp(mod, scope, rl, node.castTag(.LessOrEqual).?, .cmp_lte),
-
- .Identifier => return rlWrap(mod, scope, rl, try identifier(mod, scope, node.castTag(.Identifier).?)),
+ .AssignBitAnd => unreachable, // Handled in `blockExpr`.
+ .AssignBitOr => unreachable, // Handled in `blockExpr`.
+ .AssignBitShiftLeft => unreachable, // Handled in `blockExpr`.
+ .AssignBitShiftRight => unreachable, // Handled in `blockExpr`.
+ .AssignBitXor => unreachable, // Handled in `blockExpr`.
+ .AssignDiv => unreachable, // Handled in `blockExpr`.
+ .AssignSub => unreachable, // Handled in `blockExpr`.
+ .AssignSubWrap => unreachable, // Handled in `blockExpr`.
+ .AssignMod => unreachable, // Handled in `blockExpr`.
+ .AssignAdd => unreachable, // Handled in `blockExpr`.
+ .AssignAddWrap => unreachable, // Handled in `blockExpr`.
+ .AssignMul => unreachable, // Handled in `blockExpr`.
+ .AssignMulWrap => unreachable, // Handled in `blockExpr`.
+
+ .Add => return simpleBinOp(mod, scope, rl, node.castTag(.Add).?, .add),
+ .AddWrap => return simpleBinOp(mod, scope, rl, node.castTag(.AddWrap).?, .addwrap),
+ .Sub => return simpleBinOp(mod, scope, rl, node.castTag(.Sub).?, .sub),
+ .SubWrap => return simpleBinOp(mod, scope, rl, node.castTag(.SubWrap).?, .subwrap),
+ .Mul => return simpleBinOp(mod, scope, rl, node.castTag(.Mul).?, .mul),
+ .MulWrap => return simpleBinOp(mod, scope, rl, node.castTag(.MulWrap).?, .mulwrap),
+ .Div => return simpleBinOp(mod, scope, rl, node.castTag(.Div).?, .div),
+ .Mod => return simpleBinOp(mod, scope, rl, node.castTag(.Mod).?, .mod_rem),
+ .BitAnd => return simpleBinOp(mod, scope, rl, node.castTag(.BitAnd).?, .bitand),
+ .BitOr => return simpleBinOp(mod, scope, rl, node.castTag(.BitOr).?, .bitor),
+ .BitShiftLeft => return simpleBinOp(mod, scope, rl, node.castTag(.BitShiftLeft).?, .shl),
+ .BitShiftRight => return simpleBinOp(mod, scope, rl, node.castTag(.BitShiftRight).?, .shr),
+ .BitXor => return simpleBinOp(mod, scope, rl, node.castTag(.BitXor).?, .xor),
+
+ .BangEqual => return simpleBinOp(mod, scope, rl, node.castTag(.BangEqual).?, .cmp_neq),
+ .EqualEqual => return simpleBinOp(mod, scope, rl, node.castTag(.EqualEqual).?, .cmp_eq),
+ .GreaterThan => return simpleBinOp(mod, scope, rl, node.castTag(.GreaterThan).?, .cmp_gt),
+ .GreaterOrEqual => return simpleBinOp(mod, scope, rl, node.castTag(.GreaterOrEqual).?, .cmp_gte),
+ .LessThan => return simpleBinOp(mod, scope, rl, node.castTag(.LessThan).?, .cmp_lt),
+ .LessOrEqual => return simpleBinOp(mod, scope, rl, node.castTag(.LessOrEqual).?, .cmp_lte),
+
+ .ArrayCat => return simpleBinOp(mod, scope, rl, node.castTag(.ArrayCat).?, .array_cat),
+ .ArrayMult => return simpleBinOp(mod, scope, rl, node.castTag(.ArrayMult).?, .array_mul),
+
+ .Identifier => return try identifier(mod, scope, rl, node.castTag(.Identifier).?),
.Asm => return rlWrap(mod, scope, rl, try assembly(mod, scope, node.castTag(.Asm).?)),
.StringLiteral => return rlWrap(mod, scope, rl, try stringLiteral(mod, scope, node.castTag(.StringLiteral).?)),
.IntegerLiteral => return rlWrap(mod, scope, rl, try integerLiteral(mod, scope, node.castTag(.IntegerLiteral).?)),
@@ -90,6 +120,8 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block
var scope = parent_scope;
for (block_node.statements()) |statement| {
+ const src = scope.tree().token_locs[statement.firstToken()].start;
+ _ = try addZIRNoOp(mod, scope, src, .dbg_stmt);
switch (statement.tag) {
.VarDecl => {
const var_decl_node = statement.castTag(.VarDecl).?;
@@ -99,10 +131,25 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block
const ass = statement.castTag(.Assign).?;
try assign(mod, scope, ass);
},
+ .AssignBitAnd => try assignOp(mod, scope, statement.castTag(.AssignBitAnd).?, .bitand),
+ .AssignBitOr => try assignOp(mod, scope, statement.castTag(.AssignBitOr).?, .bitor),
+ .AssignBitShiftLeft => try assignOp(mod, scope, statement.castTag(.AssignBitShiftLeft).?, .shl),
+ .AssignBitShiftRight => try assignOp(mod, scope, statement.castTag(.AssignBitShiftRight).?, .shr),
+ .AssignBitXor => try assignOp(mod, scope, statement.castTag(.AssignBitXor).?, .xor),
+ .AssignDiv => try assignOp(mod, scope, statement.castTag(.AssignDiv).?, .div),
+ .AssignSub => try assignOp(mod, scope, statement.castTag(.AssignSub).?, .sub),
+ .AssignSubWrap => try assignOp(mod, scope, statement.castTag(.AssignSubWrap).?, .subwrap),
+ .AssignMod => try assignOp(mod, scope, statement.castTag(.AssignMod).?, .mod_rem),
+ .AssignAdd => try assignOp(mod, scope, statement.castTag(.AssignAdd).?, .add),
+ .AssignAddWrap => try assignOp(mod, scope, statement.castTag(.AssignAddWrap).?, .addwrap),
+ .AssignMul => try assignOp(mod, scope, statement.castTag(.AssignMul).?, .mul),
+ .AssignMulWrap => try assignOp(mod, scope, statement.castTag(.AssignMulWrap).?, .mulwrap),
+
else => {
const possibly_unused_result = try expr(mod, scope, .none, statement);
- const src = scope.tree().token_locs[statement.firstToken()].start;
- _ = try mod.addZIRUnOp(scope, src, .ensure_result_used, possibly_unused_result);
+ if (!possibly_unused_result.tag.isNoReturn()) {
+ _ = try addZIRUnOp(mod, scope, src, .ensure_result_used, possibly_unused_result);
+ }
},
}
}
@@ -133,7 +180,7 @@ fn varDecl(
if (nodeMayNeedMemoryLocation(init_node)) {
if (node.getTrailer("type_node")) |type_node| {
const type_inst = try typeExpr(mod, scope, type_node);
- const alloc = try mod.addZIRUnOp(scope, name_src, .alloc, type_inst);
+ const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst);
const result_loc: ResultLoc = .{ .ptr = alloc };
const init_inst = try expr(mod, scope, result_loc, init_node);
const sub_scope = try block_arena.create(Scope.LocalVal);
@@ -145,7 +192,7 @@ fn varDecl(
};
return &sub_scope.base;
} else {
- const alloc = try mod.addZIRNoOpT(scope, name_src, .alloc_inferred);
+ const alloc = try addZIRNoOpT(mod, scope, name_src, .alloc_inferred);
const result_loc: ResultLoc = .{ .inferred_ptr = alloc };
const init_inst = try expr(mod, scope, result_loc, init_node);
const sub_scope = try block_arena.create(Scope.LocalVal);
@@ -176,7 +223,7 @@ fn varDecl(
.Keyword_var => {
if (node.getTrailer("type_node")) |type_node| {
const type_inst = try typeExpr(mod, scope, type_node);
- const alloc = try mod.addZIRUnOp(scope, name_src, .alloc, type_inst);
+ const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst);
const result_loc: ResultLoc = .{ .ptr = alloc };
const init_inst = try expr(mod, scope, result_loc, init_node);
const sub_scope = try block_arena.create(Scope.LocalPtr);
@@ -188,7 +235,7 @@ fn varDecl(
};
return &sub_scope.base;
} else {
- const alloc = try mod.addZIRNoOp(scope, name_src, .alloc_inferred);
+ const alloc = try addZIRNoOp(mod, scope, name_src, .alloc_inferred);
const result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred).? };
const init_inst = try expr(mod, scope, result_loc, init_node);
const sub_scope = try block_arena.create(Scope.LocalPtr);
@@ -207,28 +254,44 @@ fn varDecl(
fn assign(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) InnerError!void {
if (infix_node.lhs.castTag(.Identifier)) |ident| {
- const tree = scope.tree();
- const ident_name = try identifierTokenString(mod, scope, ident.token);
+ // This intentionally does not support @"_" syntax.
+ const ident_name = scope.tree().tokenSlice(ident.token);
if (std.mem.eql(u8, ident_name, "_")) {
_ = try expr(mod, scope, .discard, infix_node.rhs);
return;
- } else {
- return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{});
}
- } else {
- return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{});
}
+ const lvalue = try expr(mod, scope, .lvalue, infix_node.lhs);
+ _ = try expr(mod, scope, .{ .ptr = lvalue }, infix_node.rhs);
+}
+
+fn assignOp(
+ mod: *Module,
+ scope: *Scope,
+ infix_node: *ast.Node.SimpleInfixOp,
+ op_inst_tag: zir.Inst.Tag,
+) InnerError!void {
+ const lhs_ptr = try expr(mod, scope, .lvalue, infix_node.lhs);
+ const lhs = try addZIRUnOp(mod, scope, lhs_ptr.src, .deref, lhs_ptr);
+ const lhs_type = try addZIRUnOp(mod, scope, lhs_ptr.src, .typeof, lhs);
+ const rhs = try expr(mod, scope, .{ .ty = lhs_type }, infix_node.rhs);
+
+ const tree = scope.tree();
+ const src = tree.token_locs[infix_node.op_token].start;
+
+ const result = try addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs);
+ _ = try addZIRBinOp(mod, scope, src, .store, lhs_ptr, result);
}
fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[node.op_token].start;
- const bool_type = try mod.addZIRInstConst(scope, src, .{
+ const bool_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.bool_type),
});
const operand = try expr(mod, scope, .{ .ty = bool_type }, node.rhs);
- return mod.addZIRUnOp(scope, src, .boolnot, operand);
+ return addZIRUnOp(mod, scope, src, .boolnot, operand);
}
/// Identifier token -> String (allocated in scope.arena())
@@ -257,7 +320,7 @@ pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToke
const ident_name = try identifierTokenString(mod, scope, node.token);
- return mod.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{});
+ return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{});
}
fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst {
@@ -268,47 +331,31 @@ fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!
const lhs = try expr(mod, scope, .none, node.lhs);
const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?);
- const pointer = try mod.addZIRInst(scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{});
- return mod.addZIRUnOp(scope, src, .deref, pointer);
+ const pointer = try addZIRInst(mod, scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{});
+ return addZIRUnOp(mod, scope, src, .deref, pointer);
}
fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[node.rtoken].start;
const lhs = try expr(mod, scope, .none, node.lhs);
- return mod.addZIRUnOp(scope, src, .deref, lhs);
+ return addZIRUnOp(mod, scope, src, .deref, lhs);
}
-fn cmp(
+fn simpleBinOp(
mod: *Module,
scope: *Scope,
rl: ResultLoc,
infix_node: *ast.Node.SimpleInfixOp,
- cmp_inst_tag: zir.Inst.Tag,
+ op_inst_tag: zir.Inst.Tag,
) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[infix_node.op_token].start;
const lhs = try expr(mod, scope, .none, infix_node.lhs);
const rhs = try expr(mod, scope, .none, infix_node.rhs);
- const result = try mod.addZIRBinOp(scope, src, cmp_inst_tag, lhs, rhs);
- return rlWrap(mod, scope, rl, result);
-}
-
-fn arithmetic(
- mod: *Module,
- scope: *Scope,
- rl: ResultLoc,
- infix_node: *ast.Node.SimpleInfixOp,
- op_inst_tag: zir.Inst.Tag,
-) InnerError!*zir.Inst {
- const lhs = try expr(mod, scope, .none, infix_node.lhs);
- const rhs = try expr(mod, scope, .none, infix_node.rhs);
- const tree = scope.tree();
- const src = tree.token_locs[infix_node.op_token].start;
-
- const result = try mod.addZIRBinOp(scope, src, op_inst_tag, lhs, rhs);
+ const result = try addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs);
return rlWrap(mod, scope, rl, result);
}
@@ -331,19 +378,19 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
const tree = scope.tree();
const if_src = tree.token_locs[if_node.if_token].start;
- const bool_type = try mod.addZIRInstConst(scope, if_src, .{
+ const bool_type = try addZIRInstConst(mod, scope, if_src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.bool_type),
});
const cond = try expr(mod, &block_scope.base, .{ .ty = bool_type }, if_node.condition);
- const condbr = try mod.addZIRInstSpecial(&block_scope.base, if_src, zir.Inst.CondBr, .{
+ const condbr = try addZIRInstSpecial(mod, &block_scope.base, if_src, zir.Inst.CondBr, .{
.condition = cond,
.then_body = undefined, // populated below
.else_body = undefined, // populated below
}, .{});
- const block = try mod.addZIRInstBlock(scope, if_src, .{
+ const block = try addZIRInstBlock(mod, scope, if_src, .{
.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
});
var then_scope: Scope.GenZIR = .{
@@ -359,14 +406,14 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
// proper type inference requires peer type resolution on the if's
// branches.
const branch_rl: ResultLoc = switch (rl) {
- .discard, .none, .ty, .ptr => rl,
+ .discard, .none, .ty, .ptr, .lvalue => rl,
.inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },
};
const then_result = try expr(mod, &then_scope.base, branch_rl, if_node.body);
if (!then_result.tag.isNoReturn()) {
const then_src = tree.token_locs[if_node.body.lastToken()].start;
- _ = try mod.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{
+ _ = try addZIRInst(mod, &then_scope.base, then_src, zir.Inst.Break, .{
.block = block,
.operand = then_result,
}, .{});
@@ -387,7 +434,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
const else_result = try expr(mod, &else_scope.base, branch_rl, else_node.body);
if (!else_result.tag.isNoReturn()) {
const else_src = tree.token_locs[else_node.body.lastToken()].start;
- _ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{
+ _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.Break, .{
.block = block,
.operand = else_result,
}, .{});
@@ -396,7 +443,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
// TODO Optimization opportunity: we can avoid an allocation and a memcpy here
// by directly allocating the body for this one instruction.
const else_src = tree.token_locs[if_node.lastToken()].start;
- _ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.BreakVoid, .{
+ _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.BreakVoid, .{
.block = block,
}, .{});
}
@@ -412,20 +459,20 @@ fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerE
const src = tree.token_locs[cfe.ltoken].start;
if (cfe.getRHS()) |rhs_node| {
if (nodeMayNeedMemoryLocation(rhs_node)) {
- const ret_ptr = try mod.addZIRNoOp(scope, src, .ret_ptr);
+ const ret_ptr = try addZIRNoOp(mod, scope, src, .ret_ptr);
const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node);
- return mod.addZIRUnOp(scope, src, .@"return", operand);
+ return addZIRUnOp(mod, scope, src, .@"return", operand);
} else {
- const fn_ret_ty = try mod.addZIRNoOp(scope, src, .ret_type);
+ const fn_ret_ty = try addZIRNoOp(mod, scope, src, .ret_type);
const operand = try expr(mod, scope, .{ .ty = fn_ret_ty }, rhs_node);
- return mod.addZIRUnOp(scope, src, .@"return", operand);
+ return addZIRUnOp(mod, scope, src, .@"return", operand);
}
} else {
- return mod.addZIRNoOp(scope, src, .returnvoid);
+ return addZIRNoOp(mod, scope, src, .returnvoid);
}
}
-fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError!*zir.Inst {
+fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneToken) InnerError!*zir.Inst {
const tracy = trace(@src());
defer tracy.end();
@@ -437,7 +484,8 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError
}
if (getSimplePrimitiveValue(ident_name)) |typed_value| {
- return mod.addZIRInstConst(scope, src, typed_value);
+ const result = try addZIRInstConst(mod, scope, src, typed_value);
+ return rlWrap(mod, scope, rl, result);
}
if (ident_name.len >= 2) integer: {
@@ -461,16 +509,18 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError
else => {
const int_type_payload = try scope.arena().create(Value.Payload.IntType);
int_type_payload.* = .{ .signed = is_signed, .bits = bit_count };
- return mod.addZIRInstConst(scope, src, .{
+ const result = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.comptime_int),
.val = Value.initPayload(&int_type_payload.base),
});
+ return rlWrap(mod, scope, rl, result);
},
};
- return mod.addZIRInstConst(scope, src, .{
+ const result = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = val,
});
+ return rlWrap(mod, scope, rl, result);
}
}
@@ -481,14 +531,19 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError
.local_val => {
const local_val = s.cast(Scope.LocalVal).?;
if (mem.eql(u8, local_val.name, ident_name)) {
- return local_val.inst;
+ return rlWrap(mod, scope, rl, local_val.inst);
}
s = local_val.parent;
},
.local_ptr => {
const local_ptr = s.cast(Scope.LocalPtr).?;
if (mem.eql(u8, local_ptr.name, ident_name)) {
- return try mod.addZIRUnOp(scope, src, .deref, local_ptr.ptr);
+ if (rl == .lvalue) {
+ return local_ptr.ptr;
+ } else {
+ const result = try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr);
+ return rlWrap(mod, scope, rl, result);
+ }
}
s = local_ptr.parent;
},
@@ -498,7 +553,9 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError
}
if (mod.lookupDeclName(scope, ident_name)) |decl| {
- return try mod.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{});
+ // TODO handle lvalues
+ const result = try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{});
+ return rlWrap(mod, scope, rl, result);
}
return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name});
@@ -520,7 +577,7 @@ fn stringLiteral(mod: *Module, scope: *Scope, str_lit: *ast.Node.OneToken) Inner
};
const src = tree.token_locs[str_lit.token].start;
- return mod.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{});
+ return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{});
}
fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) InnerError!*zir.Inst {
@@ -545,7 +602,7 @@ fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) Inne
const int_payload = try arena.create(Value.Payload.Int_u64);
int_payload.* = .{ .int = small_int };
const src = tree.token_locs[int_lit.token].start;
- return mod.addZIRInstConst(scope, src, .{
+ return addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.comptime_int),
.val = Value.initPayload(&int_payload.base),
});
@@ -568,7 +625,7 @@ fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.OneToken) Inne
const float_payload = try arena.create(Value.Payload.Float_128);
float_payload.* = .{ .val = val };
const src = tree.token_locs[float_lit.token].start;
- return mod.addZIRInstConst(scope, src, .{
+ return addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.comptime_float),
.val = Value.initPayload(&float_payload.base),
});
@@ -578,7 +635,7 @@ fn undefLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerErro
const arena = scope.arena();
const tree = scope.tree();
const src = tree.token_locs[node.token].start;
- return mod.addZIRInstConst(scope, src, .{
+ return addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.@"undefined"),
.val = Value.initTag(.undef),
});
@@ -588,7 +645,7 @@ fn boolLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError
const arena = scope.arena();
const tree = scope.tree();
const src = tree.token_locs[node.token].start;
- return mod.addZIRInstConst(scope, src, .{
+ return addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.bool),
.val = switch (tree.token_ids[node.token]) {
.Keyword_true => Value.initTag(.bool_true),
@@ -602,7 +659,7 @@ fn nullLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError
const arena = scope.arena();
const tree = scope.tree();
const src = tree.token_locs[node.token].start;
- return mod.addZIRInstConst(scope, src, .{
+ return addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.@"null"),
.val = Value.initTag(.null_value),
});
@@ -620,7 +677,7 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi
const src = tree.token_locs[asm_node.asm_token].start;
- const str_type = try mod.addZIRInstConst(scope, src, .{
+ const str_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.const_slice_u8_type),
});
@@ -632,11 +689,11 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi
args[i] = try expr(mod, scope, .none, input.expr);
}
- const return_type = try mod.addZIRInstConst(scope, src, .{
+ const return_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.void_type),
});
- const asm_inst = try mod.addZIRInst(scope, src, zir.Inst.Asm, .{
+ const asm_inst = try addZIRInst(mod, scope, src, zir.Inst.Asm, .{
.asm_source = try expr(mod, scope, str_type_rl, asm_node.template),
.return_type = return_type,
}, .{
@@ -666,14 +723,14 @@ fn simpleCast(
try ensureBuiltinParamCount(mod, scope, call, 2);
const tree = scope.tree();
const src = tree.token_locs[call.builtin_token].start;
- const type_type = try mod.addZIRInstConst(scope, src, .{
+ const type_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.type_type),
});
const params = call.params();
const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]);
const rhs = try expr(mod, scope, .none, params[1]);
- const result = try mod.addZIRBinOp(scope, src, inst_tag, dest_type, rhs);
+ const result = try addZIRBinOp(mod, scope, src, inst_tag, dest_type, rhs);
return rlWrap(mod, scope, rl, result);
}
@@ -682,7 +739,7 @@ fn ptrToInt(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError
const operand = try expr(mod, scope, .none, call.params()[0]);
const tree = scope.tree();
const src = tree.token_locs[call.builtin_token].start;
- return mod.addZIRUnOp(scope, src, .ptrtoint, operand);
+ return addZIRUnOp(mod, scope, src, .ptrtoint, operand);
}
fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst {
@@ -695,15 +752,19 @@ fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) I
.none => return try expr(mod, scope, .{ .ty = dest_type }, params[1]),
.discard => {
const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]);
- _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result);
+ _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result);
return result;
},
+ .lvalue => {
+ const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]);
+ return addZIRUnOp(mod, scope, result.src, .ref, result);
+ },
.ty => |result_ty| {
const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]);
- return mod.addZIRBinOp(scope, src, .as, result_ty, result);
+ return addZIRBinOp(mod, scope, src, .as, result_ty, result);
},
.ptr => |result_ptr| {
- const casted_result_ptr = try mod.addZIRBinOp(scope, src, .coerce_result_ptr, dest_type, result_ptr);
+ const casted_result_ptr = try addZIRBinOp(mod, scope, src, .coerce_result_ptr, dest_type, result_ptr);
return expr(mod, scope, .{ .ptr = casted_result_ptr }, params[1]);
},
.bitcasted_ptr => |bitcasted_ptr| {
@@ -715,7 +776,7 @@ fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) I
return mod.failTok(scope, call.builtin_token, "TODO implement @as with inferred-type result location pointer", .{});
},
.block_ptr => |block_ptr| {
- const casted_block_ptr = try mod.addZIRInst(scope, src, zir.Inst.CoerceResultBlockPtr, .{
+ const casted_block_ptr = try addZIRInst(mod, scope, src, zir.Inst.CoerceResultBlockPtr, .{
.dest_type = dest_type,
.block = block_ptr,
}, .{});
@@ -728,7 +789,7 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa
try ensureBuiltinParamCount(mod, scope, call, 2);
const tree = scope.tree();
const src = tree.token_locs[call.builtin_token].start;
- const type_type = try mod.addZIRInstConst(scope, src, .{
+ const type_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.type_type),
});
@@ -737,21 +798,26 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa
switch (rl) {
.none => {
const operand = try expr(mod, scope, .none, params[1]);
- return mod.addZIRBinOp(scope, src, .bitcast, dest_type, operand);
+ return addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand);
},
.discard => {
const operand = try expr(mod, scope, .none, params[1]);
- const result = try mod.addZIRBinOp(scope, src, .bitcast, dest_type, operand);
- _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result);
+ const result = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand);
+ _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result);
+ return result;
+ },
+ .lvalue => {
+ const operand = try expr(mod, scope, .lvalue, params[1]);
+ const result = try addZIRBinOp(mod, scope, src, .bitcast_lvalue, dest_type, operand);
return result;
},
.ty => |result_ty| {
const result = try expr(mod, scope, .none, params[1]);
- const bitcasted = try mod.addZIRBinOp(scope, src, .bitcast, dest_type, result);
- return mod.addZIRBinOp(scope, src, .as, result_ty, bitcasted);
+ const bitcasted = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, result);
+ return addZIRBinOp(mod, scope, src, .as, result_ty, bitcasted);
},
.ptr => |result_ptr| {
- const casted_result_ptr = try mod.addZIRUnOp(scope, src, .bitcast_result_ptr, result_ptr);
+ const casted_result_ptr = try addZIRUnOp(mod, scope, src, .bitcast_result_ptr, result_ptr);
return expr(mod, scope, .{ .bitcasted_ptr = casted_result_ptr.castTag(.bitcast_result_ptr).? }, params[1]);
},
.bitcasted_ptr => |bitcasted_ptr| {
@@ -799,7 +865,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In
const args = try scope.getGenZIR().arena.alloc(*zir.Inst, param_nodes.len);
for (param_nodes) |param_node, i| {
const param_src = tree.token_locs[param_node.firstToken()].start;
- const param_type = try mod.addZIRInst(scope, param_src, zir.Inst.ParamType, .{
+ const param_type = try addZIRInst(mod, scope, param_src, zir.Inst.ParamType, .{
.func = lhs,
.arg_index = i,
}, .{});
@@ -807,7 +873,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In
}
const src = tree.token_locs[node.lhs.firstToken()].start;
- const result = try mod.addZIRInst(scope, src, zir.Inst.Call, .{
+ const result = try addZIRInst(mod, scope, src, zir.Inst.Call, .{
.func = lhs,
.args = args,
}, .{});
@@ -818,7 +884,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In
fn unreach(mod: *Module, scope: *Scope, unreach_node: *ast.Node.OneToken) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[unreach_node.token].start;
- return mod.addZIRNoOp(scope, src, .@"unreachable");
+ return addZIRNoOp(mod, scope, src, .@"unreachable");
}
fn getSimplePrimitiveValue(name: []const u8) ?TypedValue {
@@ -1000,19 +1066,20 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr
.none => return result,
.discard => {
// Emit a compile error for discarding error values.
- _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result);
+ _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result);
return result;
},
- .ty => |ty_inst| return mod.addZIRBinOp(scope, result.src, .as, ty_inst, result),
+ .lvalue => {
+ // We need a pointer but we have a value.
+ return addZIRUnOp(mod, scope, result.src, .ref, result);
+ },
+ .ty => |ty_inst| return addZIRBinOp(mod, scope, result.src, .as, ty_inst, result),
.ptr => |ptr_inst| {
- const casted_result = try mod.addZIRInst(scope, result.src, zir.Inst.CoerceToPtrElem, .{
+ const casted_result = try addZIRInst(mod, scope, result.src, zir.Inst.CoerceToPtrElem, .{
.ptr = ptr_inst,
.value = result,
}, .{});
- _ = try mod.addZIRInst(scope, result.src, zir.Inst.Store, .{
- .ptr = ptr_inst,
- .value = casted_result,
- }, .{});
+ _ = try addZIRBinOp(mod, scope, result.src, .store, ptr_inst, casted_result);
return casted_result;
},
.bitcasted_ptr => |bitcasted_ptr| {
@@ -1026,3 +1093,121 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr
},
}
}
+
+pub fn addZIRInstSpecial(
+ mod: *Module,
+ scope: *Scope,
+ src: usize,
+ comptime T: type,
+ positionals: std.meta.fieldInfo(T, "positionals").field_type,
+ kw_args: std.meta.fieldInfo(T, "kw_args").field_type,
+) !*T {
+ const gen_zir = scope.getGenZIR();
+ try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
+ const inst = try gen_zir.arena.create(T);
+ inst.* = .{
+ .base = .{
+ .tag = T.base_tag,
+ .src = src,
+ },
+ .positionals = positionals,
+ .kw_args = kw_args,
+ };
+ gen_zir.instructions.appendAssumeCapacity(&inst.base);
+ return inst;
+}
+
+pub fn addZIRNoOpT(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst.NoOp {
+ const gen_zir = scope.getGenZIR();
+ try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
+ const inst = try gen_zir.arena.create(zir.Inst.NoOp);
+ inst.* = .{
+ .base = .{
+ .tag = tag,
+ .src = src,
+ },
+ .positionals = .{},
+ .kw_args = .{},
+ };
+ gen_zir.instructions.appendAssumeCapacity(&inst.base);
+ return inst;
+}
+
+pub fn addZIRNoOp(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst {
+ const inst = try addZIRNoOpT(mod, scope, src, tag);
+ return &inst.base;
+}
+
+pub fn addZIRUnOp(
+ mod: *Module,
+ scope: *Scope,
+ src: usize,
+ tag: zir.Inst.Tag,
+ operand: *zir.Inst,
+) !*zir.Inst {
+ const gen_zir = scope.getGenZIR();
+ try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
+ const inst = try gen_zir.arena.create(zir.Inst.UnOp);
+ inst.* = .{
+ .base = .{
+ .tag = tag,
+ .src = src,
+ },
+ .positionals = .{
+ .operand = operand,
+ },
+ .kw_args = .{},
+ };
+ gen_zir.instructions.appendAssumeCapacity(&inst.base);
+ return &inst.base;
+}
+
+pub fn addZIRBinOp(
+ mod: *Module,
+ scope: *Scope,
+ src: usize,
+ tag: zir.Inst.Tag,
+ lhs: *zir.Inst,
+ rhs: *zir.Inst,
+) !*zir.Inst {
+ const gen_zir = scope.getGenZIR();
+ try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
+ const inst = try gen_zir.arena.create(zir.Inst.BinOp);
+ inst.* = .{
+ .base = .{
+ .tag = tag,
+ .src = src,
+ },
+ .positionals = .{
+ .lhs = lhs,
+ .rhs = rhs,
+ },
+ .kw_args = .{},
+ };
+ gen_zir.instructions.appendAssumeCapacity(&inst.base);
+ return &inst.base;
+}
+
+pub fn addZIRInst(
+ mod: *Module,
+ scope: *Scope,
+ src: usize,
+ comptime T: type,
+ positionals: std.meta.fieldInfo(T, "positionals").field_type,
+ kw_args: std.meta.fieldInfo(T, "kw_args").field_type,
+) !*zir.Inst {
+ const inst_special = try addZIRInstSpecial(mod, scope, src, T, positionals, kw_args);
+ return &inst_special.base;
+}
+
+/// TODO The existence of this function is a workaround for a bug in stage1.
+pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst {
+ const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type;
+ return addZIRInst(mod, scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{});
+}
+
+/// TODO The existence of this function is a workaround for a bug in stage1.
+pub fn addZIRInstBlock(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block {
+ const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type;
+ return addZIRInstSpecial(mod, scope, src, zir.Inst.Block, P{ .body = body }, .{});
+}
diff --git a/src-self-hosted/clang.zig b/src-self-hosted/clang.zig
@@ -828,6 +828,14 @@ pub const ZigClangExpr_ConstExprUsage = extern enum {
EvaluateForMangling,
};
+pub const ZigClangUnaryExprOrTypeTrait_Kind = extern enum {
+ SizeOf,
+ AlignOf,
+ VecStep,
+ OpenMPRequiredSimdAlign,
+ PreferredAlignOf,
+};
+
pub extern fn ZigClangSourceManager_getSpellingLoc(self: ?*const struct_ZigClangSourceManager, Loc: struct_ZigClangSourceLocation) struct_ZigClangSourceLocation;
pub extern fn ZigClangSourceManager_getFilename(self: *const struct_ZigClangSourceManager, SpellingLoc: struct_ZigClangSourceLocation) ?[*:0]const u8;
pub extern fn ZigClangSourceManager_getSpellingLineNumber(self: ?*const struct_ZigClangSourceManager, Loc: struct_ZigClangSourceLocation) c_uint;
@@ -1225,6 +1233,7 @@ pub extern fn ZigClangCallExpr_getArgs(*const ZigClangCallExpr) [*]const *const
pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangQualType;
pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangSourceLocation;
+pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getKind(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangUnaryExprOrTypeTrait_Kind;
pub extern fn ZigClangUnaryOperator_getOpcode(*const ZigClangUnaryOperator) ZigClangUO;
pub extern fn ZigClangUnaryOperator_getType(*const ZigClangUnaryOperator) ZigClangQualType;
diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig
@@ -1,5 +1,6 @@
const std = @import("std");
const mem = std.mem;
+const math = std.math;
const assert = std.debug.assert;
const ir = @import("ir.zig");
const Type = @import("type.zig").Type;
@@ -11,6 +12,11 @@ const ErrorMsg = Module.ErrorMsg;
const Target = std.Target;
const Allocator = mem.Allocator;
const trace = @import("tracy.zig").trace;
+const DW = std.dwarf;
+const leb128 = std.debug.leb;
+
+// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented.
+// zig fmt: off
/// The codegen-related data that is stored in `ir.Inst.Block` instructions.
pub const BlockData = struct {
@@ -43,64 +49,66 @@ pub fn generateSymbol(
src: usize,
typed_value: TypedValue,
code: *std.ArrayList(u8),
+ dbg_line: *std.ArrayList(u8),
) GenerateSymbolError!Result {
const tracy = trace(@src());
defer tracy.end();
switch (typed_value.ty.zigTypeTag()) {
.Fn => {
- switch (bin_file.options.target.cpu.arch) {
- .arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code),
- .armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code),
- .aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code),
- .aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code),
- .aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code),
- .arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code),
- .avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code),
- .bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code),
- .bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code),
- .hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code),
- .mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code),
- .mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code),
- .mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code),
- .mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code),
- .msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code),
- .powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code),
- .powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code),
- .powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code),
- .r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code),
- .amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code),
- .riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code),
- .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code),
- .sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code),
- .sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code),
- .sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code),
- .s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code),
- .tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code),
- .tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code),
- .thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code),
- .thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code),
- .i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code),
- .x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code),
- .xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code),
- .nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code),
- .nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code),
- .le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code),
- .le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code),
- .amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code),
- .amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code),
- .hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code),
- .hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code),
- .spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code),
- .spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code),
- .kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code),
- .shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code),
- .lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code),
- .wasm32 => return Function(.wasm32).generateSymbol(bin_file, src, typed_value, code),
- .wasm64 => return Function(.wasm64).generateSymbol(bin_file, src, typed_value, code),
- .renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code),
- .renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code),
- .ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code),
+ switch (bin_file.base.options.target.cpu.arch) {
+ //.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ .x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.wasm32 => return Function(.wasm32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.wasm64 => return Function(.wasm64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ //.ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code, dbg_line),
+ else => @panic("Backend architectures that don't have good support yet are commented out, to improve compilation performance. If you are interested in one of these other backends feel free to uncomment them. Eventually these will be completed, but stage1 is slow and a memory hog."),
}
},
.Array => {
@@ -112,7 +120,7 @@ pub fn generateSymbol(
switch (try generateSymbol(bin_file, src, .{
.ty = typed_value.ty.elemType(),
.val = sentinel,
- }, code)) {
+ }, code, dbg_line)) {
.appended => return Result{ .appended = {} },
.externally_managed => |slice| {
code.appendSliceAssumeCapacity(slice);
@@ -141,7 +149,7 @@ pub fn generateSymbol(
// TODO handle the dependency of this symbol on the decl's vaddr.
// If the decl changes vaddr, then this symbol needs to get regenerated.
const vaddr = bin_file.local_symbols.items[decl.link.local_sym_index].st_value;
- const endian = bin_file.options.target.cpu.arch.endian();
+ const endian = bin_file.base.options.target.cpu.arch.endian();
switch (bin_file.ptr_width) {
.p32 => {
try code.resize(4);
@@ -164,7 +172,7 @@ pub fn generateSymbol(
};
},
.Int => {
- const info = typed_value.ty.intInfo(bin_file.options.target);
+ const info = typed_value.ty.intInfo(bin_file.base.options.target);
if (info.bits == 8 and !info.signed) {
const x = typed_value.val.toUnsignedInt();
try code.append(@intCast(u8, x));
@@ -204,10 +212,28 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
target: *const std.Target,
mod_fn: *const Module.Fn,
code: *std.ArrayList(u8),
+ dbg_line: *std.ArrayList(u8),
err_msg: ?*ErrorMsg,
args: []MCValue,
+ ret_mcv: MCValue,
+ fn_type: Type,
arg_index: usize,
src: usize,
+ stack_align: u32,
+
+ /// Byte offset within the source file.
+ prev_di_src: usize,
+ /// Relative to the beginning of `code`.
+ prev_di_pc: usize,
+ /// Used to find newlines and count line deltas.
+ source: []const u8,
+ /// Byte offset within the source file of the ending curly.
+ rbrace_src: usize,
+
+ /// The value is an offset into the `Function` `code` from the beginning.
+ /// To perform the reloc, write 32-bit signed little-endian integer
+ /// which is a relative jump, based on the address following the reloc.
+ exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{},
/// Whenever there is a runtime branch, we push a Branch onto this stack,
/// and pop it off when the runtime branch joins. This provides an "overlay"
@@ -225,22 +251,32 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
unreach,
/// No more references to this value remain.
dead,
+ /// The value is undefined.
+ undef,
/// A pointer-sized integer that fits in a register.
+ /// If the type is a pointer, this is the pointer address in virtual address space.
immediate: u64,
/// The constant was emitted into the code, at this offset.
+ /// If the type is a pointer, it means the pointer address is embedded in the code.
embedded_in_code: usize,
+ /// The value is a pointer to a constant which was emitted into the code, at this offset.
+ ptr_embedded_in_code: usize,
/// The value is in a target-specific register.
register: Register,
/// The value is in memory at a hard-coded address.
+ /// If the type is a pointer, it means the pointer address is at this memory location.
memory: u64,
/// The value is one of the stack variables.
- stack_offset: u64,
+ /// If the type is a pointer, it means the pointer address is in the stack at this offset.
+ stack_offset: u32,
+ /// The value is a pointer to one of the stack variables (payload is stack offset).
+ ptr_stack_offset: u32,
/// The value is in the compare flags assuming an unsigned operation,
/// with this operator applied on top of it.
- compare_flags_unsigned: std.math.CompareOperator,
+ compare_flags_unsigned: math.CompareOperator,
/// The value is in the compare flags assuming a signed operation,
/// with this operator applied on top of it.
- compare_flags_signed: std.math.CompareOperator,
+ compare_flags_signed: math.CompareOperator,
fn isMemory(mcv: MCValue) bool {
return switch (mcv) {
@@ -267,6 +303,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.memory,
.compare_flags_unsigned,
.compare_flags_signed,
+ .ptr_stack_offset,
+ .ptr_embedded_in_code,
+ .undef,
=> false,
.register,
@@ -279,10 +318,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const Branch = struct {
inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{},
registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{},
- free_registers: FreeRegInt = std.math.maxInt(FreeRegInt),
+ free_registers: FreeRegInt = math.maxInt(FreeRegInt),
/// Maps offset to what is stored there.
- stack: std.AutoHashMapUnmanaged(usize, StackAllocation) = .{},
+ stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{},
/// Offset from the stack base, representing the end of the stack frame.
max_end_stack: u32 = 0,
/// Represents the current end stack offset. If there is no existing slot
@@ -292,7 +331,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn markRegUsed(self: *Branch, reg: Register) void {
if (FreeRegInt == u0) return;
const index = reg.allocIndex() orelse return;
- const ShiftInt = std.math.Log2Int(FreeRegInt);
+ const ShiftInt = math.Log2Int(FreeRegInt);
const shift = @intCast(ShiftInt, index);
self.free_registers &= ~(@as(FreeRegInt, 1) << shift);
}
@@ -300,11 +339,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn markRegFree(self: *Branch, reg: Register) void {
if (FreeRegInt == u0) return;
const index = reg.allocIndex() orelse return;
- const ShiftInt = std.math.Log2Int(FreeRegInt);
+ const ShiftInt = math.Log2Int(FreeRegInt);
const shift = @intCast(ShiftInt, index);
self.free_registers |= @as(FreeRegInt, 1) << shift;
}
+ /// Before calling, must ensureCapacity + 1 on branch.registers.
+ /// Returns `null` if all registers are allocated.
+ fn allocReg(self: *Branch, inst: *ir.Inst) ?Register {
+ const free_index = @ctz(FreeRegInt, self.free_registers);
+ if (free_index >= callee_preserved_regs.len) {
+ return null;
+ }
+ self.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
+ const reg = callee_preserved_regs[free_index];
+ self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst });
+ return reg;
+ }
+
fn deinit(self: *Branch, gpa: *Allocator) void {
self.inst_table.deinit(gpa);
self.registers.deinit(gpa);
@@ -329,15 +381,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
src: usize,
typed_value: TypedValue,
code: *std.ArrayList(u8),
+ dbg_line: *std.ArrayList(u8),
) GenerateSymbolError!Result {
const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
const fn_type = module_fn.owner_decl.typed_value.most_recent.typed_value.ty;
- const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen());
- defer bin_file.allocator.free(param_types);
- fn_type.fnParamTypes(param_types);
- var mc_args = try bin_file.allocator.alloc(MCValue, param_types.len);
- defer bin_file.allocator.free(mc_args);
var branch_stack = std.ArrayList(Branch).init(bin_file.allocator);
defer {
@@ -348,24 +396,54 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const branch = try branch_stack.addOne();
branch.* = .{};
+ const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: {
+ if (module_fn.owner_decl.scope.cast(Module.Scope.File)) |scope_file| {
+ const tree = scope_file.contents.tree;
+ const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?;
+ const block = fn_proto.body().?.castTag(.Block).?;
+ const lbrace_src = tree.token_locs[block.lbrace].start;
+ const rbrace_src = tree.token_locs[block.rbrace].start;
+ break :blk .{ .lbrace_src = lbrace_src, .rbrace_src = rbrace_src, .source = tree.source };
+ } else if (module_fn.owner_decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
+ const byte_off = zir_module.contents.module.decls[module_fn.owner_decl.src_index].inst.src;
+ break :blk .{ .lbrace_src = byte_off, .rbrace_src = byte_off, .source = zir_module.source.bytes };
+ } else {
+ unreachable;
+ }
+ };
+
var function = Self{
.gpa = bin_file.allocator,
- .target = &bin_file.options.target,
+ .target = &bin_file.base.options.target,
.bin_file = bin_file,
.mod_fn = module_fn,
.code = code,
+ .dbg_line = dbg_line,
.err_msg = null,
- .args = mc_args,
+ .args = undefined, // populated after `resolveCallingConventionValues`
+ .ret_mcv = undefined, // populated after `resolveCallingConventionValues`
+ .fn_type = fn_type,
.arg_index = 0,
.branch_stack = &branch_stack,
.src = src,
+ .stack_align = undefined,
+ .prev_di_pc = 0,
+ .prev_di_src = src_data.lbrace_src,
+ .rbrace_src = src_data.rbrace_src,
+ .source = src_data.source,
};
+ defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
- const cc = fn_type.fnCallingConvention();
- branch.max_end_stack = function.resolveParameters(src, cc, param_types, mc_args) catch |err| switch (err) {
+ var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) {
error.CodegenFail => return Result{ .fail = function.err_msg.? },
else => |e| return e,
};
+ defer call_info.deinit(&function);
+
+ function.args = call_info.args;
+ function.ret_mcv = call_info.return_value;
+ function.stack_align = call_info.stack_align;
+ branch.max_end_stack = call_info.stack_byte_count;
function.gen() catch |err| switch (err) {
error.CodegenFail => return Result{ .fail = function.err_msg.? },
@@ -380,28 +458,81 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn gen(self: *Self) !void {
- try self.code.ensureCapacity(self.code.items.len + 11);
-
- // push rbp
- // mov rbp, rsp
- self.code.appendSliceAssumeCapacity(&[_]u8{ 0x55, 0x48, 0x89, 0xe5 });
-
- // sub rsp, x
- const stack_end = self.branch_stack.items[0].max_end_stack;
- if (stack_end > std.math.maxInt(i32)) {
- return self.fail(self.src, "too much stack used in call parameters", .{});
- } else if (stack_end > std.math.maxInt(i8)) {
- // 48 83 ec xx sub rsp,0x10
- self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xec });
- const x = @intCast(u32, stack_end);
- mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
- } else if (stack_end != 0) {
- // 48 81 ec xx xx xx xx sub rsp,0x80
- const x = @intCast(u8, stack_end);
- self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xec, x });
- }
+ switch (arch) {
+ .x86_64 => {
+ try self.code.ensureCapacity(self.code.items.len + 11);
+
+ const cc = self.fn_type.fnCallingConvention();
+ if (cc != .Naked) {
+ // We want to subtract the aligned stack frame size from rsp here, but we don't
+ // yet know how big it will be, so we leave room for a 4-byte stack size.
+ // TODO During semantic analysis, check if there are no function calls. If there
+ // are none, here we can omit the part where we subtract and then add rsp.
+ self.code.appendSliceAssumeCapacity(&[_]u8{
+ 0x55, // push rbp
+ 0x48, 0x89, 0xe5, // mov rbp, rsp
+ 0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc)
+ });
+ const reloc_index = self.code.items.len;
+ self.code.items.len += 4;
+
+ try self.dbgSetPrologueEnd();
+ try self.genBody(self.mod_fn.analysis.success);
+
+ const stack_end = self.branch_stack.items[0].max_end_stack;
+ if (stack_end > math.maxInt(i32))
+ return self.fail(self.src, "too much stack used in call parameters", .{});
+ const aligned_stack_end = mem.alignForward(stack_end, self.stack_align);
+ mem.writeIntLittle(u32, self.code.items[reloc_index..][0..4], @intCast(u32, aligned_stack_end));
+
+ if (self.code.items.len >= math.maxInt(i32)) {
+ return self.fail(self.src, "unable to perform relocation: jump too far", .{});
+ }
+ for (self.exitlude_jump_relocs.items) |jmp_reloc| {
+ const amt = self.code.items.len - (jmp_reloc + 4);
+ // If it wouldn't jump at all, elide it.
+ if (amt == 0) {
+ self.code.items.len -= 5;
+ continue;
+ }
+ const s32_amt = @intCast(i32, amt);
+ mem.writeIntLittle(i32, self.code.items[jmp_reloc..][0..4], s32_amt);
+ }
+
+ // Important to be after the possible self.code.items.len -= 5 above.
+ try self.dbgSetEpilogueBegin();
+
+ try self.code.ensureCapacity(self.code.items.len + 9);
+ // add rsp, x
+ if (aligned_stack_end > math.maxInt(i8)) {
+ // example: 48 81 c4 ff ff ff 7f add rsp,0x7fffffff
+ self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xc4 });
+ const x = @intCast(u32, aligned_stack_end);
+ mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
+ } else if (aligned_stack_end != 0) {
+ // example: 48 83 c4 7f add rsp,0x7f
+ const x = @intCast(u8, aligned_stack_end);
+ self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xc4, x });
+ }
- try self.genBody(self.mod_fn.analysis.success);
+ self.code.appendSliceAssumeCapacity(&[_]u8{
+ 0x5d, // pop rbp
+ 0xc3, // ret
+ });
+ } else {
+ try self.dbgSetPrologueEnd();
+ try self.genBody(self.mod_fn.analysis.success);
+ try self.dbgSetEpilogueBegin();
+ }
+ },
+ else => {
+ try self.dbgSetPrologueEnd();
+ try self.genBody(self.mod_fn.analysis.success);
+ try self.dbgSetEpilogueBegin();
+ },
+ }
+ // Drop them off at the rbrace.
+ try self.dbgAdvancePCAndLine(self.rbrace_src);
}
fn genBody(self: *Self, body: ir.Body) InnerError!void {
@@ -418,6 +549,38 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
+ fn dbgSetPrologueEnd(self: *Self) InnerError!void {
+ try self.dbg_line.append(DW.LNS_set_prologue_end);
+ try self.dbgAdvancePCAndLine(self.prev_di_src);
+ }
+
+ fn dbgSetEpilogueBegin(self: *Self) InnerError!void {
+ try self.dbg_line.append(DW.LNS_set_epilogue_begin);
+ try self.dbgAdvancePCAndLine(self.prev_di_src);
+ }
+
+ fn dbgAdvancePCAndLine(self: *Self, src: usize) InnerError!void {
+ // TODO Look into improving the performance here by adding a token-index-to-line
+ // lookup table, and changing ir.Inst from storing byte offset to token. Currently
+ // this involves scanning over the source code for newlines
+ // (but only from the previous byte offset to the new one).
+ const delta_line = std.zig.lineDelta(self.source, self.prev_di_src, src);
+ const delta_pc = self.code.items.len - self.prev_di_pc;
+ self.prev_di_src = src;
+ self.prev_di_pc = self.code.items.len;
+ // TODO Look into using the DWARF special opcodes to compress this data. It lets you emit
+ // single-byte opcodes that add different numbers to both the PC and the line number
+ // at the same time.
+ try self.dbg_line.ensureCapacity(self.dbg_line.items.len + 11);
+ self.dbg_line.appendAssumeCapacity(DW.LNS_advance_pc);
+ leb128.writeULEB128(self.dbg_line.writer(), delta_pc) catch unreachable;
+ if (delta_line != 0) {
+ self.dbg_line.appendAssumeCapacity(DW.LNS_advance_line);
+ leb128.writeILEB128(self.dbg_line.writer(), delta_line) catch unreachable;
+ }
+ self.dbg_line.appendAssumeCapacity(DW.LNS_copy);
+ }
+
fn processDeath(self: *Self, inst: *ir.Inst) void {
const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
const entry = branch.inst_table.getEntry(inst) orelse return;
@@ -425,8 +588,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
entry.value = .dead;
switch (prev_value) {
.register => |reg| {
- _ = branch.registers.remove(reg);
- branch.markRegFree(reg);
+ const canon_reg = toCanonicalReg(reg);
+ _ = branch.registers.remove(canon_reg);
+ branch.markRegFree(canon_reg);
},
else => {}, // TODO process stack allocation death
}
@@ -435,6 +599,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn genFuncInst(self: *Self, inst: *ir.Inst) !MCValue {
switch (inst.tag) {
.add => return self.genAdd(inst.castTag(.add).?),
+ .alloc => return self.genAlloc(inst.castTag(.alloc).?),
.arg => return self.genArg(inst.castTag(.arg).?),
.assembly => return self.genAsm(inst.castTag(.assembly).?),
.bitcast => return self.genBitCast(inst.castTag(.bitcast).?),
@@ -451,19 +616,91 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.cmp_neq => return self.genCmp(inst.castTag(.cmp_neq).?, .neq),
.condbr => return self.genCondBr(inst.castTag(.condbr).?),
.constant => unreachable, // excluded from function bodies
+ .dbg_stmt => return self.genDbgStmt(inst.castTag(.dbg_stmt).?),
+ .floatcast => return self.genFloatCast(inst.castTag(.floatcast).?),
+ .intcast => return self.genIntCast(inst.castTag(.intcast).?),
.isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?),
.isnull => return self.genIsNull(inst.castTag(.isnull).?),
+ .load => return self.genLoad(inst.castTag(.load).?),
+ .not => return self.genNot(inst.castTag(.not).?),
.ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?),
+ .ref => return self.genRef(inst.castTag(.ref).?),
.ret => return self.genRet(inst.castTag(.ret).?),
.retvoid => return self.genRetVoid(inst.castTag(.retvoid).?),
+ .store => return self.genStore(inst.castTag(.store).?),
.sub => return self.genSub(inst.castTag(.sub).?),
.unreach => return MCValue{ .unreach = {} },
- .not => return self.genNot(inst.castTag(.not).?),
- .floatcast => return self.genFloatCast(inst.castTag(.floatcast).?),
- .intcast => return self.genIntCast(inst.castTag(.intcast).?),
}
}
+ fn allocMem(self: *Self, inst: *ir.Inst, abi_size: u32, abi_align: u32) !u32 {
+ if (abi_align > self.stack_align)
+ self.stack_align = abi_align;
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ // TODO find a free slot instead of always appending
+ const offset = mem.alignForwardGeneric(u32, branch.next_stack_offset, abi_align);
+ branch.next_stack_offset = offset + abi_size;
+ if (branch.next_stack_offset > branch.max_end_stack)
+ branch.max_end_stack = branch.next_stack_offset;
+ try branch.stack.putNoClobber(self.gpa, offset, .{
+ .inst = inst,
+ .size = abi_size,
+ });
+ return offset;
+ }
+
+ /// Use a pointer instruction as the basis for allocating stack memory.
+ fn allocMemPtr(self: *Self, inst: *ir.Inst) !u32 {
+ const elem_ty = inst.ty.elemType();
+ const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch {
+ return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty});
+ };
+ // TODO swap this for inst.ty.ptrAlign
+ const abi_align = elem_ty.abiAlignment(self.target.*);
+ return self.allocMem(inst, abi_size, abi_align);
+ }
+
+ fn allocRegOrMem(self: *Self, inst: *ir.Inst) !MCValue {
+ const elem_ty = inst.ty;
+ const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch {
+ return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty});
+ };
+ const abi_align = elem_ty.abiAlignment(self.target.*);
+ if (abi_align > self.stack_align)
+ self.stack_align = abi_align;
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+
+ // Make sure the type can fit in a register before we try to allocate one.
+ const ptr_bits = arch.ptrBitWidth();
+ const ptr_bytes: u64 = @divExact(ptr_bits, 8);
+ if (abi_size <= ptr_bytes) {
+ try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
+ if (branch.allocReg(inst)) |reg| {
+ return MCValue{ .register = registerAlias(reg, abi_size) };
+ }
+ }
+ const stack_offset = try self.allocMem(inst, abi_size, abi_align);
+ return MCValue{ .stack_offset = stack_offset };
+ }
+
+ /// Does not "move" the instruction.
+ fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue {
+ const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
+ try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
+
+ const reg = branch.allocReg(inst) orelse
+ return self.fail(inst.src, "TODO implement spilling register to stack", .{});
+ const old_mcv = branch.inst_table.get(inst).?;
+ const new_mcv: MCValue = .{ .register = reg };
+ try self.genSetReg(inst.src, reg, old_mcv);
+ return new_mcv;
+ }
+
+ fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
+ const stack_offset = try self.allocMemPtr(&inst.base);
+ return MCValue{ .ptr_stack_offset = stack_offset };
+ }
+
fn genFloatCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
@@ -542,6 +779,87 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
+ fn genLoad(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+ const elem_ty = inst.base.ty;
+ if (!elem_ty.hasCodeGenBits())
+ return MCValue.none;
+ const ptr = try self.resolveInst(inst.operand);
+ const is_volatile = inst.operand.ty.isVolatilePtr();
+ if (inst.base.isUnused() and !is_volatile)
+ return MCValue.dead;
+ const dst_mcv: MCValue = blk: {
+ if (inst.base.operandDies(0) and ptr.isMutable()) {
+ // The MCValue that holds the pointer can be re-used as the value.
+ // TODO track this in the register/stack allocation metadata.
+ break :blk ptr;
+ } else {
+ break :blk try self.allocRegOrMem(&inst.base);
+ }
+ };
+ switch (ptr) {
+ .none => unreachable,
+ .undef => unreachable,
+ .unreach => unreachable,
+ .dead => unreachable,
+ .compare_flags_unsigned => unreachable,
+ .compare_flags_signed => unreachable,
+ .immediate => |imm| try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .memory = imm }),
+ .ptr_stack_offset => |off| try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .stack_offset = off }),
+ .ptr_embedded_in_code => |off| {
+ try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .embedded_in_code = off });
+ },
+ .embedded_in_code => {
+ return self.fail(inst.base.src, "TODO implement loading from MCValue.embedded_in_code", .{});
+ },
+ .register => {
+ return self.fail(inst.base.src, "TODO implement loading from MCValue.register", .{});
+ },
+ .memory => {
+ return self.fail(inst.base.src, "TODO implement loading from MCValue.memory", .{});
+ },
+ .stack_offset => {
+ return self.fail(inst.base.src, "TODO implement loading from MCValue.stack_offset", .{});
+ },
+ }
+ return dst_mcv;
+ }
+
+ fn genStore(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+ const ptr = try self.resolveInst(inst.lhs);
+ const value = try self.resolveInst(inst.rhs);
+ const elem_ty = inst.rhs.ty;
+ switch (ptr) {
+ .none => unreachable,
+ .undef => unreachable,
+ .unreach => unreachable,
+ .dead => unreachable,
+ .compare_flags_unsigned => unreachable,
+ .compare_flags_signed => unreachable,
+ .immediate => |imm| {
+ try self.setRegOrMem(inst.base.src, elem_ty, .{ .memory = imm }, value);
+ },
+ .ptr_stack_offset => |off| {
+ try self.genSetStack(inst.base.src, elem_ty, off, value);
+ },
+ .ptr_embedded_in_code => |off| {
+ try self.setRegOrMem(inst.base.src, elem_ty, .{ .embedded_in_code = off }, value);
+ },
+ .embedded_in_code => {
+ return self.fail(inst.base.src, "TODO implement storing to MCValue.embedded_in_code", .{});
+ },
+ .register => {
+ return self.fail(inst.base.src, "TODO implement storing to MCValue.register", .{});
+ },
+ .memory => {
+ return self.fail(inst.base.src, "TODO implement storing to MCValue.memory", .{});
+ },
+ .stack_offset => {
+ return self.fail(inst.base.src, "TODO implement storing to MCValue.stack_offset", .{});
+ },
+ }
+ return .none;
+ }
+
fn genSub(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
@@ -609,7 +927,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// and as a register.
switch (src_mcv) {
.immediate => |imm| {
- if (imm > std.math.maxInt(u31)) {
+ if (imm > math.maxInt(u31)) {
src_mcv = try self.copyToNewRegister(src_inst);
}
},
@@ -624,13 +942,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn genX8664BinMathCode(self: *Self, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void {
switch (dst_mcv) {
.none => unreachable,
+ .undef => unreachable,
.dead, .unreach, .immediate => unreachable,
.compare_flags_unsigned => unreachable,
.compare_flags_signed => unreachable,
+ .ptr_stack_offset => unreachable,
+ .ptr_embedded_in_code => unreachable,
.register => |dst_reg| {
switch (src_mcv) {
.none => unreachable,
+ .undef => try self.genSetReg(src, dst_reg, .undef),
.dead, .unreach => unreachable,
+ .ptr_stack_offset => unreachable,
+ .ptr_embedded_in_code => unreachable,
.register => |src_reg| {
self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 });
self.code.appendSliceAssumeCapacity(&[_]u8{ mr + 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) });
@@ -638,7 +962,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.immediate => |imm| {
const imm32 = @intCast(u31, imm); // This case must be handled before calling genX8664BinMathCode.
// 81 /opx id
- if (imm32 <= std.math.maxInt(u7)) {
+ if (imm32 <= math.maxInt(u7)) {
self.rex(.{ .b = dst_reg.isExtended(), .w = dst_reg.size() == 64 });
self.code.appendSliceAssumeCapacity(&[_]u8{
0x83,
@@ -699,26 +1023,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.i386, .x86_64 => {
try self.code.append(0xcc); // int3
},
+ .riscv64 => {
+ const full = @bitCast(u32, instructions.CallBreak{
+ .mode = @enumToInt(instructions.CallBreak.Mode.ebreak),
+ });
+
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), full);
+ },
else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}),
}
return .none;
}
fn genCall(self: *Self, inst: *ir.Inst.Call) !MCValue {
- const fn_ty = inst.func.ty;
- const cc = fn_ty.fnCallingConvention();
- const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
- defer self.gpa.free(param_types);
- fn_ty.fnParamTypes(param_types);
- var mc_args = try self.gpa.alloc(MCValue, param_types.len);
- defer self.gpa.free(mc_args);
- const stack_byte_count = try self.resolveParameters(inst.base.src, cc, param_types, mc_args);
+ var info = try self.resolveCallingConventionValues(inst.base.src, inst.func.ty);
+ defer info.deinit(self);
switch (arch) {
.x86_64 => {
- for (mc_args) |mc_arg, arg_i| {
+ for (info.args) |mc_arg, arg_i| {
const arg = inst.args[arg_i];
const arg_mcv = try self.resolveInst(inst.args[arg_i]);
+ // Here we do not use setRegOrMem even though the logic is similar, because
+ // the function call will move the stack pointer, so the offsets are different.
switch (mc_arg) {
.none => continue,
.register => |reg| {
@@ -730,6 +1057,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// mov qword ptr [rsp + stack_offset], x
return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
},
+ .ptr_stack_offset => {
+ return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{});
+ },
+ .ptr_embedded_in_code => {
+ return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{});
+ },
+ .undef => unreachable,
.immediate => unreachable,
.unreach => unreachable,
.dead => unreachable,
@@ -758,30 +1092,86 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
}
},
+ .riscv64 => {
+ if (info.args.len > 0) return self.fail(inst.base.src, "TODO implement fn args for {}", .{self.target.cpu.arch});
+
+ if (inst.func.cast(ir.Inst.Constant)) |func_inst| {
+ if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
+ const func = func_val.func;
+ const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
+ const ptr_bits = self.target.cpu.arch.ptrBitWidth();
+ const ptr_bytes: u64 = @divExact(ptr_bits, 8);
+ const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes);
+
+ try self.genSetReg(inst.base.src, .ra, .{ .memory = got_addr });
+ const jalr = instructions.Jalr{
+ .rd = Register.ra.id(),
+ .rs1 = Register.ra.id(),
+ .offset = 0,
+ };
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), @bitCast(u32, jalr));
+ } else {
+ return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
+ }
+ } else {
+ return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
+ }
+ },
else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
}
- const return_type = fn_ty.fnReturnType();
- switch (return_type.zigTypeTag()) {
- .Void => return MCValue{ .none = {} },
- .NoReturn => return MCValue{ .unreach = {} },
- else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}),
+ return info.return_value;
+ }
+
+ fn genRef(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
+ const operand = try self.resolveInst(inst.operand);
+ switch (operand) {
+ .unreach => unreachable,
+ .dead => unreachable,
+ .none => return .none,
+
+ .immediate,
+ .register,
+ .ptr_stack_offset,
+ .ptr_embedded_in_code,
+ .compare_flags_unsigned,
+ .compare_flags_signed,
+ => {
+ const stack_offset = try self.allocMemPtr(&inst.base);
+ try self.genSetStack(inst.base.src, inst.operand.ty, stack_offset, operand);
+ return MCValue{ .ptr_stack_offset = stack_offset };
+ },
+
+ .stack_offset => |offset| return MCValue{ .ptr_stack_offset = offset },
+ .embedded_in_code => |offset| return MCValue{ .ptr_embedded_in_code = offset },
+ .memory => |vaddr| return MCValue{ .immediate = vaddr },
+
+ .undef => return self.fail(inst.base.src, "TODO implement ref on an undefined value", .{}),
}
}
fn ret(self: *Self, src: usize, mcv: MCValue) !MCValue {
- if (mcv != .none) {
- return self.fail(src, "TODO implement return with non-void operand", .{});
- }
+ const ret_ty = self.fn_type.fnReturnType();
+ try self.setRegOrMem(src, ret_ty, self.ret_mcv, mcv);
switch (arch) {
.i386 => {
try self.code.append(0xc3); // ret
},
.x86_64 => {
- try self.code.appendSlice(&[_]u8{
- 0x5d, // pop rbp
- 0xc3, // ret
- });
+ // TODO when implementing defer, this will need to jump to the appropriate defer expression.
+ // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction
+ // which is available if the jump is 127 bytes or less forward.
+ try self.code.resize(self.code.items.len + 5);
+ self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32
+ try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4);
+ },
+ .riscv64 => {
+ const jalr = instructions.Jalr{
+ .rd = Register.zero.id(),
+ .rs1 = Register.ra.id(),
+ .offset = 0,
+ };
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), @bitCast(u32, jalr));
},
else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),
}
@@ -797,7 +1187,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.ret(inst.base.src, .none);
}
- fn genCmp(self: *Self, inst: *ir.Inst.BinOp, op: std.math.CompareOperator) !MCValue {
+ fn genCmp(self: *Self, inst: *ir.Inst.BinOp, op: math.CompareOperator) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
return MCValue.dead;
@@ -830,6 +1220,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
+ fn genDbgStmt(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
+ try self.dbgAdvancePCAndLine(inst.base.src);
+ return MCValue.none;
+ }
+
fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue {
switch (arch) {
.x86_64 => {
@@ -865,7 +1260,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// test reg, 1
// TODO detect al, ax, eax
try self.code.ensureCapacity(self.code.items.len + 4);
- self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 });
+ // TODO audit this codegen: we force w = true here to make
+ // the value affect the big register
+ self.rex(.{ .b = reg.isExtended(), .w = true });
self.code.appendSliceAssumeCapacity(&[_]u8{
0xf6,
@as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()),
@@ -921,7 +1318,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (reloc) {
.rel32 => |pos| {
const amt = self.code.items.len - (pos + 4);
- const s32_amt = std.math.cast(i32, amt) catch
+ // If it wouldn't jump at all, elide it.
+ if (amt == 0) {
+ self.code.items.len -= 5;
+ return;
+ }
+ const s32_amt = math.cast(i32, amt) catch
return self.fail(src, "unable to perform relocation: jump too far", .{});
mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt);
},
@@ -963,36 +1365,72 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
fn genAsm(self: *Self, inst: *ir.Inst.Assembly) !MCValue {
if (!inst.is_volatile and inst.base.isUnused())
return MCValue.dead;
- if (arch != .x86_64 and arch != .i386) {
- return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{});
- }
- for (inst.inputs) |input, i| {
- if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
- return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input});
- }
- const reg_name = input[1 .. input.len - 1];
- const reg = parseRegName(reg_name) orelse
- return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
- const arg = try self.resolveInst(inst.args[i]);
- try self.genSetReg(inst.base.src, reg, arg);
- }
+ switch (arch) {
+ .riscv64 => {
+ for (inst.inputs) |input, i| {
+ if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
+ return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input});
+ }
+ const reg_name = input[1 .. input.len - 1];
+ const reg = parseRegName(reg_name) orelse
+ return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
+ const arg = try self.resolveInst(inst.args[i]);
+ try self.genSetReg(inst.base.src, reg, arg);
+ }
- if (mem.eql(u8, inst.asm_source, "syscall")) {
- try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 });
- } else {
- return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{});
- }
+ if (mem.eql(u8, inst.asm_source, "ecall")) {
+ const full = @bitCast(u32, instructions.CallBreak{
+ .mode = @enumToInt(instructions.CallBreak.Mode.ecall),
+ });
- if (inst.output) |output| {
- if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
- return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output});
- }
- const reg_name = output[2 .. output.len - 1];
- const reg = parseRegName(reg_name) orelse
- return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
- return MCValue{ .register = reg };
- } else {
- return MCValue.none;
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), full);
+ } else {
+ return self.fail(inst.base.src, "TODO implement support for more riscv64 assembly instructions", .{});
+ }
+
+ if (inst.output) |output| {
+ if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
+ return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output});
+ }
+ const reg_name = output[2 .. output.len - 1];
+ const reg = parseRegName(reg_name) orelse
+ return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
+ return MCValue{ .register = reg };
+ } else {
+ return MCValue.none;
+ }
+ },
+ .x86_64, .i386 => {
+ for (inst.inputs) |input, i| {
+ if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') {
+ return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input});
+ }
+ const reg_name = input[1 .. input.len - 1];
+ const reg = parseRegName(reg_name) orelse
+ return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
+ const arg = try self.resolveInst(inst.args[i]);
+ try self.genSetReg(inst.base.src, reg, arg);
+ }
+
+ if (mem.eql(u8, inst.asm_source, "syscall")) {
+ try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 });
+ } else {
+ return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{});
+ }
+
+ if (inst.output) |output| {
+ if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
+ return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output});
+ }
+ const reg_name = output[2 .. output.len - 1];
+ const reg = parseRegName(reg_name) orelse
+ return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name});
+ return MCValue{ .register = reg };
+ } else {
+ return MCValue.none;
+ }
+ },
+ else => return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{}),
}
}
@@ -1024,15 +1462,211 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) error{ CodegenFail, OutOfMemory }!void {
+ /// Sets the value without any modifications to register allocation metadata or stack allocation metadata.
+ fn setRegOrMem(self: *Self, src: usize, ty: Type, loc: MCValue, val: MCValue) !void {
+ switch (loc) {
+ .none => return,
+ .register => |reg| return self.genSetReg(src, reg, val),
+ .stack_offset => |off| return self.genSetStack(src, ty, off, val),
+ .memory => {
+ return self.fail(src, "TODO implement setRegOrMem for memory", .{});
+ },
+ else => unreachable,
+ }
+ }
+
+ fn genSetStack(self: *Self, src: usize, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void {
switch (arch) {
.x86_64 => switch (mcv) {
.dead => unreachable,
- .none => unreachable,
- .unreach => unreachable,
+ .ptr_stack_offset => unreachable,
+ .ptr_embedded_in_code => unreachable,
+ .unreach, .none => return, // Nothing to do.
+ .undef => {
+ if (!self.wantSafety())
+ return; // The already existing value will do just fine.
+ // TODO Upgrade this to a memset call when we have that available.
+ switch (ty.abiSize(self.target.*)) {
+ 1 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaa }),
+ 2 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaa }),
+ 4 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa }),
+ 8 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
+ else => return self.fail(src, "TODO implement memset", .{}),
+ }
+ },
+ .compare_flags_unsigned => |op| {
+ return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{});
+ },
+ .compare_flags_signed => |op| {
+ return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{});
+ },
+ .immediate => |x_big| {
+ const abi_size = ty.abiSize(self.target.*);
+ const adj_off = stack_offset + abi_size;
+ if (adj_off > 128) {
+ return self.fail(src, "TODO implement set stack variable with large stack offset", .{});
+ }
+ try self.code.ensureCapacity(self.code.items.len + 8);
+ switch (abi_size) {
+ 1 => {
+ return self.fail(src, "TODO implement set abi_size=1 stack variable with immediate", .{});
+ },
+ 2 => {
+ return self.fail(src, "TODO implement set abi_size=2 stack variable with immediate", .{});
+ },
+ 4 => {
+ const x = @intCast(u32, x_big);
+ // We have a positive stack offset value but we want a twos complement negative
+ // offset from rbp, which is at the top of the stack frame.
+ const negative_offset = @intCast(i8, -@intCast(i32, adj_off));
+ const twos_comp = @bitCast(u8, negative_offset);
+ // mov DWORD PTR [rbp+offset], immediate
+ self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp });
+ mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
+ },
+ 8 => {
+ return self.fail(src, "TODO implement set abi_size=8 stack variable with immediate", .{});
+ },
+ else => {
+ return self.fail(src, "TODO implement set abi_size=large stack variable with immediate", .{});
+ },
+ }
+ if (x_big <= math.maxInt(u32)) {} else {
+ return self.fail(src, "TODO implement set stack variable with large immediate", .{});
+ }
+ },
+ .embedded_in_code => |code_offset| {
+ return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{});
+ },
+ .register => |reg| {
+ const abi_size = ty.abiSize(self.target.*);
+ const adj_off = stack_offset + abi_size;
+ try self.code.ensureCapacity(self.code.items.len + 7);
+ self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
+ const reg_id: u8 = @truncate(u3, reg.id());
+ if (adj_off <= 128) {
+ // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx
+ const RM = @as(u8, 0b01_000_101) | (reg_id << 3);
+ const negative_offset = @intCast(i8, -@intCast(i32, adj_off));
+ const twos_comp = @bitCast(u8, negative_offset);
+ self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM, twos_comp });
+ } else if (adj_off <= 2147483648) {
+ // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx
+ const RM = @as(u8, 0b10_000_101) | (reg_id << 3);
+ const negative_offset = @intCast(i32, -@intCast(i33, adj_off));
+ const twos_comp = @bitCast(u32, negative_offset);
+ self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM });
+ mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp);
+ } else {
+ return self.fail(src, "stack offset too large", .{});
+ }
+ },
+ .memory => |vaddr| {
+ return self.fail(src, "TODO implement set stack variable from memory vaddr", .{});
+ },
+ .stack_offset => |off| {
+ if (stack_offset == off)
+ return; // Copy stack variable to itself; nothing to do.
+ return self.fail(src, "TODO implement copy stack variable to stack variable", .{});
+ },
+ },
+ else => return self.fail(src, "TODO implement getSetStack for {}", .{self.target.cpu.arch}),
+ }
+ }
+
+ fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) InnerError!void {
+ switch (arch) {
+ .riscv64 => switch (mcv) {
+ .dead => unreachable,
+ .ptr_stack_offset => unreachable,
+ .ptr_embedded_in_code => unreachable,
+ .unreach, .none => return, // Nothing to do.
+ .undef => {
+ if (!self.wantSafety())
+ return; // The already existing value will do just fine.
+ // Write the debug undefined value.
+ return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa });
+ },
+ .immediate => |unsigned_x| {
+ const x = @bitCast(i64, unsigned_x);
+ if (math.minInt(i12) <= x and x <= math.maxInt(i12)) {
+ const instruction = @bitCast(u32, instructions.Addi{
+ .mode = @enumToInt(instructions.Addi.Mode.addi),
+ .imm = @truncate(i12, x),
+ .rs1 = Register.zero.id(),
+ .rd = reg.id(),
+ });
+
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), instruction);
+ return;
+ }
+ if (math.minInt(i32) <= x and x <= math.maxInt(i32)) {
+ const split = @bitCast(packed struct {
+ low12: i12,
+ up20: i20,
+ }, @truncate(i32, x));
+ if (split.low12 < 0) return self.fail(src, "TODO support riscv64 genSetReg i32 immediates with 12th bit set to 1", .{});
+
+ const lui = @bitCast(u32, instructions.Lui{
+ .imm = split.up20,
+ .rd = reg.id(),
+ });
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), lui);
+
+ const addi = @bitCast(u32, instructions.Addi{
+ .mode = @enumToInt(instructions.Addi.Mode.addi),
+ .imm = @truncate(i12, split.low12),
+ .rs1 = reg.id(),
+ .rd = reg.id(),
+ });
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), addi);
+ return;
+ }
+ // li rd, immediate
+ // "Myriad sequences"
+ return self.fail(src, "TODO genSetReg 33-64 bit immediates for riscv64", .{}); // glhf
+ },
+ .memory => |addr| {
+ // The value is in memory at a hard-coded address.
+ // If the type is a pointer, it means the pointer address is at this memory location.
+ try self.genSetReg(src, reg, .{ .immediate = addr });
+
+ const ld = @bitCast(u32, instructions.Load{
+ .mode = @enumToInt(instructions.Load.Mode.ld),
+ .rs1 = reg.id(),
+ .rd = reg.id(),
+ .offset = 0,
+ });
+
+ mem.writeIntLittle(u32, try self.code.addManyAsArray(4), ld);
+ // LOAD imm=[i12 offset = 0], rs1 =
+
+ // return self.fail("TODO implement genSetReg memory for riscv64");
+ },
+ else => return self.fail(src, "TODO implement getSetReg for riscv64 {}", .{mcv}),
+ },
+ .x86_64 => switch (mcv) {
+ .dead => unreachable,
+ .ptr_stack_offset => unreachable,
+ .ptr_embedded_in_code => unreachable,
+ .unreach, .none => return, // Nothing to do.
+ .undef => {
+ if (!self.wantSafety())
+ return; // The already existing value will do just fine.
+ // Write the debug undefined value.
+ switch (reg.size()) {
+ 8 => return self.genSetReg(src, reg, .{ .immediate = 0xaa }),
+ 16 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaa }),
+ 32 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }),
+ 64 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }),
+ else => unreachable,
+ }
+ },
.compare_flags_unsigned => |op| {
try self.code.ensureCapacity(self.code.items.len + 3);
- self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 });
+ // TODO audit this codegen: we force w = true here to make
+ // the value affect the big register
+ self.rex(.{ .b = reg.isExtended(), .w = true });
const opcode: u8 = switch (op) {
.gte => 0x93,
.gt => 0x97,
@@ -1048,9 +1682,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail(src, "TODO set register with compare flags value (signed)", .{});
},
.immediate => |x| {
- if (reg.size() != 64) {
- return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
- }
// 32-bit moves zero-extend to 64-bit, so xoring the 32-bit
// register is the fastest way to zero a register.
if (x == 0) {
@@ -1071,7 +1702,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x31, 0xC0 | id << 3 | id });
return;
}
- if (x <= std.math.maxInt(u32)) {
+ if (x <= math.maxInt(u32)) {
// Next best case: if we set the lower four bytes, the upper four will be zeroed.
//
// The encoding for `mov IMM32 -> REG` is (0xB8 + R) IMM.
@@ -1103,16 +1734,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
//
// In this case, the encoding of the REX byte is 0b0100100B
try self.code.ensureCapacity(self.code.items.len + 10);
- self.rex(.{ .w = true, .b = reg.isExtended() });
+ self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
self.code.items.len += 9;
self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111);
const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
mem.writeIntLittle(u64, imm_ptr, x);
},
.embedded_in_code => |code_offset| {
- if (reg.size() != 64) {
- return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
- }
// We need the offset from RIP in a signed i32 twos complement.
// The instruction is 7 bytes long and RIP points to the next instruction.
try self.code.ensureCapacity(self.code.items.len + 7);
@@ -1120,7 +1748,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three
// bits as five.
// REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id.
- self.rex(.{ .w = true, .b = reg.isExtended() });
+ self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
self.code.items.len += 6;
const rip = self.code.items.len;
const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
@@ -1131,9 +1759,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
mem.writeIntLittle(i32, imm_ptr, offset);
},
.register => |src_reg| {
- if (reg.size() != 64) {
- return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
- }
+ // If the registers are the same, nothing to do.
+ if (src_reg.id() == reg.id())
+ return;
+
// This is a variant of 8B /r. Since we're using 64-bit moves, we require a REX.
// This is thus three bytes: REX 0x8B R/M.
// If the destination is extended, the R field must be 1.
@@ -1141,15 +1770,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// Since the register is being accessed directly, the R/M mode is three. The reg field (the middle
// three bits) contain the destination, and the R/M field (the lower three bits) contain the source.
try self.code.ensureCapacity(self.code.items.len + 3);
- self.rex(.{ .w = true, .r = reg.isExtended(), .b = src_reg.isExtended() });
+ self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended(), .b = src_reg.isExtended() });
const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111);
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R });
},
.memory => |x| {
- if (reg.size() != 64) {
- return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
- }
- if (x <= std.math.maxInt(u32)) {
+ if (x <= math.maxInt(u32)) {
// Moving from memory to a register is a variant of `8B /r`.
// Since we're using 64-bit moves, we require a REX.
// This variant also requires a SIB, as it would otherwise be RIP-relative.
@@ -1158,7 +1784,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// 0b00RRR100, where RRR is the lower three bits of the register ID.
// The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32.
try self.code.ensureCapacity(self.code.items.len + 8);
- self.rex(.{ .w = true, .b = reg.isExtended() });
+ self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
self.code.appendSliceAssumeCapacity(&[_]u8{
0x8B,
0x04 | (@as(u8, reg.id() & 0b111) << 3), // R
@@ -1186,7 +1812,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// is no way to possibly encode it. This means that RSP, RBP, R12, and R13 cannot be used with
// this instruction.
const id3 = @truncate(u3, reg.id());
- std.debug.assert(id3 != 4 and id3 != 5);
+ assert(id3 != 4 and id3 != 5);
// Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue.
try self.genSetReg(src, reg, MCValue{ .immediate = x });
@@ -1201,17 +1827,37 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
//
// Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both*
// register operands need to be marked as extended.
- self.rex(.{ .w = true, .b = reg.isExtended(), .r = reg.isExtended() });
+ self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), .r = reg.isExtended() });
const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id());
self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM });
}
}
},
- .stack_offset => |off| {
- return self.fail(src, "TODO implement genSetReg for stack variables", .{});
+ .stack_offset => |unadjusted_off| {
+ try self.code.ensureCapacity(self.code.items.len + 7);
+ const size_bytes = @divExact(reg.size(), 8);
+ const off = unadjusted_off + size_bytes;
+ self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() });
+ const reg_id: u8 = @truncate(u3, reg.id());
+ if (off <= 128) {
+ // Example: 48 8b 4d 7f mov rcx,QWORD PTR [rbp+0x7f]
+ const RM = @as(u8, 0b01_000_101) | (reg_id << 3);
+ const negative_offset = @intCast(i8, -@intCast(i32, off));
+ const twos_comp = @bitCast(u8, negative_offset);
+ self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8b, RM, twos_comp });
+ } else if (off <= 2147483648) {
+ // Example: 48 8b 8d 80 00 00 00 mov rcx,QWORD PTR [rbp+0x80]
+ const RM = @as(u8, 0b10_000_101) | (reg_id << 3);
+ const negative_offset = @intCast(i32, -@intCast(i33, off));
+ const twos_comp = @bitCast(u32, negative_offset);
+ self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8b, RM });
+ mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp);
+ } else {
+ return self.fail(src, "stack offset too large", .{});
+ }
},
},
- else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}),
+ else => return self.fail(src, "TODO implement getSetReg for {}", .{self.target.cpu.arch}),
}
}
@@ -1247,24 +1893,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- /// Does not "move" the instruction.
- fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue {
- const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
- try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
- try branch.inst_table.ensureCapacity(self.gpa, branch.inst_table.items().len + 1);
-
- const free_index = @ctz(FreeRegInt, branch.free_registers);
- if (free_index >= callee_preserved_regs.len)
- return self.fail(inst.src, "TODO implement spilling register to stack", .{});
- branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
- const reg = callee_preserved_regs[free_index];
- branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst });
- const old_mcv = branch.inst_table.get(inst).?;
- const new_mcv: MCValue = .{ .register = reg };
- try self.genSetReg(inst.src, reg, old_mcv);
- return new_mcv;
- }
-
/// If the MCValue is an immediate, and it does not fit within this type,
/// we put it in a register.
/// A potential opportunity for future optimization here would be keeping track
@@ -1282,7 +1910,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.is_signed = false,
},
});
- if (imm >= std.math.maxInt(U)) {
+ if (imm >= math.maxInt(U)) {
return self.copyToNewRegister(inst);
}
},
@@ -1292,6 +1920,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) !MCValue {
+ if (typed_value.val.isUndef())
+ return MCValue.undef;
const ptr_bits = self.target.cpu.arch.ptrBitWidth();
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
switch (typed_value.ty.zigTypeTag()) {
@@ -1320,19 +1950,44 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
- fn resolveParameters(
- self: *Self,
- src: usize,
- cc: std.builtin.CallingConvention,
- param_types: []const Type,
- results: []MCValue,
- ) !u32 {
+ const CallMCValues = struct {
+ args: []MCValue,
+ return_value: MCValue,
+ stack_byte_count: u32,
+ stack_align: u32,
+
+ fn deinit(self: *CallMCValues, func: *Self) void {
+ func.gpa.free(self.args);
+ self.* = undefined;
+ }
+ };
+
+ /// Caller must call `CallMCValues.deinit`.
+ fn resolveCallingConventionValues(self: *Self, src: usize, fn_ty: Type) !CallMCValues {
+ const cc = fn_ty.fnCallingConvention();
+ const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
+ defer self.gpa.free(param_types);
+ fn_ty.fnParamTypes(param_types);
+ var result: CallMCValues = .{
+ .args = try self.gpa.alloc(MCValue, param_types.len),
+ // These undefined values must be populated before returning from this function.
+ .return_value = undefined,
+ .stack_byte_count = undefined,
+ .stack_align = undefined,
+ };
+ errdefer self.gpa.free(result.args);
+
+ const ret_ty = fn_ty.fnReturnType();
+
switch (arch) {
.x86_64 => {
switch (cc) {
.Naked => {
- assert(results.len == 0);
- return 0;
+ assert(result.args.len == 0);
+ result.return_value = .{ .unreach = {} };
+ result.stack_byte_count = 0;
+ result.stack_align = 1;
+ return result;
},
.Unspecified, .C => {
var next_int_reg: usize = 0;
@@ -1341,24 +1996,59 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
for (param_types) |ty, i| {
switch (ty.zigTypeTag()) {
.Bool, .Int => {
+ const param_size = @intCast(u32, ty.abiSize(self.target.*));
if (next_int_reg >= c_abi_int_param_regs.len) {
- results[i] = .{ .stack_offset = next_stack_offset };
- next_stack_offset += @intCast(u32, ty.abiSize(self.target.*));
+ result.args[i] = .{ .stack_offset = next_stack_offset };
+ next_stack_offset += param_size;
} else {
- results[i] = .{ .register = c_abi_int_param_regs[next_int_reg] };
+ const aliased_reg = registerAlias(
+ c_abi_int_param_regs[next_int_reg],
+ param_size,
+ );
+ result.args[i] = .{ .register = aliased_reg };
next_int_reg += 1;
}
},
else => return self.fail(src, "TODO implement function parameters of type {}", .{@tagName(ty.zigTypeTag())}),
}
}
- return next_stack_offset;
+ result.stack_byte_count = next_stack_offset;
+ result.stack_align = 16;
},
else => return self.fail(src, "TODO implement function parameters for {}", .{cc}),
}
},
- else => return self.fail(src, "TODO implement C ABI support for {}", .{self.target.cpu.arch}),
+ else => if (param_types.len != 0)
+ return self.fail(src, "TODO implement codegen parameters for {}", .{self.target.cpu.arch}),
}
+
+ if (ret_ty.zigTypeTag() == .NoReturn) {
+ result.return_value = .{ .unreach = {} };
+ } else if (!ret_ty.hasCodeGenBits()) {
+ result.return_value = .{ .none = {} };
+ } else switch (arch) {
+ .x86_64 => switch (cc) {
+ .Naked => unreachable,
+ .Unspecified, .C => {
+ const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*));
+ const aliased_reg = registerAlias(c_abi_int_return_regs[0], ret_ty_size);
+ result.return_value = .{ .register = aliased_reg };
+ },
+ else => return self.fail(src, "TODO implement function return values for {}", .{cc}),
+ },
+ else => return self.fail(src, "TODO implement codegen return values for {}", .{self.target.cpu.arch}),
+ }
+ return result;
+ }
+
+ /// TODO support scope overrides. Also note this logic is duplicated with `Module.wantSafety`.
+ fn wantSafety(self: *Self) bool {
+ return switch (self.bin_file.base.options.optimize_mode) {
+ .Debug => true,
+ .ReleaseSafe => true,
+ .ReleaseFast => false,
+ .ReleaseSmall => false,
+ };
}
fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } {
@@ -1371,6 +2061,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
usingnamespace switch (arch) {
.i386 => @import("codegen/x86.zig"),
.x86_64 => @import("codegen/x86_64.zig"),
+ .riscv64 => @import("codegen/riscv64.zig"),
else => struct {
pub const Register = enum {
dummy,
@@ -1387,7 +2078,33 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const FreeRegInt = @Type(.{ .Int = .{ .is_signed = false, .bits = callee_preserved_regs.len } });
fn parseRegName(name: []const u8) ?Register {
+ if (@hasDecl(Register, "parseRegName")) {
+ return Register.parseRegName(name);
+ }
return std.meta.stringToEnum(Register, name);
}
+
+ fn registerAlias(reg: Register, size_bytes: u32) Register {
+ switch (arch) {
+ // For x86_64 we have to pick a smaller register alias depending on abi size.
+ .x86_64 => switch (size_bytes) {
+ 1 => return reg.to8(),
+ 2 => return reg.to16(),
+ 4 => return reg.to32(),
+ 8 => return reg.to64(),
+ else => unreachable,
+ },
+ else => return reg,
+ }
+ }
+
+ /// For most architectures this does nothing. For x86_64 it resolves any aliased registers
+ /// to the 64-bit wide ones.
+ fn toCanonicalReg(reg: Register) Register {
+ return switch (arch) {
+ .x86_64 => reg.to64(),
+ else => reg,
+ };
+ }
};
}
diff --git a/src-self-hosted/codegen/c.zig b/src-self-hosted/codegen/c.zig
@@ -89,17 +89,17 @@ fn genFn(file: *C, decl: *Decl) !void {
const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func;
const instructions = func.analysis.success.instructions;
if (instructions.len > 0) {
+ try writer.writeAll("\n");
for (instructions) |inst| {
- try writer.writeAll("\n ");
switch (inst.tag) {
.assembly => try genAsm(file, inst.castTag(.assembly).?, decl),
.call => try genCall(file, inst.castTag(.call).?, decl),
.ret => try genRet(file, inst.castTag(.ret).?, decl, tv.ty.fnReturnType()),
- .retvoid => try file.main.writer().print("return;", .{}),
+ .retvoid => try file.main.writer().print(" return;\n", .{}),
+ .dbg_stmt => try genDbgStmt(file, inst.castTag(.dbg_stmt).?, decl),
else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
}
}
- try writer.writeAll("\n");
}
try writer.writeAll("}\n\n");
@@ -112,6 +112,7 @@ fn genRet(file: *C, inst: *Inst.UnOp, decl: *Decl, expected_return_type: Type) !
fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
const writer = file.main.writer();
const header = file.header.writer();
+ try writer.writeAll(" ");
if (inst.func.castTag(.constant)) |func_inst| {
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const target = func_val.func.owner_decl;
@@ -126,7 +127,7 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
try renderFunctionSignature(file, header, target);
try header.writeAll(";\n");
}
- try writer.print("{}();", .{tname});
+ try writer.print("{}();\n", .{tname});
} else {
return file.fail(decl.src(), "TODO non-function call target?", .{});
}
@@ -138,8 +139,13 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
}
}
+fn genDbgStmt(file: *C, inst: *Inst.NoOp, decl: *Decl) !void {
+ // TODO emit #line directive here with line number and filename
+}
+
fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void {
const writer = file.main.writer();
+ try writer.writeAll(" ");
for (as.inputs) |i, index| {
if (i[0] == '{' and i[i.len - 1] == '}') {
const reg = i[1 .. i.len - 1];
@@ -187,5 +193,5 @@ fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void {
}
}
}
- try writer.writeAll(");");
+ try writer.writeAll(");\n");
}
diff --git a/src-self-hosted/codegen/riscv64.zig b/src-self-hosted/codegen/riscv64.zig
@@ -0,0 +1,92 @@
+const std = @import("std");
+
+pub const instructions = struct {
+ pub const CallBreak = packed struct {
+ pub const Mode = packed enum(u12) { ecall, ebreak };
+ opcode: u7 = 0b1110011,
+ unused1: u5 = 0,
+ unused2: u3 = 0,
+ unused3: u5 = 0,
+ mode: u12, //: Mode
+ };
+ // I-type
+ pub const Addi = packed struct {
+ pub const Mode = packed enum(u3) { addi = 0b000, slti = 0b010, sltiu = 0b011, xori = 0b100, ori = 0b110, andi = 0b111 };
+ opcode: u7 = 0b0010011,
+ rd: u5,
+ mode: u3, //: Mode
+ rs1: u5,
+ imm: i12,
+ };
+ pub const Lui = packed struct {
+ opcode: u7 = 0b0110111,
+ rd: u5,
+ imm: i20,
+ };
+ // I_type
+ pub const Load = packed struct {
+ pub const Mode = packed enum(u3) { ld = 0b011, lwu = 0b110 };
+ opcode: u7 = 0b0000011,
+ rd: u5,
+ mode: u3, //: Mode
+ rs1: u5,
+ offset: i12,
+ };
+ // I-type
+ pub const Jalr = packed struct {
+ opcode: u7 = 0b1100111,
+ rd: u5,
+ mode: u3 = 0,
+ rs1: u5,
+ offset: i12,
+ };
+};
+
+// zig fmt: off
+pub const RawRegister = enum(u8) {
+ x0, x1, x2, x3, x4, x5, x6, x7,
+ x8, x9, x10, x11, x12, x13, x14, x15,
+ x16, x17, x18, x19, x20, x21, x22, x23,
+ x24, x25, x26, x27, x28, x29, x30, x31,
+};
+
+pub const Register = enum(u8) {
+ // 64 bit registers
+ zero, // zero
+ ra, // return address. caller saved
+ sp, // stack pointer. callee saved.
+ gp, // global pointer
+ tp, // thread pointer
+ t0, t1, t2, // temporaries. caller saved.
+ s0, // s0/fp, callee saved.
+ s1, // callee saved.
+ a0, a1, // fn args/return values. caller saved.
+ a2, a3, a4, a5, a6, a7, // fn args. caller saved.
+ s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, // saved registers. callee saved.
+ t3, t4, t5, t6, // caller saved
+
+ pub fn parseRegName(name: []const u8) ?Register {
+ if(std.meta.stringToEnum(Register, name)) |reg| return reg;
+ if(std.meta.stringToEnum(RawRegister, name)) |rawreg| return @intToEnum(Register, @enumToInt(rawreg));
+ return null;
+ }
+
+ /// Returns the register's id.
+ pub fn id(self: @This()) u5 {
+ return @truncate(u5, @enumToInt(self));
+ }
+
+ /// Returns the index into `callee_preserved_regs`.
+ pub fn allocIndex(self: Register) ?u4 {
+ inline for(callee_preserved_regs) |cpreg, i| {
+ if(self == cpreg) return i;
+ }
+ return null;
+ }
+};
+
+// zig fmt: on
+
+pub const callee_preserved_regs = [_]Register{
+ .s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11,
+};
diff --git a/src-self-hosted/codegen/x86_64.zig b/src-self-hosted/codegen/x86_64.zig
@@ -81,6 +81,26 @@ pub const Register = enum(u8) {
else => null,
};
}
+
+ /// Convert from any register to its 64 bit alias.
+ pub fn to64(self: Register) Register {
+ return @intToEnum(Register, self.id());
+ }
+
+ /// Convert from any register to its 32 bit alias.
+ pub fn to32(self: Register) Register {
+ return @intToEnum(Register, @as(u8, self.id()) + 16);
+ }
+
+ /// Convert from any register to its 16 bit alias.
+ pub fn to16(self: Register) Register {
+ return @intToEnum(Register, @as(u8, self.id()) + 32);
+ }
+
+ /// Convert from any register to its 8 bit alias.
+ pub fn to8(self: Register) Register {
+ return @intToEnum(Register, @as(u8, self.id()) + 48);
+ }
};
// zig fmt: on
@@ -88,3 +108,4 @@ pub const Register = enum(u8) {
/// These registers belong to the called function.
pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 };
pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
+pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };
diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig
@@ -4,6 +4,7 @@ const Type = @import("type.zig").Type;
const Module = @import("Module.zig");
const assert = std.debug.assert;
const codegen = @import("codegen.zig");
+const ast = std.zig.ast;
/// These are in-memory, analyzed instructions. See `zir.Inst` for the representation
/// of instructions that correspond to the ZIR text format.
@@ -47,6 +48,7 @@ pub const Inst = struct {
pub const Tag = enum {
add,
+ alloc,
arg,
assembly,
bitcast,
@@ -63,28 +65,34 @@ pub const Inst = struct {
cmp_neq,
condbr,
constant,
+ dbg_stmt,
isnonnull,
isnull,
+ /// Read a value from a pointer.
+ load,
ptrtoint,
+ ref,
ret,
retvoid,
+ /// Write a value to a pointer. LHS is pointer, RHS is value.
+ store,
sub,
unreach,
not,
floatcast,
intcast,
- /// There is one-to-one correspondence between tag and type for now,
- /// but this will not always be the case. For example, binary operations
- /// such as + and - will have different tags but the same type.
pub fn Type(tag: Tag) type {
return switch (tag) {
+ .alloc,
.retvoid,
.unreach,
.arg,
.breakpoint,
+ .dbg_stmt,
=> NoOp,
+ .ref,
.ret,
.bitcast,
.not,
@@ -93,6 +101,7 @@ pub const Inst = struct {
.ptrtoint,
.floatcast,
.intcast,
+ .load,
=> UnOp,
.add,
@@ -103,6 +112,7 @@ pub const Inst = struct {
.cmp_gte,
.cmp_gt,
.cmp_neq,
+ .store,
=> BinOp,
.assembly => Assembly,
@@ -157,8 +167,7 @@ pub const Inst = struct {
/// Returns `null` if runtime-known.
pub fn value(base: *Inst) ?Value {
- if (base.ty.onePossibleValue())
- return Value.initTag(.the_one_possible_value);
+ if (base.ty.onePossibleValue()) |opv| return opv;
const inst = base.cast(Constant) orelse return null;
return inst.val;
diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig
@@ -8,6 +8,15 @@ const fs = std.fs;
const elf = std.elf;
const codegen = @import("codegen.zig");
const c_codegen = @import("codegen/c.zig");
+const log = std.log;
+const DW = std.dwarf;
+const trace = @import("tracy.zig").trace;
+const leb128 = std.debug.leb;
+const Package = @import("Package.zig");
+const Value = @import("value.zig").Value;
+
+// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented.
+// zig fmt: off
const default_entry_addr = 0x8000000;
@@ -16,6 +25,9 @@ pub const Options = struct {
output_mode: std.builtin.OutputMode,
link_mode: std.builtin.LinkMode,
object_format: std.builtin.ObjectFormat,
+ optimize_mode: std.builtin.Mode,
+ root_name: []const u8,
+ root_pkg: *const Package,
/// Used for calculating how much space to reserve for symbols in case the binary file
/// does not already have a symbol table.
symbol_count_hint: u64 = 32,
@@ -24,96 +36,27 @@ pub const Options = struct {
program_code_size_hint: u64 = 256 * 1024,
};
-/// Attempts incremental linking, if the file already exists.
-/// If incremental linking fails, falls back to truncating the file and rewriting it.
-/// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
-/// This operation is not atomic.
-pub fn openBinFilePath(
- allocator: *Allocator,
- dir: fs.Dir,
- sub_path: []const u8,
+pub const File = struct {
+ tag: Tag,
options: Options,
-) !*File {
- const cbe = options.object_format == .c;
- const file = try dir.createFile(sub_path, .{ .truncate = cbe, .read = true, .mode = determineMode(options) });
- errdefer file.close();
-
- if (cbe) {
- var bin_file = try allocator.create(File.C);
- errdefer allocator.destroy(bin_file);
- bin_file.* = try openCFile(allocator, file, options);
- return &bin_file.base;
- } else {
- var bin_file = try allocator.create(File.Elf);
- errdefer allocator.destroy(bin_file);
- bin_file.* = try openBinFile(allocator, file, options);
- bin_file.owns_file_handle = true;
- return &bin_file.base;
- }
-}
-/// Atomically overwrites the old file, if present.
-pub fn writeFilePath(
- allocator: *Allocator,
- dir: fs.Dir,
- sub_path: []const u8,
- module: Module,
- errors: *std.ArrayList(Module.ErrorMsg),
-) !void {
- const options: Options = .{
- .target = module.target,
- .output_mode = module.output_mode,
- .link_mode = module.link_mode,
- .object_format = module.object_format,
- .symbol_count_hint = module.decls.items.len,
- };
- const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(options) });
- defer af.deinit();
-
- const elf_file = try createElfFile(allocator, af.file, options);
- for (module.decls.items) |decl| {
- try elf_file.updateDecl(module, decl, errors);
- }
- try elf_file.flush();
- if (elf_file.error_flags.no_entry_point_found) {
- try errors.ensureCapacity(errors.items.len + 1);
- errors.appendAssumeCapacity(.{
- .byte_offset = 0,
- .msg = try std.fmt.allocPrint(errors.allocator, "no entry point found", .{}),
- });
+ /// Attempts incremental linking, if the file already exists. If
+ /// incremental linking fails, falls back to truncating the file and
+ /// rewriting it. A malicious file is detected as incremental link failure
+ /// and does not cause Illegal Behavior. This operation is not atomic.
+ pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File {
+ switch (options.object_format) {
+ .unknown => unreachable,
+ .coff => return error.TODOImplementCoff,
+ .elf => return Elf.openPath(allocator, dir, sub_path, options),
+ .macho => return error.TODOImplementMacho,
+ .wasm => return error.TODOImplementWasm,
+ .c => return C.openPath(allocator, dir, sub_path, options),
+ .hex => return error.TODOImplementHex,
+ .raw => return error.TODOImplementRaw,
+ }
}
- try af.finish();
- return result;
-}
-fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C {
- return File.C{
- .allocator = allocator,
- .file = file,
- .options = options,
- .main = std.ArrayList(u8).init(allocator),
- .header = std.ArrayList(u8).init(allocator),
- .constants = std.ArrayList(u8).init(allocator),
- .called = std.StringHashMap(void).init(allocator),
- };
-}
-
-/// Attempts incremental linking, if the file already exists.
-/// If incremental linking fails, falls back to truncating the file and rewriting it.
-/// Returns an error if `file` is not already open with +read +write +seek abilities.
-/// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
-/// This operation is not atomic.
-pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !File.Elf {
- return openBinFileInner(allocator, file, options) catch |err| switch (err) {
- error.IncrFailed => {
- return createElfFile(allocator, file, options);
- },
- else => |e| return e,
- };
-}
-
-pub const File = struct {
- tag: Tag,
pub fn cast(base: *File, comptime T: type) ?*T {
if (base.tag != T.base_tag)
return null;
@@ -123,86 +66,82 @@ pub const File = struct {
pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void {
switch (base.tag) {
- .Elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path),
- .C => {},
- else => unreachable,
+ .elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path),
+ .c => {},
}
}
pub fn makeExecutable(base: *File) !void {
switch (base.tag) {
- .Elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(),
- else => unreachable,
+ .elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(),
+ .c => unreachable,
}
}
pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void {
switch (base.tag) {
- .Elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
- .C => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
- else => unreachable,
+ .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
+ .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
+ }
+ }
+
+ pub fn updateDeclLineNumber(base: *File, module: *Module, decl: *Module.Decl) !void {
+ switch (base.tag) {
+ .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
+ .c => {},
}
}
pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void {
switch (base.tag) {
- .Elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
- .C => {},
- else => unreachable,
+ .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
+ .c => {},
}
}
pub fn deinit(base: *File) void {
switch (base.tag) {
- .Elf => @fieldParentPtr(Elf, "base", base).deinit(),
- .C => @fieldParentPtr(C, "base", base).deinit(),
- else => unreachable,
+ .elf => @fieldParentPtr(Elf, "base", base).deinit(),
+ .c => @fieldParentPtr(C, "base", base).deinit(),
}
}
pub fn destroy(base: *File) void {
switch (base.tag) {
- .Elf => {
+ .elf => {
const parent = @fieldParentPtr(Elf, "base", base);
parent.deinit();
parent.allocator.destroy(parent);
},
- .C => {
+ .c => {
const parent = @fieldParentPtr(C, "base", base);
parent.deinit();
parent.allocator.destroy(parent);
},
- else => unreachable,
}
}
pub fn flush(base: *File) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
try switch (base.tag) {
- .Elf => @fieldParentPtr(Elf, "base", base).flush(),
- .C => @fieldParentPtr(C, "base", base).flush(),
- else => unreachable,
+ .elf => @fieldParentPtr(Elf, "base", base).flush(),
+ .c => @fieldParentPtr(C, "base", base).flush(),
};
}
pub fn freeDecl(base: *File, decl: *Module.Decl) void {
switch (base.tag) {
- .Elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
- else => unreachable,
+ .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
+ .c => unreachable,
}
}
pub fn errorFlags(base: *File) ErrorFlags {
return switch (base.tag) {
- .Elf => @fieldParentPtr(Elf, "base", base).error_flags,
- .C => return .{ .no_entry_point_found = false },
- else => unreachable,
- };
- }
-
- pub fn options(base: *File) Options {
- return switch (base.tag) {
- .Elf => @fieldParentPtr(Elf, "base", base).options,
- .C => @fieldParentPtr(C, "base", base).options,
+ .elf => @fieldParentPtr(Elf, "base", base).error_flags,
+ .c => return .{ .no_entry_point_found = false },
};
}
@@ -214,14 +153,14 @@ pub const File = struct {
exports: []const *Module.Export,
) !void {
switch (base.tag) {
- .Elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
- .C => return {},
+ .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
+ .c => return {},
}
}
pub const Tag = enum {
- Elf,
- C,
+ elf,
+ c,
};
pub const ErrorFlags = struct {
@@ -229,24 +168,49 @@ pub const File = struct {
};
pub const C = struct {
- pub const base_tag: Tag = .C;
- base: File = File{ .tag = base_tag },
+ pub const base_tag: Tag = .c;
+
+ base: File,
allocator: *Allocator,
header: std.ArrayList(u8),
constants: std.ArrayList(u8),
main: std.ArrayList(u8),
file: ?fs.File,
- options: Options,
called: std.StringHashMap(void),
need_stddef: bool = false,
need_stdint: bool = false,
need_noreturn: bool = false,
error_msg: *Module.ErrorMsg = undefined,
+ pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File {
+ assert(options.object_format == .c);
+
+ const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true, .mode = determineMode(options) });
+ errdefer file.close();
+
+ var c_file = try allocator.create(C);
+ errdefer allocator.destroy(c_file);
+
+ c_file.* = File.C{
+ .base = .{
+ .tag = .c,
+ .options = options,
+ },
+ .allocator = allocator,
+ .file = file,
+ .main = std.ArrayList(u8).init(allocator),
+ .header = std.ArrayList(u8).init(allocator),
+ .constants = std.ArrayList(u8).init(allocator),
+ .called = std.StringHashMap(void).init(allocator),
+ };
+
+ return &c_file.base;
+ }
+
pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) !void {
self.error_msg = try Module.ErrorMsg.create(self.allocator, src, format, args);
- return error.CGenFailure;
+ return error.AnalysisFail;
}
pub fn deinit(self: *File.C) void {
@@ -260,7 +224,7 @@ pub const File = struct {
pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void {
c_codegen.generate(self, decl) catch |err| {
- if (err == error.CGenFailure) {
+ if (err == error.AnalysisFail) {
try module.failed_decls.put(module.gpa, decl, self.error_msg);
}
return err;
@@ -301,13 +265,13 @@ pub const File = struct {
};
pub const Elf = struct {
- pub const base_tag: Tag = .Elf;
- base: File = File{ .tag = base_tag },
+ pub const base_tag: Tag = .elf;
+
+ base: File,
allocator: *Allocator,
file: ?fs.File,
owns_file_handle: bool,
- options: Options,
ptr_width: enum { p32, p64 },
/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
@@ -326,12 +290,20 @@ pub const File = struct {
phdr_got_index: ?u16 = null,
entry_addr: ?u64 = null,
+ debug_strtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){},
shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){},
shstrtab_index: ?u16 = null,
text_section_index: ?u16 = null,
symtab_section_index: ?u16 = null,
got_section_index: ?u16 = null,
+ debug_info_section_index: ?u16 = null,
+ debug_abbrev_section_index: ?u16 = null,
+ debug_str_section_index: ?u16 = null,
+ debug_aranges_section_index: ?u16 = null,
+ debug_line_section_index: ?u16 = null,
+
+ debug_abbrev_table_offset: ?u64 = null,
/// The same order as in the file. ELF requires global symbols to all be after the
/// local symbols, they cannot be mixed. So we must buffer all the global symbols and
@@ -352,7 +324,12 @@ pub const File = struct {
phdr_table_dirty: bool = false,
shdr_table_dirty: bool = false,
shstrtab_dirty: bool = false,
+ debug_strtab_dirty: bool = false,
offset_table_count_dirty: bool = false,
+ debug_info_section_dirty: bool = false,
+ debug_abbrev_section_dirty: bool = false,
+ debug_aranges_section_dirty: bool = false,
+ debug_line_header_dirty: bool = false,
error_flags: ErrorFlags = ErrorFlags{},
@@ -374,6 +351,12 @@ pub const File = struct {
text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){},
last_text_block: ?*TextBlock = null,
+ /// A list of `SrcFn` whose Line Number Programs have surplus capacity.
+ /// This is the same concept as `text_block_free_list`; see those doc comments.
+ dbg_line_fn_free_list: std.AutoHashMapUnmanaged(*SrcFn, void) = .{},
+ dbg_line_fn_first: ?*SrcFn = null,
+ dbg_line_fn_last: ?*SrcFn = null,
+
/// `alloc_num / alloc_den` is the factor of padding when allocating.
const alloc_num = 4;
const alloc_den = 3;
@@ -437,16 +420,139 @@ pub const File = struct {
sym_index: ?u32 = null,
};
+ pub const SrcFn = struct {
+ /// Offset from the beginning of the Debug Line Program header that contains this function.
+ off: u32,
+ /// Size of the line number program component belonging to this function, not
+ /// including padding.
+ len: u32,
+
+ /// Points to the previous and next neighbors, based on the offset from .debug_line.
+ /// This can be used to find, for example, the capacity of this `SrcFn`.
+ prev: ?*SrcFn,
+ next: ?*SrcFn,
+
+ pub const empty: SrcFn = .{
+ .off = 0,
+ .len = 0,
+ .prev = null,
+ .next = null,
+ };
+ };
+
+ pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File {
+ assert(options.object_format == .elf);
+
+ const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) });
+ errdefer file.close();
+
+ var elf_file = try allocator.create(Elf);
+ errdefer allocator.destroy(elf_file);
+
+ elf_file.* = openFile(allocator, file, options) catch |err| switch (err) {
+ error.IncrFailed => try createFile(allocator, file, options),
+ else => |e| return e,
+ };
+
+ elf_file.owns_file_handle = true;
+ return &elf_file.base;
+ }
+
+ /// Returns error.IncrFailed if incremental update could not be performed.
+ fn openFile(allocator: *Allocator, file: fs.File, options: Options) !Elf {
+ switch (options.output_mode) {
+ .Exe => {},
+ .Obj => {},
+ .Lib => return error.IncrFailed,
+ }
+ var self: Elf = .{
+ .base = .{
+ .tag = .elf,
+ .options = options,
+ },
+ .allocator = allocator,
+ .file = file,
+ .owns_file_handle = false,
+ .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
+ 32 => .p32,
+ 64 => .p64,
+ else => return error.UnsupportedELFArchitecture,
+ },
+ };
+ errdefer self.deinit();
+
+ // TODO implement reading the elf file
+ return error.IncrFailed;
+ //try self.populateMissingMetadata();
+ //return self;
+ }
+
+ /// Truncates the existing file contents and overwrites the contents.
+ /// Returns an error if `file` is not already open with +read +write +seek abilities.
+ fn createFile(allocator: *Allocator, file: fs.File, options: Options) !Elf {
+ switch (options.output_mode) {
+ .Exe => {},
+ .Obj => {},
+ .Lib => return error.TODOImplementWritingLibFiles,
+ }
+ var self: Elf = .{
+ .base = .{
+ .tag = .elf,
+ .options = options,
+ },
+ .allocator = allocator,
+ .file = file,
+ .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
+ 32 => .p32,
+ 64 => .p64,
+ else => return error.UnsupportedELFArchitecture,
+ },
+ .shdr_table_dirty = true,
+ .owns_file_handle = false,
+ };
+ errdefer self.deinit();
+
+ // Index 0 is always a null symbol.
+ try self.local_symbols.append(allocator, .{
+ .st_name = 0,
+ .st_info = 0,
+ .st_other = 0,
+ .st_shndx = 0,
+ .st_value = 0,
+ .st_size = 0,
+ });
+
+ // There must always be a null section in index 0
+ try self.sections.append(allocator, .{
+ .sh_name = 0,
+ .sh_type = elf.SHT_NULL,
+ .sh_flags = 0,
+ .sh_addr = 0,
+ .sh_offset = 0,
+ .sh_size = 0,
+ .sh_link = 0,
+ .sh_info = 0,
+ .sh_addralign = 0,
+ .sh_entsize = 0,
+ });
+
+ try self.populateMissingMetadata();
+
+ return self;
+ }
+
pub fn deinit(self: *Elf) void {
self.sections.deinit(self.allocator);
self.program_headers.deinit(self.allocator);
self.shstrtab.deinit(self.allocator);
+ self.debug_strtab.deinit(self.allocator);
self.local_symbols.deinit(self.allocator);
self.global_symbols.deinit(self.allocator);
self.global_symbol_free_list.deinit(self.allocator);
self.local_symbol_free_list.deinit(self.allocator);
self.offset_table_free_list.deinit(self.allocator);
self.text_block_free_list.deinit(self.allocator);
+ self.dbg_line_fn_free_list.deinit(self.allocator);
self.offset_table.deinit(self.allocator);
if (self.owns_file_handle) {
if (self.file) |f| f.close();
@@ -467,13 +573,21 @@ pub const File = struct {
self.file = try dir.createFile(sub_path, .{
.truncate = false,
.read = true,
- .mode = determineMode(self.options),
+ .mode = determineMode(self.base.options),
});
}
+ fn getDebugLineProgramOff(self: Elf) u32 {
+ return self.dbg_line_fn_first.?.off;
+ }
+
+ fn getDebugLineProgramEnd(self: Elf) u32 {
+ return self.dbg_line_fn_last.?.off + self.dbg_line_fn_last.?.len;
+ }
+
/// Returns end pos of collision, if any.
fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
- const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32;
+ const small_ptr = self.base.options.target.cpu.arch.ptrBitWidth() == 32;
const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);
if (start < ehdr_size)
return ehdr_size;
@@ -518,6 +632,8 @@ pub const File = struct {
}
fn allocatedSize(self: *Elf, start: u64) u64 {
+ if (start == 0)
+ return 0;
var min_pos: u64 = std.math.maxInt(u64);
if (self.shdr_table_offset) |off| {
if (off > start and off < min_pos) min_pos = off;
@@ -544,6 +660,7 @@ pub const File = struct {
return start;
}
+ /// TODO Improve this to use a table.
fn makeString(self: *Elf, bytes: []const u8) !u32 {
try self.shstrtab.ensureCapacity(self.allocator, self.shstrtab.items.len + bytes.len + 1);
const result = self.shstrtab.items.len;
@@ -552,6 +669,15 @@ pub const File = struct {
return @intCast(u32, result);
}
+ /// TODO Improve this to use a table.
+ fn makeDebugString(self: *Elf, bytes: []const u8) !u32 {
+ try self.debug_strtab.ensureCapacity(self.allocator, self.debug_strtab.items.len + bytes.len + 1);
+ const result = self.debug_strtab.items.len;
+ self.debug_strtab.appendSliceAssumeCapacity(bytes);
+ self.debug_strtab.appendAssumeCapacity(0);
+ return @intCast(u32, result);
+ }
+
fn getString(self: *Elf, str_off: u32) []const u8 {
assert(str_off < self.shstrtab.items.len);
return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off));
@@ -570,16 +696,13 @@ pub const File = struct {
.p32 => true,
.p64 => false,
};
- const ptr_size: u8 = switch (self.ptr_width) {
- .p32 => 4,
- .p64 => 8,
- };
+ const ptr_size: u8 = self.ptrWidthBytes();
if (self.phdr_load_re_index == null) {
self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len);
- const file_size = self.options.program_code_size_hint;
+ const file_size = self.base.options.program_code_size_hint;
const p_align = 0x1000;
const off = self.findFreeSpace(file_size, p_align);
- std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
+ log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
try self.program_headers.append(self.allocator, .{
.p_type = elf.PT_LOAD,
.p_offset = off,
@@ -595,12 +718,12 @@ pub const File = struct {
}
if (self.phdr_got_index == null) {
self.phdr_got_index = @intCast(u16, self.program_headers.items.len);
- const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint;
+ const file_size = @as(u64, ptr_size) * self.base.options.symbol_count_hint;
// We really only need ptr alignment but since we are using PROGBITS, linux requires
// page align.
const p_align = 0x1000;
const off = self.findFreeSpace(file_size, p_align);
- std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
+ log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
// TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at.
// we'll need to re-use that function anyway, in case the GOT grows and overlaps something
// else in virtual memory.
@@ -622,7 +745,7 @@ pub const File = struct {
assert(self.shstrtab.items.len == 0);
try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0
const off = self.findFreeSpace(self.shstrtab.items.len, 1);
- std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len });
+ log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len });
try self.sections.append(self.allocator, .{
.sh_name = try self.makeString(".shstrtab"),
.sh_type = elf.SHT_STRTAB,
@@ -678,9 +801,9 @@ pub const File = struct {
self.symtab_section_index = @intCast(u16, self.sections.items.len);
const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym);
const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym);
- const file_size = self.options.symbol_count_hint * each_size;
+ const file_size = self.base.options.symbol_count_hint * each_size;
const off = self.findFreeSpace(file_size, min_align);
- std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
+ log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
try self.sections.append(self.allocator, .{
.sh_name = try self.makeString(".symtab"),
@@ -698,6 +821,124 @@ pub const File = struct {
self.shdr_table_dirty = true;
try self.writeSymbol(0);
}
+ if (self.debug_str_section_index == null) {
+ self.debug_str_section_index = @intCast(u16, self.sections.items.len);
+ assert(self.debug_strtab.items.len == 0);
+ try self.sections.append(self.allocator, .{
+ .sh_name = try self.makeString(".debug_str"),
+ .sh_type = elf.SHT_PROGBITS,
+ .sh_flags = elf.SHF_MERGE | elf.SHF_STRINGS,
+ .sh_addr = 0,
+ .sh_offset = 0,
+ .sh_size = self.debug_strtab.items.len,
+ .sh_link = 0,
+ .sh_info = 0,
+ .sh_addralign = 1,
+ .sh_entsize = 1,
+ });
+ self.debug_strtab_dirty = true;
+ self.shdr_table_dirty = true;
+ }
+ if (self.debug_info_section_index == null) {
+ self.debug_info_section_index = @intCast(u16, self.sections.items.len);
+
+ const file_size_hint = 200;
+ const p_align = 1;
+ const off = self.findFreeSpace(file_size_hint, p_align);
+ log.debug(.link, "found .debug_info free space 0x{x} to 0x{x}\n", .{
+ off,
+ off + file_size_hint,
+ });
+ try self.sections.append(self.allocator, .{
+ .sh_name = try self.makeString(".debug_info"),
+ .sh_type = elf.SHT_PROGBITS,
+ .sh_flags = 0,
+ .sh_addr = 0,
+ .sh_offset = off,
+ .sh_size = file_size_hint,
+ .sh_link = 0,
+ .sh_info = 0,
+ .sh_addralign = p_align,
+ .sh_entsize = 0,
+ });
+ self.shdr_table_dirty = true;
+ self.debug_info_section_dirty = true;
+ }
+ if (self.debug_abbrev_section_index == null) {
+ self.debug_abbrev_section_index = @intCast(u16, self.sections.items.len);
+
+ const file_size_hint = 128;
+ const p_align = 1;
+ const off = self.findFreeSpace(file_size_hint, p_align);
+ log.debug(.link, "found .debug_abbrev free space 0x{x} to 0x{x}\n", .{
+ off,
+ off + file_size_hint,
+ });
+ try self.sections.append(self.allocator, .{
+ .sh_name = try self.makeString(".debug_abbrev"),
+ .sh_type = elf.SHT_PROGBITS,
+ .sh_flags = 0,
+ .sh_addr = 0,
+ .sh_offset = off,
+ .sh_size = file_size_hint,
+ .sh_link = 0,
+ .sh_info = 0,
+ .sh_addralign = p_align,
+ .sh_entsize = 0,
+ });
+ self.shdr_table_dirty = true;
+ self.debug_abbrev_section_dirty = true;
+ }
+ if (self.debug_aranges_section_index == null) {
+ self.debug_aranges_section_index = @intCast(u16, self.sections.items.len);
+
+ const file_size_hint = 160;
+ const p_align = 16;
+ const off = self.findFreeSpace(file_size_hint, p_align);
+ log.debug(.link, "found .debug_aranges free space 0x{x} to 0x{x}\n", .{
+ off,
+ off + file_size_hint,
+ });
+ try self.sections.append(self.allocator, .{
+ .sh_name = try self.makeString(".debug_aranges"),
+ .sh_type = elf.SHT_PROGBITS,
+ .sh_flags = 0,
+ .sh_addr = 0,
+ .sh_offset = off,
+ .sh_size = file_size_hint,
+ .sh_link = 0,
+ .sh_info = 0,
+ .sh_addralign = p_align,
+ .sh_entsize = 0,
+ });
+ self.shdr_table_dirty = true;
+ self.debug_aranges_section_dirty = true;
+ }
+ if (self.debug_line_section_index == null) {
+ self.debug_line_section_index = @intCast(u16, self.sections.items.len);
+
+ const file_size_hint = 250;
+ const p_align = 1;
+ const off = self.findFreeSpace(file_size_hint, p_align);
+ log.debug(.link, "found .debug_line free space 0x{x} to 0x{x}\n", .{
+ off,
+ off + file_size_hint,
+ });
+ try self.sections.append(self.allocator, .{
+ .sh_name = try self.makeString(".debug_line"),
+ .sh_type = elf.SHT_PROGBITS,
+ .sh_flags = 0,
+ .sh_addr = 0,
+ .sh_offset = off,
+ .sh_size = file_size_hint,
+ .sh_link = 0,
+ .sh_info = 0,
+ .sh_addralign = p_align,
+ .sh_entsize = 0,
+ });
+ self.shdr_table_dirty = true;
+ self.debug_line_header_dirty = true;
+ }
const shsize: u64 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Shdr),
.p64 => @sizeOf(elf.Elf64_Shdr),
@@ -733,12 +974,307 @@ pub const File = struct {
/// Commit pending changes and write headers.
pub fn flush(self: *Elf) !void {
- const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+ const target_endian = self.base.options.target.cpu.arch.endian();
+ const foreign_endian = target_endian != std.Target.current.cpu.arch.endian();
+ const ptr_width_bytes: u8 = self.ptrWidthBytes();
+ const init_len_size: usize = switch (self.ptr_width) {
+ .p32 => 4,
+ .p64 => 12,
+ };
// Unfortunately these have to be buffered and done at the end because ELF does not allow
// mixing local and global symbols within a symbol table.
try self.writeAllGlobalSymbols();
+ if (self.debug_abbrev_section_dirty) {
+ const debug_abbrev_sect = &self.sections.items[self.debug_abbrev_section_index.?];
+
+ // These are LEB encoded but since the values are all less than 127
+ // we can simply append these bytes.
+ const abbrev_buf = [_]u8{
+ 1, DW.TAG_compile_unit, DW.CHILDREN_no, // header
+ DW.AT_stmt_list, DW.FORM_sec_offset,
+ DW.AT_low_pc , DW.FORM_addr,
+ DW.AT_high_pc , DW.FORM_addr,
+ DW.AT_name , DW.FORM_strp,
+ DW.AT_comp_dir , DW.FORM_strp,
+ DW.AT_producer , DW.FORM_strp,
+ DW.AT_language , DW.FORM_data2,
+ 0, 0, // table sentinel
+
+ 0, 0, 0, // section sentinel
+ };
+
+ const needed_size = abbrev_buf.len;
+ const allocated_size = self.allocatedSize(debug_abbrev_sect.sh_offset);
+ if (needed_size > allocated_size) {
+ debug_abbrev_sect.sh_size = 0; // free the space
+ debug_abbrev_sect.sh_offset = self.findFreeSpace(needed_size, 1);
+ }
+ debug_abbrev_sect.sh_size = needed_size;
+ log.debug(.link, ".debug_abbrev start=0x{x} end=0x{x}\n", .{
+ debug_abbrev_sect.sh_offset,
+ debug_abbrev_sect.sh_offset + needed_size,
+ });
+
+ const abbrev_offset = 0;
+ self.debug_abbrev_table_offset = abbrev_offset;
+ try self.file.?.pwriteAll(&abbrev_buf, debug_abbrev_sect.sh_offset + abbrev_offset);
+ if (!self.shdr_table_dirty) {
+ // Then it won't get written with the others and we need to do it.
+ try self.writeSectHeader(self.debug_abbrev_section_index.?);
+ }
+
+ self.debug_abbrev_section_dirty = false;
+ }
+ if (self.debug_info_section_dirty) {
+ const debug_info_sect = &self.sections.items[self.debug_info_section_index.?];
+
+ var di_buf = std.ArrayList(u8).init(self.allocator);
+ defer di_buf.deinit();
+
+ // Enough for a 64-bit header and main compilation unit without resizing.
+ try di_buf.ensureCapacity(100);
+
+ // initial length - length of the .debug_info contribution for this compilation unit,
+ // not including the initial length itself.
+ // We have to come back and write it later after we know the size.
+ const init_len_index = di_buf.items.len;
+ di_buf.items.len += init_len_size;
+ const after_init_len = di_buf.items.len;
+ mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // DWARF version
+ const abbrev_offset = self.debug_abbrev_table_offset.?;
+ switch (self.ptr_width) {
+ .p32 => {
+ mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, abbrev_offset), target_endian);
+ di_buf.appendAssumeCapacity(4); // address size
+ },
+ .p64 => {
+ mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), abbrev_offset, target_endian);
+ di_buf.appendAssumeCapacity(8); // address size
+ },
+ }
+ // Write the form for the compile unit, which must match the abbrev table above.
+ const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path);
+ const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path);
+ const producer_strp = try self.makeDebugString("zig (TODO version here)");
+ // Currently only one compilation unit is supported, so the address range is simply
+ // identical to the main program header virtual address and memory size.
+ const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];
+ const low_pc = text_phdr.p_vaddr;
+ const high_pc = text_phdr.p_vaddr + text_phdr.p_memsz;
+
+ di_buf.appendAssumeCapacity(1); // abbrev tag, matching the value from the abbrev table header
+ self.writeDwarfAddrAssumeCapacity(&di_buf, 0); // DW.AT_stmt_list, DW.FORM_sec_offset
+ self.writeDwarfAddrAssumeCapacity(&di_buf, low_pc);
+ self.writeDwarfAddrAssumeCapacity(&di_buf, high_pc);
+ self.writeDwarfAddrAssumeCapacity(&di_buf, name_strp);
+ self.writeDwarfAddrAssumeCapacity(&di_buf, comp_dir_strp);
+ self.writeDwarfAddrAssumeCapacity(&di_buf, producer_strp);
+ // We are still waiting on dwarf-std.org to assign DW_LANG_Zig a number:
+ // http://dwarfstd.org/ShowIssue.php?issue=171115.1
+ // Until then we say it is C99.
+ mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), DW.LANG_C99, target_endian);
+
+ const init_len = di_buf.items.len - after_init_len;
+ switch (self.ptr_width) {
+ .p32 => {
+ mem.writeInt(u32, di_buf.items[init_len_index..][0..4], @intCast(u32, init_len), target_endian);
+ },
+ .p64 => {
+ // initial length - length of the .debug_info contribution for this compilation unit,
+ // not including the initial length itself.
+ di_buf.items[init_len_index..][0..4].* = [_]u8{ 0xff, 0xff, 0xff, 0xff };
+ mem.writeInt(u64, di_buf.items[init_len_index + 4..][0..8], init_len, target_endian);
+ },
+ }
+
+ const needed_size = di_buf.items.len;
+ const allocated_size = self.allocatedSize(debug_info_sect.sh_offset);
+ if (needed_size > allocated_size) {
+ debug_info_sect.sh_size = 0; // free the space
+ debug_info_sect.sh_offset = self.findFreeSpace(needed_size, 1);
+ }
+ debug_info_sect.sh_size = needed_size;
+ log.debug(.link, ".debug_info start=0x{x} end=0x{x}\n", .{
+ debug_info_sect.sh_offset,
+ debug_info_sect.sh_offset + needed_size,
+ });
+
+ try self.file.?.pwriteAll(di_buf.items, debug_info_sect.sh_offset);
+ if (!self.shdr_table_dirty) {
+ // Then it won't get written with the others and we need to do it.
+ try self.writeSectHeader(self.debug_info_section_index.?);
+ }
+
+ self.debug_info_section_dirty = false;
+ }
+ if (self.debug_aranges_section_dirty) {
+ const debug_aranges_sect = &self.sections.items[self.debug_aranges_section_index.?];
+
+ var di_buf = std.ArrayList(u8).init(self.allocator);
+ defer di_buf.deinit();
+
+ // Enough for all the data without resizing. When support for more compilation units
+ // is added, the size of this section will become more variable.
+ try di_buf.ensureCapacity(100);
+
+ // initial length - length of the .debug_aranges contribution for this compilation unit,
+ // not including the initial length itself.
+ // We have to come back and write it later after we know the size.
+ const init_len_index = di_buf.items.len;
+ di_buf.items.len += init_len_size;
+ const after_init_len = di_buf.items.len;
+ mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 2, target_endian); // version
+ // When more than one compilation unit is supported, this will be the offset to it.
+ // For now it is always at offset 0 in .debug_info.
+ self.writeDwarfAddrAssumeCapacity(&di_buf, 0); // .debug_info offset
+ di_buf.appendAssumeCapacity(ptr_width_bytes); // address_size
+ di_buf.appendAssumeCapacity(0); // segment_selector_size
+
+ const end_header_offset = di_buf.items.len;
+ const begin_entries_offset = mem.alignForward(end_header_offset, ptr_width_bytes * 2);
+ di_buf.appendNTimesAssumeCapacity(0, begin_entries_offset - end_header_offset);
+
+ // Currently only one compilation unit is supported, so the address range is simply
+ // identical to the main program header virtual address and memory size.
+ const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];
+ self.writeDwarfAddrAssumeCapacity(&di_buf, text_phdr.p_vaddr);
+ self.writeDwarfAddrAssumeCapacity(&di_buf, text_phdr.p_memsz);
+
+ // Sentinel.
+ self.writeDwarfAddrAssumeCapacity(&di_buf, 0);
+ self.writeDwarfAddrAssumeCapacity(&di_buf, 0);
+
+ // Go back and populate the initial length.
+ const init_len = di_buf.items.len - after_init_len;
+ switch (self.ptr_width) {
+ .p32 => {
+ mem.writeInt(u32, di_buf.items[init_len_index..][0..4], @intCast(u32, init_len), target_endian);
+ },
+ .p64 => {
+ // initial length - length of the .debug_aranges contribution for this compilation unit,
+ // not including the initial length itself.
+ di_buf.items[init_len_index..][0..4].* = [_]u8{ 0xff, 0xff, 0xff, 0xff };
+ mem.writeInt(u64, di_buf.items[init_len_index + 4..][0..8], init_len, target_endian);
+ },
+ }
+
+ const needed_size = di_buf.items.len;
+ const allocated_size = self.allocatedSize(debug_aranges_sect.sh_offset);
+ if (needed_size > allocated_size) {
+ debug_aranges_sect.sh_size = 0; // free the space
+ debug_aranges_sect.sh_offset = self.findFreeSpace(needed_size, 16);
+ }
+ debug_aranges_sect.sh_size = needed_size;
+ log.debug(.link, ".debug_aranges start=0x{x} end=0x{x}\n", .{
+ debug_aranges_sect.sh_offset,
+ debug_aranges_sect.sh_offset + needed_size,
+ });
+
+ try self.file.?.pwriteAll(di_buf.items, debug_aranges_sect.sh_offset);
+ if (!self.shdr_table_dirty) {
+ // Then it won't get written with the others and we need to do it.
+ try self.writeSectHeader(self.debug_aranges_section_index.?);
+ }
+
+ self.debug_aranges_section_dirty = false;
+ }
+ if (self.debug_line_header_dirty) {
+ const dbg_line_prg_off = self.getDebugLineProgramOff();
+ const dbg_line_prg_end = self.getDebugLineProgramEnd();
+ assert(dbg_line_prg_end != 0);
+
+ const debug_line_sect = &self.sections.items[self.debug_line_section_index.?];
+
+ var di_buf = std.ArrayList(u8).init(self.allocator);
+ defer di_buf.deinit();
+
+ // The size of this header is variable, depending on the number of directories,
+ // files, and padding. We have a function to compute the upper bound size, however,
+ // because it's needed for determining where to put the offset of the first `SrcFn`.
+ try di_buf.ensureCapacity(self.dbgLineNeededHeaderBytes());
+
+ // initial length - length of the .debug_line contribution for this compilation unit,
+ // not including the initial length itself.
+ const after_init_len = di_buf.items.len + init_len_size;
+ const init_len = dbg_line_prg_end - after_init_len;
+ switch (self.ptr_width) {
+ .p32 => {
+ mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len), target_endian);
+ },
+ .p64 => {
+ di_buf.appendNTimesAssumeCapacity(0xff, 4);
+ mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), init_len, target_endian);
+ },
+ }
+
+ mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // version
+
+ // Empirically, debug info consumers do not respect this field, or otherwise
+ // consider it to be an error when it does not point exactly to the end of the header.
+ // Therefore we rely on the NOP jump at the beginning of the Line Number Program for
+ // padding rather than this field.
+ const before_header_len = di_buf.items.len;
+ di_buf.items.len += ptr_width_bytes; // We will come back and write this.
+ const after_header_len = di_buf.items.len;
+
+ const opcode_base = DW.LNS_set_isa + 1;
+ di_buf.appendSliceAssumeCapacity(&[_]u8{
+ 1, // minimum_instruction_length
+ 1, // maximum_operations_per_instruction
+ 1, // default_is_stmt
+ 1, // line_base (signed)
+ 1, // line_range
+ opcode_base,
+
+ // Standard opcode lengths. The number of items here is based on `opcode_base`.
+ // The value is the number of LEB128 operands the instruction takes.
+ 0, // `DW.LNS_copy`
+ 1, // `DW.LNS_advance_pc`
+ 1, // `DW.LNS_advance_line`
+ 1, // `DW.LNS_set_file`
+ 1, // `DW.LNS_set_column`
+ 0, // `DW.LNS_negate_stmt`
+ 0, // `DW.LNS_set_basic_block`
+ 0, // `DW.LNS_const_add_pc`
+ 1, // `DW.LNS_fixed_advance_pc`
+ 0, // `DW.LNS_set_prologue_end`
+ 0, // `DW.LNS_set_epilogue_begin`
+ 1, // `DW.LNS_set_isa`
+
+ 0, // include_directories (none except the compilation unit cwd)
+ });
+ // file_names[0]
+ di_buf.appendSliceAssumeCapacity(self.base.options.root_pkg.root_src_path); // relative path name
+ di_buf.appendSliceAssumeCapacity(&[_]u8{
+ 0, // null byte for the relative path name
+ 0, // directory_index
+ 0, // mtime (TODO supply this)
+ 0, // file size bytes (TODO supply this)
+ 0, // file_names sentinel
+ });
+
+ const header_len = di_buf.items.len - after_header_len;
+ switch (self.ptr_width) {
+ .p32 => {
+ mem.writeInt(u32, di_buf.items[before_header_len..][0..4], @intCast(u32, header_len), target_endian);
+ },
+ .p64 => {
+ mem.writeInt(u64, di_buf.items[before_header_len..][0..8], header_len, target_endian);
+ },
+ }
+
+ // We use NOPs because consumers empirically do not respect the header length field.
+ if (di_buf.items.len > dbg_line_prg_off) {
+ // Move the first N files to the end to make more padding for the header.
+ @panic("TODO: handle .debug_line header exceeding its padding");
+ }
+ const jmp_amt = dbg_line_prg_off - di_buf.items.len;
+ try self.pwriteWithNops(0, di_buf.items, jmp_amt, debug_line_sect.sh_offset);
+ self.debug_line_header_dirty = false;
+ }
+
if (self.phdr_table_dirty) {
const phsize: u64 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Phdr),
@@ -796,7 +1332,7 @@ pub const File = struct {
shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1);
}
shstrtab_sect.sh_size = needed_size;
- std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size });
+ log.debug(.link, "writing shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size });
try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset);
if (!self.shdr_table_dirty) {
@@ -806,6 +1342,27 @@ pub const File = struct {
self.shstrtab_dirty = false;
}
}
+ {
+ const debug_strtab_sect = &self.sections.items[self.debug_str_section_index.?];
+ if (self.debug_strtab_dirty or self.debug_strtab.items.len != debug_strtab_sect.sh_size) {
+ const allocated_size = self.allocatedSize(debug_strtab_sect.sh_offset);
+ const needed_size = self.debug_strtab.items.len;
+
+ if (needed_size > allocated_size) {
+ debug_strtab_sect.sh_size = 0; // free the space
+ debug_strtab_sect.sh_offset = self.findFreeSpace(needed_size, 1);
+ }
+ debug_strtab_sect.sh_size = needed_size;
+ log.debug(.link, "debug_strtab start=0x{x} end=0x{x}\n", .{ debug_strtab_sect.sh_offset, debug_strtab_sect.sh_offset + needed_size });
+
+ try self.file.?.pwriteAll(self.debug_strtab.items, debug_strtab_sect.sh_offset);
+ if (!self.shdr_table_dirty) {
+ // Then it won't get written with the others and we need to do it.
+ try self.writeSectHeader(self.debug_str_section_index.?);
+ }
+ self.debug_strtab_dirty = false;
+ }
+ }
if (self.shdr_table_dirty) {
const shsize: u64 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Shdr),
@@ -842,7 +1399,7 @@ pub const File = struct {
for (buf) |*shdr, i| {
shdr.* = self.sections.items[i];
- std.log.debug(.link, "writing section {}\n", .{shdr.*});
+ log.debug(.link, "writing section {}\n", .{shdr.*});
if (foreign_endian) {
bswapAllFields(elf.Elf64_Shdr, shdr);
}
@@ -852,8 +1409,8 @@ pub const File = struct {
}
self.shdr_table_dirty = false;
}
- if (self.entry_addr == null and self.options.output_mode == .Exe) {
- std.log.debug(.link, "no_entry_point_found = true\n", .{});
+ if (self.entry_addr == null and self.base.options.output_mode == .Exe) {
+ log.debug(.link, "no_entry_point_found = true\n", .{});
self.error_flags.no_entry_point_found = true;
} else {
self.error_flags.no_entry_point_found = false;
@@ -861,14 +1418,27 @@ pub const File = struct {
}
// The point of flush() is to commit changes, so nothing should be dirty after this.
+ assert(!self.debug_info_section_dirty);
+ assert(!self.debug_abbrev_section_dirty);
+ assert(!self.debug_aranges_section_dirty);
+ assert(!self.debug_line_header_dirty);
assert(!self.phdr_table_dirty);
assert(!self.shdr_table_dirty);
assert(!self.shstrtab_dirty);
+ assert(!self.debug_strtab_dirty);
assert(!self.offset_table_count_dirty);
const syms_sect = &self.sections.items[self.symtab_section_index.?];
assert(syms_sect.sh_info == self.local_symbols.items.len);
}
+ fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void {
+ const target_endian = self.base.options.target.cpu.arch.endian();
+ switch (self.ptr_width) {
+ .p32 => mem.writeInt(u32, buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, addr), target_endian),
+ .p64 => mem.writeInt(u64, buf.addManyAsArrayAssumeCapacity(8), addr, target_endian),
+ }
+ }
+
fn writeElfHeader(self: *Elf) !void {
var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined;
@@ -882,7 +1452,7 @@ pub const File = struct {
};
index += 1;
- const endian = self.options.target.cpu.arch.endian();
+ const endian = self.base.options.target.cpu.arch.endian();
hdr_buf[index] = switch (endian) {
.Little => elf.ELFDATA2LSB,
.Big => elf.ELFDATA2MSB,
@@ -900,10 +1470,10 @@ pub const File = struct {
assert(index == 16);
- const elf_type = switch (self.options.output_mode) {
+ const elf_type = switch (self.base.options.output_mode) {
.Exe => elf.ET.EXEC,
.Obj => elf.ET.REL,
- .Lib => switch (self.options.link_mode) {
+ .Lib => switch (self.base.options.link_mode) {
.Static => elf.ET.REL,
.Dynamic => elf.ET.DYN,
},
@@ -911,7 +1481,7 @@ pub const File = struct {
mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian);
index += 2;
- const machine = self.options.target.cpu.arch.toElfMachine();
+ const machine = self.base.options.target.cpu.arch.toElfMachine();
mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian);
index += 2;
@@ -1129,6 +1699,15 @@ pub const File = struct {
phdr.p_memsz = needed_size;
phdr.p_filesz = needed_size;
+ // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address
+ // range of the compilation unit. When we expand the text section, this range changes,
+ // so the .debug_info section becomes dirty.
+ self.debug_info_section_dirty = true;
+ // This becomes dirty for the same reason. We could potentially make this more
+ // fine-grained with the addition of support for more compilation units. It is planned to
+ // model each package as a different compilation unit.
+ self.debug_aranges_section_dirty = true;
+
self.phdr_table_dirty = true; // TODO look into making only the one program header dirty
self.shdr_table_dirty = true; // TODO look into making only the one section dirty
}
@@ -1160,17 +1739,14 @@ pub const File = struct {
pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void {
if (decl.link.local_sym_index != 0) return;
- // Here we also ensure capacity for the free lists so that they can be appended to without fail.
try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1);
- try self.local_symbol_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len);
try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1);
- try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len);
if (self.local_symbol_free_list.popOrNull()) |i| {
- std.log.debug(.link, "reusing symbol index {} for {}\n", .{ i, decl.name });
+ log.debug(.link, "reusing symbol index {} for {}\n", .{ i, decl.name });
decl.link.local_sym_index = i;
} else {
- std.log.debug(.link, "allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name });
+ log.debug(.link, "allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name });
decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len);
_ = self.local_symbols.addOneAssumeCapacity();
}
@@ -1197,23 +1773,107 @@ pub const File = struct {
}
pub fn freeDecl(self: *Elf, decl: *Module.Decl) void {
+ // Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
self.freeTextBlock(&decl.link);
if (decl.link.local_sym_index != 0) {
- self.local_symbol_free_list.appendAssumeCapacity(decl.link.local_sym_index);
- self.offset_table_free_list.appendAssumeCapacity(decl.link.offset_table_index);
+ self.local_symbol_free_list.append(self.allocator, decl.link.local_sym_index) catch {};
+ self.offset_table_free_list.append(self.allocator, decl.link.offset_table_index) catch {};
self.local_symbols.items[decl.link.local_sym_index].st_info = 0;
decl.link.local_sym_index = 0;
}
+ // TODO make this logic match freeTextBlock. Maybe abstract the logic out since the same thing
+ // is desired for both.
+ _ = self.dbg_line_fn_free_list.remove(&decl.fn_link);
+ if (decl.fn_link.prev) |prev| {
+ _ = self.dbg_line_fn_free_list.put(self.allocator, prev, {}) catch {};
+ prev.next = decl.fn_link.next;
+ if (decl.fn_link.next) |next| {
+ next.prev = prev;
+ } else {
+ self.dbg_line_fn_last = prev;
+ }
+ } else if (decl.fn_link.next) |next| {
+ self.dbg_line_fn_first = next;
+ next.prev = null;
+ }
+ if (self.dbg_line_fn_first == &decl.fn_link) {
+ self.dbg_line_fn_first = null;
+ }
+ if (self.dbg_line_fn_last == &decl.fn_link) {
+ self.dbg_line_fn_last = null;
+ }
}
pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
var code_buffer = std.ArrayList(u8).init(self.allocator);
defer code_buffer.deinit();
+ var dbg_line_buffer = std.ArrayList(u8).init(self.allocator);
+ defer dbg_line_buffer.deinit();
+
const typed_value = decl.typed_value.most_recent.typed_value;
- const code = switch (try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer)) {
+ const is_fn: bool = switch (typed_value.ty.zigTypeTag()) {
+ .Fn => true,
+ else => false,
+ };
+ if (is_fn) {
+ // For functions we need to add a prologue to the debug line program.
+ try dbg_line_buffer.ensureCapacity(26);
+
+ const line_off: u28 = blk: {
+ if (decl.scope.cast(Module.Scope.File)) |scope_file| {
+ const tree = scope_file.contents.tree;
+ const file_ast_decls = tree.root_node.decls();
+ // TODO Look into improving the performance here by adding a token-index-to-line
+ // lookup table. Currently this involves scanning over the source code for newlines.
+ const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
+ const block = fn_proto.body().?.castTag(.Block).?;
+ const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
+ break :blk @intCast(u28, line_delta);
+ } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
+ const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src;
+ const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off);
+ break :blk @intCast(u28, line_delta);
+ } else {
+ unreachable;
+ }
+ };
+
+ const ptr_width_bytes = self.ptrWidthBytes();
+ dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{
+ DW.LNS_extended_op,
+ ptr_width_bytes + 1,
+ DW.LNE_set_address,
+ });
+ // This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`.
+ assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len);
+ dbg_line_buffer.items.len += ptr_width_bytes;
+
+ dbg_line_buffer.appendAssumeCapacity(DW.LNS_advance_line);
+ // This is the "relocatable" relative line offset from the previous function's end curly
+ // to this function's begin curly.
+ assert(self.getRelocDbgLineOff() == dbg_line_buffer.items.len);
+ // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later.
+ leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line_off);
+
+ dbg_line_buffer.appendAssumeCapacity(DW.LNS_set_file);
+ assert(self.getRelocDbgFileIndex() == dbg_line_buffer.items.len);
+ // Once we support more than one source file, this will have the ability to be more
+ // than one possible value.
+ const file_index = 1;
+ leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index);
+
+ // Emit a line for the begin curly with prologue_end=false. The codegen will
+ // do the work of setting prologue_end=true and epilogue_begin=true.
+ dbg_line_buffer.appendAssumeCapacity(DW.LNS_copy);
+ }
+ const res = try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer, &dbg_line_buffer);
+ const code = switch (res) {
.externally_managed => |x| x,
.appended => code_buffer.items,
.fail => |em| {
@@ -1223,12 +1883,9 @@ pub const File = struct {
},
};
- const required_alignment = typed_value.ty.abiAlignment(self.options.target);
+ const required_alignment = typed_value.ty.abiAlignment(self.base.options.target);
- const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) {
- .Fn => elf.STT_FUNC,
- else => elf.STT_OBJECT,
- };
+ const stt_bits: u8 = if (is_fn) elf.STT_FUNC else elf.STT_OBJECT;
assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes()
const local_sym = &self.local_symbols.items[decl.link.local_sym_index];
@@ -1238,11 +1895,11 @@ pub const File = struct {
!mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment);
if (need_realloc) {
const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment);
- std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr });
+ log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr });
if (vaddr != local_sym.st_value) {
local_sym.st_value = vaddr;
- std.log.debug(.link, " (writing new offset table entry)\n", .{});
+ log.debug(.link, " (writing new offset table entry)\n", .{});
self.offset_table.items[decl.link.offset_table_index] = vaddr;
try self.writeOffsetTableEntry(decl.link.offset_table_index);
}
@@ -1260,7 +1917,7 @@ pub const File = struct {
const decl_name = mem.spanZ(decl.name);
const name_str_index = try self.makeString(decl_name);
const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment);
- std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr });
+ log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr });
errdefer self.freeTextBlock(&decl.link);
local_sym.* = .{
@@ -1281,6 +1938,94 @@ pub const File = struct {
const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset;
try self.file.?.pwriteAll(code, file_offset);
+ // If the Decl is a function, we need to update the .debug_line program.
+ if (is_fn) {
+ // Perform the relocation based on vaddr.
+ const target_endian = self.base.options.target.cpu.arch.endian();
+ switch (self.ptr_width) {
+ .p32 => {
+ const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..4];
+ mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian);
+ },
+ .p64 => {
+ const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8];
+ mem.writeInt(u64, ptr, local_sym.st_value, target_endian);
+ },
+ }
+
+ try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS_extended_op, 1, DW.LNE_end_sequence });
+
+ // Now we have the full contents and may allocate a region to store it.
+
+ const debug_line_sect = &self.sections.items[self.debug_line_section_index.?];
+ const src_fn = &decl.fn_link;
+ src_fn.len = @intCast(u32, dbg_line_buffer.items.len);
+ if (self.dbg_line_fn_last) |last| {
+ if (src_fn.next) |next| {
+ // Update existing function - non-last item.
+ if (src_fn.off + src_fn.len + min_nop_size > next.off) {
+ // It grew too big, so we move it to a new location.
+ if (src_fn.prev) |prev| {
+ _ = self.dbg_line_fn_free_list.put(self.allocator, prev, {}) catch {};
+ prev.next = src_fn.next;
+ }
+ next.prev = src_fn.prev;
+ src_fn.next = null;
+ // Populate where it used to be with NOPs.
+ const file_pos = debug_line_sect.sh_offset + src_fn.off;
+ try self.pwriteWithNops(0, &[0]u8{}, src_fn.len, file_pos);
+ // TODO Look at the free list before appending at the end.
+ src_fn.prev = last;
+ last.next = src_fn;
+ self.dbg_line_fn_last = src_fn;
+
+ src_fn.off = last.off + (last.len * alloc_num / alloc_den);
+ }
+ } else if (src_fn.prev == null) {
+ // Append new function.
+ // TODO Look at the free list before appending at the end.
+ src_fn.prev = last;
+ last.next = src_fn;
+ self.dbg_line_fn_last = src_fn;
+
+ src_fn.off = last.off + (last.len * alloc_num / alloc_den);
+ }
+ } else {
+ // This is the first function of the Line Number Program.
+ self.dbg_line_fn_first = src_fn;
+ self.dbg_line_fn_last = src_fn;
+
+ src_fn.off = self.dbgLineNeededHeaderBytes() * alloc_num / alloc_den;
+ }
+
+ const last_src_fn = self.dbg_line_fn_last.?;
+ const needed_size = last_src_fn.off + last_src_fn.len;
+ if (needed_size != debug_line_sect.sh_size) {
+ if (needed_size > self.allocatedSize(debug_line_sect.sh_offset)) {
+ const new_offset = self.findFreeSpace(needed_size, 1);
+ const existing_size = last_src_fn.off;
+ log.debug(.link, "moving .debug_line section: {} bytes from 0x{x} to 0x{x}\n", .{
+ existing_size,
+ debug_line_sect.sh_offset,
+ new_offset,
+ });
+ const amt = try self.file.?.copyRangeAll(debug_line_sect.sh_offset, self.file.?, new_offset, existing_size);
+ if (amt != existing_size) return error.InputOutput;
+ debug_line_sect.sh_offset = new_offset;
+ }
+ debug_line_sect.sh_size = needed_size;
+ self.shdr_table_dirty = true; // TODO look into making only the one section dirty
+ self.debug_line_header_dirty = true;
+ }
+ const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0;
+ const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0;
+
+ // We only have support for one compilation unit so far, so the offsets are directly
+ // from the .debug_line section.
+ const file_pos = debug_line_sect.sh_offset + src_fn.off;
+ try self.pwriteWithNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos);
+ }
+
// Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
return self.updateDeclExports(module, decl, decl_exports);
@@ -1293,10 +2038,10 @@ pub const File = struct {
decl: *const Module.Decl,
exports: []const *Module.Export,
) !void {
- // In addition to ensuring capacity for global_symbols, we also ensure capacity for freeing all of
- // them, so that deleting exports is guaranteed to succeed.
+ const tracy = trace(@src());
+ defer tracy.end();
+
try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len);
- try self.global_symbol_free_list.ensureCapacity(self.allocator, self.global_symbols.items.len);
const typed_value = decl.typed_value.most_recent.typed_value;
if (decl.link.local_sym_index == 0) return;
const decl_sym = self.local_symbols.items[decl.link.local_sym_index];
@@ -1361,16 +2106,38 @@ pub const File = struct {
}
}
+ /// Must be called only after a successful call to `updateDecl`.
+ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Decl) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const scope_file = decl.scope.cast(Module.Scope.File).?;
+ const tree = scope_file.contents.tree;
+ const file_ast_decls = tree.root_node.decls();
+ // TODO Look into improving the performance here by adding a token-index-to-line
+ // lookup table. Currently this involves scanning over the source code for newlines.
+ const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?;
+ const block = fn_proto.body().?.castTag(.Block).?;
+ const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start);
+ const casted_line_off = @intCast(u28, line_delta);
+
+ const shdr = &self.sections.items[self.debug_line_section_index.?];
+ const file_pos = shdr.sh_offset + decl.fn_link.off + self.getRelocDbgLineOff();
+ var data: [4]u8 = undefined;
+ leb128.writeUnsignedFixed(4, &data, casted_line_off);
+ try self.file.?.pwriteAll(&data, file_pos);
+ }
+
pub fn deleteExport(self: *Elf, exp: Export) void {
const sym_index = exp.sym_index orelse return;
- self.global_symbol_free_list.appendAssumeCapacity(sym_index);
+ self.global_symbol_free_list.append(self.allocator, sym_index) catch {};
self.global_symbols.items[sym_index].st_info = 0;
}
fn writeProgHeader(self: *Elf, index: usize) !void {
- const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+ const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const offset = self.program_headers.items[index].p_offset;
- switch (self.options.target.cpu.arch.ptrBitWidth()) {
+ switch (self.base.options.target.cpu.arch.ptrBitWidth()) {
32 => {
var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};
if (foreign_endian) {
@@ -1390,15 +2157,15 @@ pub const File = struct {
}
fn writeSectHeader(self: *Elf, index: usize) !void {
- const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
- const offset = self.sections.items[index].sh_offset;
- switch (self.options.target.cpu.arch.ptrBitWidth()) {
+ const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+ switch (self.base.options.target.cpu.arch.ptrBitWidth()) {
32 => {
var shdr: [1]elf.Elf32_Shdr = undefined;
shdr[0] = sectHeaderTo32(self.sections.items[index]);
if (foreign_endian) {
bswapAllFields(elf.Elf32_Shdr, &shdr[0]);
}
+ const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr);
return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
},
64 => {
@@ -1406,6 +2173,7 @@ pub const File = struct {
if (foreign_endian) {
bswapAllFields(elf.Elf64_Shdr, &shdr[0]);
}
+ const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr);
return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
},
else => return error.UnsupportedArchitecture,
@@ -1415,10 +2183,7 @@ pub const File = struct {
fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
const shdr = &self.sections.items[self.got_section_index.?];
const phdr = &self.program_headers.items[self.phdr_got_index.?];
- const entry_size: u16 = switch (self.ptr_width) {
- .p32 => 4,
- .p64 => 8,
- };
+ const entry_size: u16 = self.ptrWidthBytes();
if (self.offset_table_count_dirty) {
// TODO Also detect virtual address collisions.
const allocated_size = self.allocatedSize(shdr.sh_offset);
@@ -1440,7 +2205,7 @@ pub const File = struct {
self.offset_table_count_dirty = false;
}
- const endian = self.options.target.cpu.arch.endian();
+ const endian = self.base.options.target.cpu.arch.endian();
const off = shdr.sh_offset + @as(u64, entry_size) * index;
switch (self.ptr_width) {
.p32 => {
@@ -1482,7 +2247,7 @@ pub const File = struct {
syms_sect.sh_size = needed_size; // anticipating adding the global symbols later
self.shdr_table_dirty = true; // TODO look into only writing one section
}
- const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+ const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
switch (self.ptr_width) {
.p32 => {
var sym = [1]elf.Elf32_Sym{
@@ -1518,7 +2283,7 @@ pub const File = struct {
.p32 => @sizeOf(elf.Elf32_Sym),
.p64 => @sizeOf(elf.Elf64_Sym),
};
- const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+ const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size;
switch (self.ptr_width) {
.p32 => {
@@ -1561,106 +2326,124 @@ pub const File = struct {
},
}
}
- };
-};
-/// Truncates the existing file contents and overwrites the contents.
-/// Returns an error if `file` is not already open with +read +write +seek abilities.
-pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !File.Elf {
- switch (options.output_mode) {
- .Exe => {},
- .Obj => {},
- .Lib => return error.TODOImplementWritingLibFiles,
- }
- switch (options.object_format) {
- .c => unreachable,
- .unknown => unreachable, // TODO remove this tag from the enum
- .coff => return error.TODOImplementWritingCOFF,
- .elf => {},
- .macho => return error.TODOImplementWritingMachO,
- .wasm => return error.TODOImplementWritingWasmObjects,
- .hex => return error.TODOImplementWritingHex,
- .raw => return error.TODOImplementWritingRaw,
- }
+ fn ptrWidthBytes(self: Elf) u8 {
+ return switch (self.ptr_width) {
+ .p32 => 4,
+ .p64 => 8,
+ };
+ }
- var self: File.Elf = .{
- .allocator = allocator,
- .file = file,
- .options = options,
- .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
- 32 => .p32,
- 64 => .p64,
- else => return error.UnsupportedELFArchitecture,
- },
- .shdr_table_dirty = true,
- .owns_file_handle = false,
- };
- errdefer self.deinit();
-
- // Index 0 is always a null symbol.
- try self.local_symbols.append(allocator, .{
- .st_name = 0,
- .st_info = 0,
- .st_other = 0,
- .st_shndx = 0,
- .st_value = 0,
- .st_size = 0,
- });
-
- // There must always be a null section in index 0
- try self.sections.append(allocator, .{
- .sh_name = 0,
- .sh_type = elf.SHT_NULL,
- .sh_flags = 0,
- .sh_addr = 0,
- .sh_offset = 0,
- .sh_size = 0,
- .sh_link = 0,
- .sh_info = 0,
- .sh_addralign = 0,
- .sh_entsize = 0,
- });
-
- try self.populateMissingMetadata();
-
- return self;
-}
+ /// The reloc offset for the virtual address of a function in its Line Number Program.
+ /// Size is a virtual address integer.
+ const dbg_line_vaddr_reloc_index = 3;
-/// Returns error.IncrFailed if incremental update could not be performed.
-fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !File.Elf {
- switch (options.output_mode) {
- .Exe => {},
- .Obj => {},
- .Lib => return error.IncrFailed,
- }
- switch (options.object_format) {
- .unknown => unreachable, // TODO remove this tag from the enum
- .c => unreachable,
- .coff => return error.IncrFailed,
- .elf => {},
- .macho => return error.IncrFailed,
- .wasm => return error.IncrFailed,
- .hex => return error.IncrFailed,
- .raw => return error.IncrFailed,
- }
- var self: File.Elf = .{
- .allocator = allocator,
- .file = file,
- .owns_file_handle = false,
- .options = options,
- .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
- 32 => .p32,
- 64 => .p64,
- else => return error.UnsupportedELFArchitecture,
- },
- };
- errdefer self.deinit();
+ /// The reloc offset for the line offset of a function from the previous function's line.
+ /// It's a fixed-size 4-byte ULEB128.
+ fn getRelocDbgLineOff(self: Elf) usize {
+ return dbg_line_vaddr_reloc_index + self.ptrWidthBytes() + 1;
+ }
- // TODO implement reading the elf file
- return error.IncrFailed;
- //try self.populateMissingMetadata();
- //return self;
-}
+ fn getRelocDbgFileIndex(self: Elf) usize {
+ return self.getRelocDbgLineOff() + 5;
+ }
+
+ fn dbgLineNeededHeaderBytes(self: Elf) u32 {
+ const directory_entry_format_count = 1;
+ const file_name_entry_format_count = 1;
+ const directory_count = 1;
+ const file_name_count = 1;
+ return @intCast(u32, 53 + directory_entry_format_count * 2 + file_name_entry_format_count * 2 +
+ directory_count * 8 + file_name_count * 8 +
+ // These are encoded as DW.FORM_string rather than DW.FORM_strp as we would like
+ // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly.
+ self.base.options.root_pkg.root_src_dir_path.len +
+ self.base.options.root_pkg.root_src_path.len);
+
+ }
+
+ /// Writes to the file a buffer, prefixed and suffixed by the specified number of
+ /// bytes of NOPs. Asserts each padding size is at least `min_nop_size` and total padding bytes
+ /// are less than 126,976 bytes (if this limit is ever reached, this function can be
+ /// improved to make more than one pwritev call, or the limit can be raised by a fixed
+ /// amount by increasing the length of `vecs`).
+ fn pwriteWithNops(
+ self: *Elf,
+ prev_padding_size: usize,
+ buf: []const u8,
+ next_padding_size: usize,
+ offset: usize,
+ ) !void {
+ const page_of_nops = [1]u8{DW.LNS_negate_stmt} ** 4096;
+ const three_byte_nop = [3]u8{DW.LNS_advance_pc, 0b1000_0000, 0};
+ var vecs: [32]std.os.iovec_const = undefined;
+ var vec_index: usize = 0;
+ {
+ var padding_left = prev_padding_size;
+ if (padding_left % 2 != 0) {
+ vecs[vec_index] = .{
+ .iov_base = &three_byte_nop,
+ .iov_len = three_byte_nop.len,
+ };
+ vec_index += 1;
+ padding_left -= three_byte_nop.len;
+ }
+ while (padding_left > page_of_nops.len) {
+ vecs[vec_index] = .{
+ .iov_base = &page_of_nops,
+ .iov_len = page_of_nops.len,
+ };
+ vec_index += 1;
+ padding_left -= page_of_nops.len;
+ }
+ if (padding_left > 0) {
+ vecs[vec_index] = .{
+ .iov_base = &page_of_nops,
+ .iov_len = padding_left,
+ };
+ vec_index += 1;
+ }
+ }
+
+ vecs[vec_index] = .{
+ .iov_base = buf.ptr,
+ .iov_len = buf.len,
+ };
+ vec_index += 1;
+
+ {
+ var padding_left = next_padding_size;
+ if (padding_left % 2 != 0) {
+ vecs[vec_index] = .{
+ .iov_base = &three_byte_nop,
+ .iov_len = three_byte_nop.len,
+ };
+ vec_index += 1;
+ padding_left -= three_byte_nop.len;
+ }
+ while (padding_left > page_of_nops.len) {
+ vecs[vec_index] = .{
+ .iov_base = &page_of_nops,
+ .iov_len = page_of_nops.len,
+ };
+ vec_index += 1;
+ padding_left -= page_of_nops.len;
+ }
+ if (padding_left > 0) {
+ vecs[vec_index] = .{
+ .iov_base = &page_of_nops,
+ .iov_len = padding_left,
+ };
+ vec_index += 1;
+ }
+ }
+ try self.file.?.pwritevAll(vecs[0..vec_index], offset - prev_padding_size);
+ }
+
+ const min_nop_size = 2;
+
+ };
+};
/// Saturating multiplication
fn satMul(a: anytype, b: anytype) @TypeOf(a, b) {
diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig
@@ -10,9 +10,7 @@ const Module = @import("Module.zig");
const link = @import("link.zig");
const Package = @import("Package.zig");
const zir = @import("zir.zig");
-
-// TODO Improve async I/O enough that we feel comfortable doing this.
-//pub const io_mode = .evented;
+const build_options = @import("build_options");
pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
@@ -47,18 +45,16 @@ pub fn log(
if (@enumToInt(level) > @enumToInt(std.log.level))
return;
- const scope_prefix = "(" ++ switch (scope) {
- // Uncomment to hide logs
- //.compiler,
- .module,
- .liveness,
- .link,
- => return,
+ const scope_name = @tagName(scope);
+ const ok = comptime for (build_options.log_scopes) |log_scope| {
+ if (mem.eql(u8, log_scope, scope_name))
+ break true;
+ } else false;
- else => @tagName(scope),
- } ++ "): ";
+ if (!ok)
+ return;
- const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix;
+ const prefix = "[" ++ @tagName(level) ++ "] " ++ "(" ++ @tagName(scope) ++ "): ";
// Print the message to stderr, silently ignoring any errors
std.debug.print(prefix ++ format, args);
@@ -94,6 +90,8 @@ pub fn main() !void {
return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
} else if (mem.eql(u8, cmd, "version")) {
// Need to set up the build script to give the version as a comptime value.
+ // TODO when you solve this, also take a look at link.zig, there is a placeholder
+ // that says "TODO version here".
std.debug.print("TODO version command not implemented yet\n", .{});
return error.Unimplemented;
} else if (mem.eql(u8, cmd, "zen")) {
@@ -492,6 +490,7 @@ fn buildOutputType(
defer root_pkg.destroy();
var module = try Module.init(gpa, .{
+ .root_name = root_name,
.target = target_info.target,
.output_mode = output_mode,
.root_pkg = root_pkg,
diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig
@@ -4,6 +4,11 @@ const Module = @import("Module.zig");
const Allocator = std.mem.Allocator;
const zir = @import("zir.zig");
const Package = @import("Package.zig");
+const build_options = @import("build_options");
+const enable_qemu: bool = build_options.enable_qemu;
+const enable_wine: bool = build_options.enable_wine;
+const enable_wasmtime: bool = build_options.enable_wasmtime;
+const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_dir;
const cheader = @embedFile("cbe.h");
@@ -401,8 +406,6 @@ pub const TestContext = struct {
const root_node = try progress.start("tests", self.cases.items.len);
defer root_node.end();
- const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{});
-
for (self.cases.items) |case| {
std.testing.base_allocator_instance.reset();
@@ -415,13 +418,19 @@ pub const TestContext = struct {
progress.initial_delay_ns = 0;
progress.refresh_rate_ns = 0;
- const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target);
- try self.runOneCase(std.testing.allocator, &prg_node, case, info.target);
+ try self.runOneCase(std.testing.allocator, &prg_node, case);
try std.testing.allocator_instance.validate();
}
}
- fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case, target: std.Target) !void {
+ fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case) !void {
+ const target_info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target);
+ const target = target_info.target;
+
+ var arena_allocator = std.heap.ArenaAllocator.init(allocator);
+ defer arena_allocator.deinit();
+ const arena = &arena_allocator.allocator;
+
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
@@ -429,10 +438,10 @@ pub const TestContext = struct {
const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path);
defer root_pkg.destroy();
- const bin_name = try std.zig.binNameAlloc(allocator, "test_case", target, case.output_mode, null);
- defer allocator.free(bin_name);
+ const bin_name = try std.zig.binNameAlloc(arena, "test_case", target, case.output_mode, null);
var module = try Module.init(allocator, .{
+ .root_name = "test_case",
.target = target,
// TODO: support tests for object file building, and library builds
// and linking. This will require a rework to support multi-file
@@ -484,8 +493,7 @@ pub const TestContext = struct {
// incremental updates
var file = try tmp.dir.openFile(bin_name, .{ .read = true });
defer file.close();
- var out = file.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!");
- defer allocator.free(out);
+ var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!");
if (expected_output.len != out.len) {
std.debug.warn("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
@@ -532,8 +540,7 @@ pub const TestContext = struct {
var test_node = update_node.start("assert", null);
test_node.activate();
defer test_node.end();
- var handled_errors = try allocator.alloc(bool, e.len);
- defer allocator.free(handled_errors);
+ var handled_errors = try arena.alloc(bool, e.len);
for (handled_errors) |*h| {
h.* = false;
}
@@ -568,14 +575,59 @@ pub const TestContext = struct {
exec_node.activate();
defer exec_node.end();
- try module.makeBinFileExecutable();
+ var argv = std.ArrayList([]const u8).init(allocator);
+ defer argv.deinit();
+
+ const exe_path = try std.fmt.allocPrint(arena, "." ++ std.fs.path.sep_str ++ "{}", .{bin_name});
+
+ switch (case.target.getExternalExecutor()) {
+ .native => try argv.append(exe_path),
+ .unavailable => return, // No executor available; pass test.
+
+ .qemu => |qemu_bin_name| if (enable_qemu) {
+ // TODO Ability for test cases to specify whether to link libc.
+ const need_cross_glibc = false; // target.isGnuLibC() and self.is_linking_libc;
+ const glibc_dir_arg = if (need_cross_glibc)
+ glibc_multi_install_dir orelse return // glibc dir not available; pass test
+ else
+ null;
+ try argv.append(qemu_bin_name);
+ if (glibc_dir_arg) |dir| {
+ const linux_triple = try target.linuxTriple(arena);
+ const full_dir = try std.fs.path.join(arena, &[_][]const u8{
+ dir,
+ linux_triple,
+ });
+
+ try argv.append("-L");
+ try argv.append(full_dir);
+ }
+ try argv.append(exe_path);
+ } else {
+ return; // QEMU not available; pass test.
+ },
+
+ .wine => |wine_bin_name| if (enable_wine) {
+ try argv.append(wine_bin_name);
+ try argv.append(exe_path);
+ } else {
+ return; // Wine not available; pass test.
+ },
+
+ .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) {
+ try argv.append(wasmtime_bin_name);
+ try argv.append("--dir=.");
+ try argv.append(exe_path);
+ } else {
+ return; // wasmtime not available; pass test.
+ },
+ }
- const exe_path = try std.fmt.allocPrint(allocator, "." ++ std.fs.path.sep_str ++ "{}", .{bin_name});
- defer allocator.free(exe_path);
+ try module.makeBinFileExecutable();
break :x try std.ChildProcess.exec(.{
.allocator = allocator,
- .argv = &[_][]const u8{exe_path},
+ .argv = argv.items,
.cwd_dir = tmp.dir,
});
};
diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig
@@ -8,7 +8,6 @@ const Token = std.zig.Token;
usingnamespace @import("clang.zig");
const ctok = std.c.tokenizer;
const CToken = std.c.Token;
-const CTokenList = std.c.tokenizer.Source.TokenList;
const mem = std.mem;
const math = std.math;
@@ -2864,7 +2863,6 @@ fn transCharLiteral(
"TODO: support character literal kind {}",
.{kind},
),
- else => unreachable,
};
if (suppress_as == .no_as) {
return maybeSuppressResult(rp, scope, result_used, int_lit_node);
@@ -3070,13 +3068,30 @@ fn transUnaryExprOrTypeTraitExpr(
stmt: *const ZigClangUnaryExprOrTypeTraitExpr,
result_used: ResultUsed,
) TransError!*ast.Node {
+ const loc = ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(stmt);
const type_node = try transQualType(
rp,
ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(stmt),
- ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(stmt),
+ loc,
);
- const builtin_node = try rp.c.createBuiltinCall("@sizeOf", 1);
+ const kind = ZigClangUnaryExprOrTypeTraitExpr_getKind(stmt);
+ const kind_str = switch (kind) {
+ .SizeOf => "@sizeOf",
+ .AlignOf => "@alignOf",
+ .PreferredAlignOf,
+ .VecStep,
+ .OpenMPRequiredSimdAlign,
+ => return revertAndWarn(
+ rp,
+ error.UnsupportedTranslation,
+ loc,
+ "Unsupported type trait kind {}",
+ .{kind},
+ ),
+ };
+
+ const builtin_node = try rp.c.createBuiltinCall(kind_str, 1);
builtin_node.params()[0] = type_node;
builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");
return maybeSuppressResult(rp, scope, result_used, &builtin_node.base);
@@ -4515,7 +4530,7 @@ const CtrlFlow = struct {
const ltoken = try appendToken(c, kw, kw_text);
const label_token = if (label) |l| blk: {
_ = try appendToken(c, .Colon, ":");
- break :blk try appendToken(c, .Identifier, l);
+ break :blk try appendIdentifier(c, l);
} else null;
return CtrlFlow{
.c = c,
@@ -5197,16 +5212,39 @@ pub fn freeErrors(errors: []ClangErrMsg) void {
ZigClangErrorMsg_delete(errors.ptr, errors.len);
}
+const CTokIterator = struct {
+ source: []const u8,
+ list: []const CToken,
+ i: usize = 0,
+
+ fn peek(self: *CTokIterator) ?CToken.Id {
+ if (self.i >= self.list.len) return null;
+ return self.list[self.i + 1].id;
+ }
+
+ fn next(self: *CTokIterator) ?CToken.Id {
+ if (self.i >= self.list.len) return null;
+ self.i += 1;
+ return self.list[self.i].id;
+ }
+
+ fn slice(self: *CTokIterator, index: usize) []const u8 {
+ const tok = self.list[index];
+ return self.source[tok.start..tok.end];
+ }
+};
+
fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
// TODO if we see #undef, delete it from the table
var it = ZigClangASTUnit_getLocalPreprocessingEntities_begin(unit);
const it_end = ZigClangASTUnit_getLocalPreprocessingEntities_end(unit);
- var tok_list = CTokenList.init(c.arena);
+ var tok_list = std.ArrayList(CToken).init(c.gpa);
+ defer tok_list.deinit();
const scope = c.global_scope;
while (it.I != it_end.I) : (it.I += 1) {
const entity = ZigClangPreprocessingRecord_iterator_deref(it);
- tok_list.shrink(0);
+ tok_list.items.len = 0;
switch (ZigClangPreprocessedEntity_getKind(entity)) {
.MacroDefinitionKind => {
const macro = @ptrCast(*ZigClangMacroDefinitionRecord, entity);
@@ -5224,38 +5262,34 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
const begin_c = ZigClangSourceManager_getCharacterData(c.source_manager, begin_loc);
const slice = begin_c[0..mem.len(begin_c)];
- tok_list.shrink(0);
var tokenizer = std.c.Tokenizer{
- .source = &std.c.tokenizer.Source{
- .buffer = slice,
- .file_name = undefined,
- .tokens = undefined,
- },
+ .buffer = slice,
};
while (true) {
const tok = tokenizer.next();
switch (tok.id) {
.Nl, .Eof => {
- try tok_list.push(tok);
+ try tok_list.append(tok);
break;
},
.LineComment, .MultiLineComment => continue,
else => {},
}
- try tok_list.push(tok);
+ try tok_list.append(tok);
}
- var tok_it = tok_list.iterator(0);
- const first_tok = tok_it.next().?;
- assert(mem.eql(u8, slice[first_tok.start..first_tok.end], name));
+ var tok_it = CTokIterator{
+ .source = slice,
+ .list = tok_list.items,
+ };
+ assert(mem.eql(u8, tok_it.slice(0), name));
var macro_fn = false;
- const next = tok_it.peek().?;
- switch (next.id) {
+ switch (tok_it.peek().?) {
.Identifier => {
// if it equals itself, ignore. for example, from stdio.h:
// #define stdin stdin
- if (mem.eql(u8, name, slice[next.start..next.end])) {
+ if (mem.eql(u8, name, tok_it.slice(1))) {
continue;
}
},
@@ -5266,15 +5300,15 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
},
.LParen => {
// if the name is immediately followed by a '(' then it is a function
- macro_fn = first_tok.end == next.start;
+ macro_fn = tok_it.list[0].end == tok_it.list[1].start;
},
else => {},
}
(if (macro_fn)
- transMacroFnDefine(c, &tok_it, slice, mangled_name, begin_loc)
+ transMacroFnDefine(c, &tok_it, mangled_name, begin_loc)
else
- transMacroDefine(c, &tok_it, slice, mangled_name, begin_loc)) catch |err| switch (err) {
+ transMacroDefine(c, &tok_it, mangled_name, begin_loc)) catch |err| switch (err) {
error.ParseError => continue,
error.OutOfMemory => |e| return e,
};
@@ -5284,7 +5318,7 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
}
}
-fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
+fn transMacroDefine(c: *Context, it: *CTokIterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
const scope = &c.global_scope.base;
const visib_tok = try appendToken(c, .Keyword_pub, "pub");
@@ -5292,15 +5326,15 @@ fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, n
const name_tok = try appendIdentifier(c, name);
const eq_token = try appendToken(c, .Equal, "=");
- const init_node = try parseCExpr(c, it, source, source_loc, scope);
+ const init_node = try parseCExpr(c, it, source_loc, scope);
const last = it.next().?;
- if (last.id != .Eof and last.id != .Nl)
+ if (last != .Eof and last != .Nl)
return failDecl(
c,
source_loc,
name,
"unable to translate C expr: unexpected token .{}",
- .{@tagName(last.id)},
+ .{@tagName(last)},
);
const semicolon_token = try appendToken(c, .Semicolon, ";");
@@ -5316,7 +5350,7 @@ fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, n
_ = try c.global_scope.macro_table.put(name, &node.base);
}
-fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
+fn transMacroFnDefine(c: *Context, it: *CTokIterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
var block_scope = try Scope.Block.init(c, &c.global_scope.base, null);
defer block_scope.deinit();
const scope = &block_scope.base;
@@ -5327,7 +5361,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
const name_tok = try appendIdentifier(c, name);
_ = try appendToken(c, .LParen, "(");
- if (it.next().?.id != .LParen) {
+ if (it.next().? != .LParen) {
return failDecl(
c,
source_loc,
@@ -5341,8 +5375,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
defer fn_params.deinit();
while (true) {
- const param_tok = it.next().?;
- if (param_tok.id != .Identifier) {
+ if (it.next().? != .Identifier) {
return failDecl(
c,
source_loc,
@@ -5352,7 +5385,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
);
}
- const mangled_name = try block_scope.makeMangledName(c, source[param_tok.start..param_tok.end]);
+ const mangled_name = try block_scope.makeMangledName(c, it.slice(it.i));
const param_name_tok = try appendIdentifier(c, mangled_name);
_ = try appendToken(c, .Colon, ":");
@@ -5370,13 +5403,13 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
.param_type = .{ .any_type = &any_type.base },
};
- if (it.peek().?.id != .Comma)
+ if (it.peek().? != .Comma)
break;
_ = it.next();
_ = try appendToken(c, .Comma, ",");
}
- if (it.next().?.id != .RParen) {
+ if (it.next().? != .RParen) {
return failDecl(
c,
source_loc,
@@ -5391,15 +5424,15 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
const type_of = try c.createBuiltinCall("@TypeOf", 1);
const return_kw = try appendToken(c, .Keyword_return, "return");
- const expr = try parseCExpr(c, it, source, source_loc, scope);
+ const expr = try parseCExpr(c, it, source_loc, scope);
const last = it.next().?;
- if (last.id != .Eof and last.id != .Nl)
+ if (last != .Eof and last != .Nl)
return failDecl(
c,
source_loc,
name,
"unable to translate C expr: unexpected token .{}",
- .{@tagName(last.id)},
+ .{@tagName(last)},
);
_ = try appendToken(c, .Semicolon, ";");
const type_of_arg = if (expr.tag != .Block) expr else blk: {
@@ -5436,28 +5469,27 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
const ParseError = Error || error{ParseError};
-fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
- const node = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
- switch (it.next().?.id) {
+fn parseCExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
+ const node = try parseCPrefixOpExpr(c, it, source_loc, scope);
+ switch (it.next().?) {
.QuestionMark => {
// must come immediately after expr
_ = try appendToken(c, .RParen, ")");
const if_node = try transCreateNodeIf(c);
if_node.condition = node;
- if_node.body = try parseCPrimaryExpr(c, it, source, source_loc, scope);
- if (it.next().?.id != .Colon) {
- const first_tok = it.list.at(0);
+ if_node.body = try parseCPrimaryExpr(c, it, source_loc, scope);
+ if (it.next().? != .Colon) {
try failDecl(
c,
source_loc,
- source[first_tok.start..first_tok.end],
+ it.slice(0),
"unable to translate C expr: expected ':'",
.{},
);
return error.ParseError;
}
if_node.@"else" = try transCreateNodeElse(c);
- if_node.@"else".?.body = try parseCPrimaryExpr(c, it, source, source_loc, scope);
+ if_node.@"else".?.body = try parseCPrimaryExpr(c, it, source_loc, scope);
return &if_node.base;
},
.Comma => {
@@ -5480,10 +5512,10 @@ fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_
};
try block_scope.statements.append(&op_node.base);
- last = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+ last = try parseCPrefixOpExpr(c, it, source_loc, scope);
_ = try appendToken(c, .Semicolon, ";");
- if (it.next().?.id != .Comma) {
- _ = it.prev();
+ if (it.next().? != .Comma) {
+ it.i -= 1;
break;
}
}
@@ -5494,70 +5526,74 @@ fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_
return &block_node.base;
},
else => {
- _ = it.prev();
+ it.i -= 1;
return node;
},
}
}
-fn parseCNumLit(c: *Context, tok: *CToken, source: []const u8, source_loc: ZigClangSourceLocation) ParseError!*ast.Node {
- var lit_bytes = source[tok.start..tok.end];
+fn parseCNumLit(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation) ParseError!*ast.Node {
+ var lit_bytes = it.slice(it.i);
- if (tok.id == .IntegerLiteral) {
- if (lit_bytes.len > 2 and lit_bytes[0] == '0') {
- switch (lit_bytes[1]) {
- '0'...'7' => {
- // Octal
- lit_bytes = try std.fmt.allocPrint(c.arena, "0o{}", .{lit_bytes});
- },
- 'X' => {
- // Hexadecimal with capital X, valid in C but not in Zig
- lit_bytes = try std.fmt.allocPrint(c.arena, "0x{}", .{lit_bytes[2..]});
- },
- else => {},
+ switch (it.list[it.i].id) {
+ .IntegerLiteral => |suffix| {
+ if (lit_bytes.len > 2 and lit_bytes[0] == '0') {
+ switch (lit_bytes[1]) {
+ '0'...'7' => {
+ // Octal
+ lit_bytes = try std.fmt.allocPrint(c.arena, "0o{}", .{lit_bytes});
+ },
+ 'X' => {
+ // Hexadecimal with capital X, valid in C but not in Zig
+ lit_bytes = try std.fmt.allocPrint(c.arena, "0x{}", .{lit_bytes[2..]});
+ },
+ else => {},
+ }
}
- }
- if (tok.id.IntegerLiteral == .None) {
- return transCreateNodeInt(c, lit_bytes);
- }
+ if (suffix == .none) {
+ return transCreateNodeInt(c, lit_bytes);
+ }
- const cast_node = try c.createBuiltinCall("@as", 2);
- cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (tok.id.IntegerLiteral) {
- .U => "c_uint",
- .L => "c_long",
- .LU => "c_ulong",
- .LL => "c_longlong",
- .LLU => "c_ulonglong",
- else => unreachable,
- });
- lit_bytes = lit_bytes[0 .. lit_bytes.len - switch (tok.id.IntegerLiteral) {
- .U, .L => @as(u8, 1),
- .LU, .LL => 2,
- .LLU => 3,
- else => unreachable,
- }];
- _ = try appendToken(c, .Comma, ",");
- cast_node.params()[1] = try transCreateNodeInt(c, lit_bytes);
- cast_node.rparen_token = try appendToken(c, .RParen, ")");
- return &cast_node.base;
- } else if (tok.id == .FloatLiteral) {
- if (lit_bytes[0] == '.')
- lit_bytes = try std.fmt.allocPrint(c.arena, "0{}", .{lit_bytes});
- if (tok.id.FloatLiteral == .None) {
- return transCreateNodeFloat(c, lit_bytes);
- }
- const cast_node = try c.createBuiltinCall("@as", 2);
- cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (tok.id.FloatLiteral) {
- .F => "f32",
- .L => "c_longdouble",
- else => unreachable,
- });
- _ = try appendToken(c, .Comma, ",");
- cast_node.params()[1] = try transCreateNodeFloat(c, lit_bytes[0 .. lit_bytes.len - 1]);
- cast_node.rparen_token = try appendToken(c, .RParen, ")");
- return &cast_node.base;
- } else unreachable;
+ const cast_node = try c.createBuiltinCall("@as", 2);
+ cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (suffix) {
+ .u => "c_uint",
+ .l => "c_long",
+ .lu => "c_ulong",
+ .ll => "c_longlong",
+ .llu => "c_ulonglong",
+ else => unreachable,
+ });
+ lit_bytes = lit_bytes[0 .. lit_bytes.len - switch (suffix) {
+ .u, .l => @as(u8, 1),
+ .lu, .ll => 2,
+ .llu => 3,
+ else => unreachable,
+ }];
+ _ = try appendToken(c, .Comma, ",");
+ cast_node.params()[1] = try transCreateNodeInt(c, lit_bytes);
+ cast_node.rparen_token = try appendToken(c, .RParen, ")");
+ return &cast_node.base;
+ },
+ .FloatLiteral => |suffix| {
+ if (lit_bytes[0] == '.')
+ lit_bytes = try std.fmt.allocPrint(c.arena, "0{}", .{lit_bytes});
+ if (suffix == .none) {
+ return transCreateNodeFloat(c, lit_bytes);
+ }
+ const cast_node = try c.createBuiltinCall("@as", 2);
+ cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (suffix) {
+ .f => "f32",
+ .l => "c_longdouble",
+ else => unreachable,
+ });
+ _ = try appendToken(c, .Comma, ",");
+ cast_node.params()[1] = try transCreateNodeFloat(c, lit_bytes[0 .. lit_bytes.len - 1]);
+ cast_node.rparen_token = try appendToken(c, .RParen, ")");
+ return &cast_node.base;
+ },
+ else => unreachable,
+ }
}
fn zigifyEscapeSequences(ctx: *Context, source_bytes: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ![]const u8 {
@@ -5720,13 +5756,13 @@ fn zigifyEscapeSequences(ctx: *Context, source_bytes: []const u8, name: []const
return bytes[0..i];
}
-fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
+fn parseCPrimaryExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
const tok = it.next().?;
- switch (tok.id) {
+ const slice = it.slice(it.i);
+ switch (tok) {
.CharLiteral => {
- const first_tok = it.list.at(0);
- if (source[tok.start] != '\'' or source[tok.start + 1] == '\\' or tok.end - tok.start == 3) {
- const token = try appendToken(c, .CharLiteral, try zigifyEscapeSequences(c, source[tok.start..tok.end], source[first_tok.start..first_tok.end], source_loc));
+ if (slice[0] != '\'' or slice[1] == '\\' or slice.len == 3) {
+ const token = try appendToken(c, .CharLiteral, try zigifyEscapeSequences(c, slice, it.slice(0), source_loc));
const node = try c.arena.create(ast.Node.OneToken);
node.* = .{
.base = .{ .tag = .CharLiteral },
@@ -5734,7 +5770,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
};
return &node.base;
} else {
- const token = try appendTokenFmt(c, .IntegerLiteral, "0x{x}", .{source[tok.start + 1 .. tok.end - 1]});
+ const token = try appendTokenFmt(c, .IntegerLiteral, "0x{x}", .{slice[1 .. slice.len - 1]});
const node = try c.arena.create(ast.Node.OneToken);
node.* = .{
.base = .{ .tag = .IntegerLiteral },
@@ -5744,8 +5780,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
}
},
.StringLiteral => {
- const first_tok = it.list.at(0);
- const token = try appendToken(c, .StringLiteral, try zigifyEscapeSequences(c, source[tok.start..tok.end], source[first_tok.start..first_tok.end], source_loc));
+ const token = try appendToken(c, .StringLiteral, try zigifyEscapeSequences(c, slice, it.slice(0), source_loc));
const node = try c.arena.create(ast.Node.OneToken);
node.* = .{
.base = .{ .tag = .StringLiteral },
@@ -5754,7 +5789,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
return &node.base;
},
.IntegerLiteral, .FloatLiteral => {
- return parseCNumLit(c, tok, source, source_loc);
+ return parseCNumLit(c, it, source_loc);
},
// eventually this will be replaced by std.c.parse which will handle these correctly
.Keyword_void => return transCreateNodeIdentifierUnchecked(c, "c_void"),
@@ -5764,22 +5799,50 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
.Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_int"),
.Keyword_float => return transCreateNodeIdentifierUnchecked(c, "f32"),
.Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_short"),
- .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "c_char"),
- .Keyword_unsigned => return transCreateNodeIdentifierUnchecked(c, "c_uint"),
+ .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "u8"),
+ .Keyword_unsigned => if (it.next()) |t| switch (t) {
+ .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "u8"),
+ .Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_ushort"),
+ .Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_uint"),
+ .Keyword_long => if (it.peek() != null and it.peek().? == .Keyword_long) {
+ _ = it.next();
+ return transCreateNodeIdentifierUnchecked(c, "c_ulonglong");
+ } else return transCreateNodeIdentifierUnchecked(c, "c_ulong"),
+ else => {
+ it.i -= 1;
+ return transCreateNodeIdentifierUnchecked(c, "c_uint");
+ },
+ } else {
+ return transCreateNodeIdentifierUnchecked(c, "c_uint");
+ },
+ .Keyword_signed => if (it.next()) |t| switch (t) {
+ .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "i8"),
+ .Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_short"),
+ .Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_int"),
+ .Keyword_long => if (it.peek() != null and it.peek().? == .Keyword_long) {
+ _ = it.next();
+ return transCreateNodeIdentifierUnchecked(c, "c_longlong");
+ } else return transCreateNodeIdentifierUnchecked(c, "c_long"),
+ else => {
+ it.i -= 1;
+ return transCreateNodeIdentifierUnchecked(c, "c_int");
+ },
+ } else {
+ return transCreateNodeIdentifierUnchecked(c, "c_int");
+ },
.Identifier => {
- const mangled_name = scope.getAlias(source[tok.start..tok.end]);
+ const mangled_name = scope.getAlias(it.slice(it.i));
return transCreateNodeIdentifier(c, mangled_name);
},
.LParen => {
- const inner_node = try parseCExpr(c, it, source, source_loc, scope);
+ const inner_node = try parseCExpr(c, it, source_loc, scope);
- const next_id = it.next().?.id;
+ const next_id = it.next().?;
if (next_id != .RParen) {
- const first_tok = it.list.at(0);
try failDecl(
c,
source_loc,
- source[first_tok.start..first_tok.end],
+ it.slice(0),
"unable to translate C expr: expected ')'' instead got: {}",
.{@tagName(next_id)},
);
@@ -5787,7 +5850,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
}
var saw_l_paren = false;
var saw_integer_literal = false;
- switch (it.peek().?.id) {
+ switch (it.peek().?) {
// (type)(to_cast)
.LParen => {
saw_l_paren = true;
@@ -5805,14 +5868,13 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
// hack to get zig fmt to render a comma in builtin calls
_ = try appendToken(c, .Comma, ",");
- const node_to_cast = try parseCExpr(c, it, source, source_loc, scope);
+ const node_to_cast = try parseCExpr(c, it, source_loc, scope);
- if (saw_l_paren and it.next().?.id != .RParen) {
- const first_tok = it.list.at(0);
+ if (saw_l_paren and it.next().? != .RParen) {
try failDecl(
c,
source_loc,
- source[first_tok.start..first_tok.end],
+ it.slice(0),
"unable to translate C expr: expected ')''",
.{},
);
@@ -5843,13 +5905,12 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
return &group_node.base;
},
else => {
- const first_tok = it.list.at(0);
try failDecl(
c,
source_loc,
- source[first_tok.start..first_tok.end],
+ it.slice(0),
"unable to translate C expr: unexpected token .{}",
- .{@tagName(tok.id)},
+ .{@tagName(tok)},
);
return error.ParseError;
},
@@ -5957,61 +6018,52 @@ fn macroIntToBool(c: *Context, node: *ast.Node) !*ast.Node {
return &group_node.base;
}
-fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
- var node = try parseCPrimaryExpr(c, it, source, source_loc, scope);
+fn parseCSuffixOpExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
+ var node = try parseCPrimaryExpr(c, it, source_loc, scope);
while (true) {
- const tok = it.next().?;
var op_token: ast.TokenIndex = undefined;
var op_id: ast.Node.Tag = undefined;
var bool_op = false;
- switch (tok.id) {
+ switch (it.next().?) {
.Period => {
- const name_tok = it.next().?;
- if (name_tok.id != .Identifier) {
- const first_tok = it.list.at(0);
+ if (it.next().? != .Identifier) {
try failDecl(
c,
source_loc,
- source[first_tok.start..first_tok.end],
+ it.slice(0),
"unable to translate C expr: expected identifier",
.{},
);
return error.ParseError;
}
- node = try transCreateNodeFieldAccess(c, node, source[name_tok.start..name_tok.end]);
+ node = try transCreateNodeFieldAccess(c, node, it.slice(it.i));
continue;
},
.Arrow => {
- const name_tok = it.next().?;
- if (name_tok.id != .Identifier) {
- const first_tok = it.list.at(0);
+ if (it.next().? != .Identifier) {
try failDecl(
c,
source_loc,
- source[first_tok.start..first_tok.end],
+ it.slice(0),
"unable to translate C expr: expected identifier",
.{},
);
return error.ParseError;
}
const deref = try transCreateNodePtrDeref(c, node);
- node = try transCreateNodeFieldAccess(c, deref, source[name_tok.start..name_tok.end]);
+ node = try transCreateNodeFieldAccess(c, deref, it.slice(it.i));
continue;
},
.Asterisk => {
- if (it.peek().?.id == .RParen) {
+ if (it.peek().? == .RParen) {
// type *)
// hack to get zig fmt to render a comma in builtin calls
_ = try appendToken(c, .Comma, ",");
- // * token
- _ = it.prev();
// last token of `node`
- const prev_id = it.prev().?.id;
- _ = it.next();
- _ = it.next();
+ const prev_id = it.list[it.i - 1].id;
if (prev_id == .Keyword_void) {
const ptr = try transCreateNodePtrType(c, false, false, .Asterisk);
@@ -6082,15 +6134,14 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
},
.LBracket => {
const arr_node = try transCreateNodeArrayAccess(c, node);
- arr_node.index_expr = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+ arr_node.index_expr = try parseCPrefixOpExpr(c, it, source_loc, scope);
arr_node.rtoken = try appendToken(c, .RBracket, "]");
node = &arr_node.base;
- if (it.next().?.id != .RBracket) {
- const first_tok = it.list.at(0);
+ if (it.next().? != .RBracket) {
try failDecl(
c,
source_loc,
- source[first_tok.start..first_tok.end],
+ it.slice(0),
"unable to translate C expr: expected ']'",
.{},
);
@@ -6103,23 +6154,21 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
var call_params = std.ArrayList(*ast.Node).init(c.gpa);
defer call_params.deinit();
while (true) {
- const arg = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+ const arg = try parseCPrefixOpExpr(c, it, source_loc, scope);
try call_params.append(arg);
- const next = it.next().?;
- if (next.id == .Comma)
- _ = try appendToken(c, .Comma, ",")
- else if (next.id == .RParen)
- break
- else {
- const first_tok = it.list.at(0);
- try failDecl(
- c,
- source_loc,
- source[first_tok.start..first_tok.end],
- "unable to translate C expr: expected ',' or ')'",
- .{},
- );
- return error.ParseError;
+ switch (it.next().?) {
+ .Comma => _ = try appendToken(c, .Comma, ","),
+ .RParen => break,
+ else => {
+ try failDecl(
+ c,
+ source_loc,
+ it.slice(0),
+ "unable to translate C expr: expected ',' or ')'",
+ .{},
+ );
+ return error.ParseError;
+ },
}
}
const call_node = try ast.Node.Call.alloc(c.arena, call_params.items.len);
@@ -6144,23 +6193,21 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
defer init_vals.deinit();
while (true) {
- const val = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+ const val = try parseCPrefixOpExpr(c, it, source_loc, scope);
try init_vals.append(val);
- const next = it.next().?;
- if (next.id == .Comma)
- _ = try appendToken(c, .Comma, ",")
- else if (next.id == .RBrace)
- break
- else {
- const first_tok = it.list.at(0);
- try failDecl(
- c,
- source_loc,
- source[first_tok.start..first_tok.end],
- "unable to translate C expr: expected ',' or '}}'",
- .{},
- );
- return error.ParseError;
+ switch (it.next().?) {
+ .Comma => _ = try appendToken(c, .Comma, ","),
+ .RBrace => break,
+ else => {
+ try failDecl(
+ c,
+ source_loc,
+ it.slice(0),
+ "unable to translate C expr: expected ',' or '}}'",
+ .{},
+ );
+ return error.ParseError;
+ },
}
}
const tuple_node = try ast.Node.StructInitializerDot.alloc(c.arena, init_vals.items.len);
@@ -6207,22 +6254,22 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
op_id = .ArrayCat;
op_token = try appendToken(c, .PlusPlus, "++");
- _ = it.prev();
+ it.i -= 1;
},
.Identifier => {
op_id = .ArrayCat;
op_token = try appendToken(c, .PlusPlus, "++");
- _ = it.prev();
+ it.i -= 1;
},
else => {
- _ = it.prev();
+ it.i -= 1;
return node;
},
}
const cast_fn = if (bool_op) macroIntToBool else macroBoolToInt;
const lhs_node = try cast_fn(c, node);
- const rhs_node = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+ const rhs_node = try parseCPrefixOpExpr(c, it, source_loc, scope);
const op_node = try c.arena.create(ast.Node.SimpleInfixOp);
op_node.* = .{
.base = .{ .tag = op_id },
@@ -6234,38 +6281,36 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
}
}
-fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
- const op_tok = it.next().?;
-
- switch (op_tok.id) {
+fn parseCPrefixOpExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
+ switch (it.next().?) {
.Bang => {
const node = try transCreateNodeSimplePrefixOp(c, .BoolNot, .Bang, "!");
- node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+ node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);
return &node.base;
},
.Minus => {
const node = try transCreateNodeSimplePrefixOp(c, .Negation, .Minus, "-");
- node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+ node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);
return &node.base;
},
- .Plus => return try parseCPrefixOpExpr(c, it, source, source_loc, scope),
+ .Plus => return try parseCPrefixOpExpr(c, it, source_loc, scope),
.Tilde => {
const node = try transCreateNodeSimplePrefixOp(c, .BitNot, .Tilde, "~");
- node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+ node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);
return &node.base;
},
.Asterisk => {
- const node = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+ const node = try parseCPrefixOpExpr(c, it, source_loc, scope);
return try transCreateNodePtrDeref(c, node);
},
.Ampersand => {
const node = try transCreateNodeSimplePrefixOp(c, .AddressOf, .Ampersand, "&");
- node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
+ node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);
return &node.base;
},
else => {
- _ = it.prev();
- return try parseCSuffixOpExpr(c, it, source, source_loc, scope);
+ it.i -= 1;
+ return try parseCSuffixOpExpr(c, it, source_loc, scope);
},
}
}
diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig
@@ -67,6 +67,7 @@ pub const Type = extern union {
.array, .array_u8_sentinel_0 => return .Array,
.single_const_pointer => return .Pointer,
+ .single_mut_pointer => return .Pointer,
.single_const_pointer_to_comptime_int => return .Pointer,
.const_slice_u8 => return .Pointer,
}
@@ -261,6 +262,15 @@ pub const Type = extern union {
};
return Type{ .ptr_otherwise = &new_payload.base };
},
+ .single_mut_pointer => {
+ const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", self.ptr_otherwise);
+ const new_payload = try allocator.create(Payload.SingleMutPointer);
+ new_payload.* = .{
+ .base = payload.base,
+ .pointee_type = try payload.pointee_type.copy(allocator),
+ };
+ return Type{ .ptr_otherwise = &new_payload.base };
+ },
.int_signed => return self.copyPayloadShallow(allocator, Payload.IntSigned),
.int_unsigned => return self.copyPayloadShallow(allocator, Payload.IntUnsigned),
.function => {
@@ -368,6 +378,12 @@ pub const Type = extern union {
ty = payload.pointee_type;
continue;
},
+ .single_mut_pointer => {
+ const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", ty.ptr_otherwise);
+ try out_stream.writeAll("*");
+ ty = payload.pointee_type;
+ continue;
+ },
.int_signed => {
const payload = @fieldParentPtr(Payload.IntSigned, "base", ty.ptr_otherwise);
return out_stream.print("i{}", .{payload.bits});
@@ -467,6 +483,7 @@ pub const Type = extern union {
.array_u8_sentinel_0,
.array, // TODO check for zero bits
.single_const_pointer,
+ .single_mut_pointer,
.int_signed, // TODO check for zero bits
.int_unsigned, // TODO check for zero bits
=> true,
@@ -493,13 +510,18 @@ pub const Type = extern union {
.u8,
.i8,
.bool,
+ .array_u8_sentinel_0,
+ => return 1,
+
.fn_noreturn_no_args, // represents machine code; not a pointer
.fn_void_no_args, // represents machine code; not a pointer
.fn_naked_noreturn_no_args, // represents machine code; not a pointer
.fn_ccc_void_no_args, // represents machine code; not a pointer
.function, // represents machine code; not a pointer
- .array_u8_sentinel_0,
- => return 1,
+ => return switch (target.cpu.arch) {
+ .riscv64 => 2,
+ else => 1,
+ },
.i16, .u16 => return 2,
.i32, .u32 => return 4,
@@ -510,6 +532,7 @@ pub const Type = extern union {
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.single_const_pointer,
+ .single_mut_pointer,
=> return @divExact(target.cpu.arch.ptrBitWidth(), 8),
.c_short => return @divExact(CType.short.sizeInBits(target), 8),
@@ -591,6 +614,7 @@ pub const Type = extern union {
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.single_const_pointer,
+ .single_mut_pointer,
=> return @divExact(target.cpu.arch.ptrBitWidth(), 8),
.c_short => return @divExact(CType.short.sizeInBits(target), 8),
@@ -671,6 +695,7 @@ pub const Type = extern union {
=> false,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
=> true,
};
@@ -714,6 +739,7 @@ pub const Type = extern union {
.array,
.array_u8_sentinel_0,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.fn_noreturn_no_args,
.fn_void_no_args,
@@ -728,8 +754,7 @@ pub const Type = extern union {
};
}
- /// Asserts the type is a pointer type.
- pub fn pointerIsConst(self: Type) bool {
+ pub fn isConstPtr(self: Type) bool {
return switch (self.tag()) {
.u8,
.i8,
@@ -773,7 +798,8 @@ pub const Type = extern union {
.function,
.int_unsigned,
.int_signed,
- => unreachable,
+ .single_mut_pointer,
+ => false,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
@@ -782,6 +808,58 @@ pub const Type = extern union {
};
}
+ pub fn isVolatilePtr(self: Type) bool {
+ return switch (self.tag()) {
+ .u8,
+ .i8,
+ .u16,
+ .i16,
+ .u32,
+ .i32,
+ .u64,
+ .i64,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .c_longdouble,
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
+ .@"null",
+ .@"undefined",
+ .array,
+ .array_u8_sentinel_0,
+ .fn_noreturn_no_args,
+ .fn_void_no_args,
+ .fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .function,
+ .int_unsigned,
+ .int_signed,
+ .single_mut_pointer,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .const_slice_u8,
+ => false,
+ };
+ }
+
/// Asserts the type is a pointer or array type.
pub fn elemType(self: Type) Type {
return switch (self.tag()) {
@@ -829,6 +907,7 @@ pub const Type = extern union {
.array => self.cast(Payload.Array).?.elem_type,
.single_const_pointer => self.cast(Payload.SingleConstPointer).?.pointee_type,
+ .single_mut_pointer => self.cast(Payload.SingleMutPointer).?.pointee_type,
.array_u8_sentinel_0, .const_slice_u8 => Type.initTag(.u8),
.single_const_pointer_to_comptime_int => Type.initTag(.comptime_int),
};
@@ -876,6 +955,7 @@ pub const Type = extern union {
.fn_ccc_void_no_args,
.function,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.int_unsigned,
@@ -929,6 +1009,7 @@ pub const Type = extern union {
.fn_ccc_void_no_args,
.function,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.int_unsigned,
@@ -970,6 +1051,7 @@ pub const Type = extern union {
.function,
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1024,6 +1106,7 @@ pub const Type = extern union {
.function,
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1078,6 +1161,7 @@ pub const Type = extern union {
.function,
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1130,6 +1214,7 @@ pub const Type = extern union {
.function,
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1211,6 +1296,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1268,6 +1354,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1324,6 +1411,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1380,6 +1468,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1433,6 +1522,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1486,6 +1576,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1559,6 +1650,7 @@ pub const Type = extern union {
.function,
.array,
.single_const_pointer,
+ .single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@@ -1566,7 +1658,7 @@ pub const Type = extern union {
};
}
- pub fn onePossibleValue(self: Type) bool {
+ pub fn onePossibleValue(self: Type) ?Value {
var ty = self;
while (true) switch (ty.tag()) {
.f16,
@@ -1605,21 +1697,32 @@ pub const Type = extern union {
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
- => return false,
-
.c_void,
- .void,
- .noreturn,
- .@"null",
- .@"undefined",
- => return true,
+ => return null,
+
+ .void => return Value.initTag(.void_value),
+ .noreturn => return Value.initTag(.unreachable_value),
+ .@"null" => return Value.initTag(.null_value),
+ .@"undefined" => return Value.initTag(.undef),
- .int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0,
- .int_signed => return ty.cast(Payload.IntSigned).?.bits == 0,
+ .int_unsigned => {
+ if (ty.cast(Payload.IntUnsigned).?.bits == 0) {
+ return Value.initTag(.zero);
+ } else {
+ return null;
+ }
+ },
+ .int_signed => {
+ if (ty.cast(Payload.IntSigned).?.bits == 0) {
+ return Value.initTag(.zero);
+ } else {
+ return null;
+ }
+ },
.array => {
const array = ty.cast(Payload.Array).?;
if (array.len == 0)
- return true;
+ return Value.initTag(.empty_array);
ty = array.elem_type;
continue;
},
@@ -1628,6 +1731,11 @@ pub const Type = extern union {
ty = ptr.pointee_type;
continue;
},
+ .single_mut_pointer => {
+ const ptr = ty.cast(Payload.SingleMutPointer).?;
+ ty = ptr.pointee_type;
+ continue;
+ },
};
}
@@ -1678,6 +1786,7 @@ pub const Type = extern union {
.int_signed,
.array,
.single_const_pointer,
+ .single_mut_pointer,
=> return false,
};
}
@@ -1734,6 +1843,7 @@ pub const Type = extern union {
array_u8_sentinel_0,
array,
single_const_pointer,
+ single_mut_pointer,
int_signed,
int_unsigned,
function,
@@ -1764,6 +1874,12 @@ pub const Type = extern union {
pointee_type: Type,
};
+ pub const SingleMutPointer = struct {
+ base: Payload = Payload{ .tag = .single_mut_pointer },
+
+ pointee_type: Type,
+ };
+
pub const IntSigned = struct {
base: Payload = Payload{ .tag = .int_signed },
diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig
@@ -63,7 +63,9 @@ pub const Value = extern union {
undef,
zero,
- the_one_possible_value, // when the type only has one possible value
+ void_value,
+ unreachable_value,
+ empty_array,
null_value,
bool_true,
bool_false, // See last_no_payload_tag below.
@@ -164,7 +166,9 @@ pub const Value = extern union {
.const_slice_u8_type,
.undef,
.zero,
- .the_one_possible_value,
+ .void_value,
+ .unreachable_value,
+ .empty_array,
.null_value,
.bool_true,
.bool_false,
@@ -285,7 +289,8 @@ pub const Value = extern union {
.null_value => return out_stream.writeAll("null"),
.undef => return out_stream.writeAll("undefined"),
.zero => return out_stream.writeAll("0"),
- .the_one_possible_value => return out_stream.writeAll("(one possible value)"),
+ .void_value => return out_stream.writeAll("{}"),
+ .unreachable_value => return out_stream.writeAll("unreachable"),
.bool_true => return out_stream.writeAll("true"),
.bool_false => return out_stream.writeAll("false"),
.ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream),
@@ -312,6 +317,7 @@ pub const Value = extern union {
try out_stream.print("&[{}] ", .{elem_ptr.index});
val = elem_ptr.array_ptr;
},
+ .empty_array => return out_stream.writeAll(".{}"),
.bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),
.repeated => {
try out_stream.writeAll("(repeated) ");
@@ -388,7 +394,9 @@ pub const Value = extern union {
.undef,
.zero,
- .the_one_possible_value,
+ .void_value,
+ .unreachable_value,
+ .empty_array,
.bool_true,
.bool_false,
.null_value,
@@ -460,15 +468,18 @@ pub const Value = extern union {
.decl_ref,
.elem_ptr,
.bytes,
- .undef,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
+ .void_value,
+ .unreachable_value,
+ .empty_array,
=> unreachable,
- .the_one_possible_value, // An integer with one possible value is always zero.
+ .undef => unreachable,
+
.zero,
.bool_false,
=> return BigIntMutable.init(&space.limbs, 0).toConst(),
@@ -532,16 +543,19 @@ pub const Value = extern union {
.decl_ref,
.elem_ptr,
.bytes,
- .undef,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
+ .void_value,
+ .unreachable_value,
+ .empty_array,
=> unreachable,
+ .undef => unreachable,
+
.zero,
- .the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> return 0,
@@ -570,10 +584,9 @@ pub const Value = extern union {
.float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val),
.float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val),
- .zero, .the_one_possible_value => 0,
+ .zero => 0,
.int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int),
- // .int_i64 => @intToFloat(f128, self.cast(Payload.Int_i64).?.int),
- .int_i64 => @panic("TODO lld: error: undefined symbol: __floatditf"),
+ .int_i64 => @intToFloat(T, self.cast(Payload.Int_i64).?.int),
.int_big_positive, .int_big_negative => @panic("big int to f128"),
else => unreachable,
@@ -637,9 +650,11 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
+ .void_value,
+ .unreachable_value,
+ .empty_array,
=> unreachable,
- .the_one_possible_value, // an integer with one possible value is always zero
.zero,
.bool_false,
=> return 0,
@@ -714,11 +729,13 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
+ .void_value,
+ .unreachable_value,
+ .empty_array,
=> unreachable,
.zero,
.undef,
- .the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> return true,
@@ -797,13 +814,13 @@ pub const Value = extern union {
// return Value.initPayload(&res_payload.base).copy(allocator);
},
32 => {
- var res_payload = Value.Payload.Float_32{.val = self.toFloat(f32)};
+ var res_payload = Value.Payload.Float_32{ .val = self.toFloat(f32) };
if (!self.eql(Value.initPayload(&res_payload.base)))
return error.Overflow;
return Value.initPayload(&res_payload.base).copy(allocator);
},
64 => {
- var res_payload = Value.Payload.Float_64{.val = self.toFloat(f64)};
+ var res_payload = Value.Payload.Float_64{ .val = self.toFloat(f64) };
if (!self.eql(Value.initPayload(&res_payload.base)))
return error.Overflow;
return Value.initPayload(&res_payload.base).copy(allocator);
@@ -875,7 +892,9 @@ pub const Value = extern union {
.int_i64,
.int_big_positive,
.int_big_negative,
- .the_one_possible_value,
+ .empty_array,
+ .void_value,
+ .unreachable_value,
=> unreachable,
.zero => false,
@@ -939,10 +958,12 @@ pub const Value = extern union {
.bytes,
.repeated,
.undef,
+ .void_value,
+ .unreachable_value,
+ .empty_array,
=> unreachable,
.zero,
- .the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> .eq,
@@ -964,8 +985,8 @@ pub const Value = extern union {
pub fn order(lhs: Value, rhs: Value) std.math.Order {
const lhs_tag = lhs.tag();
const rhs_tag = rhs.tag();
- const lhs_is_zero = lhs_tag == .zero or lhs_tag == .the_one_possible_value;
- const rhs_is_zero = rhs_tag == .zero or rhs_tag == .the_one_possible_value;
+ const lhs_is_zero = lhs_tag == .zero;
+ const rhs_is_zero = rhs_tag == .zero;
if (lhs_is_zero) return rhs.orderAgainstZero().invert();
if (rhs_is_zero) return lhs.orderAgainstZero();
@@ -1071,9 +1092,11 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
+ .void_value,
+ .unreachable_value,
+ .empty_array,
=> unreachable,
- .the_one_possible_value => Value.initTag(.the_one_possible_value),
.ref_val => self.cast(Payload.RefVal).?.val,
.decl_ref => self.cast(Payload.DeclRef).?.decl.value(),
.elem_ptr => {
@@ -1130,7 +1153,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.zero,
- .the_one_possible_value,
.bool_true,
.bool_false,
.null_value,
@@ -1147,8 +1169,12 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
+ .void_value,
+ .unreachable_value,
=> unreachable,
+ .empty_array => unreachable, // out of bounds array index
+
.bytes => {
const int_payload = try allocator.create(Payload.Int_u64);
int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] };
@@ -1175,8 +1201,7 @@ pub const Value = extern union {
return self.tag() == .undef;
}
- /// Valid for all types. Asserts the value is not undefined.
- /// `.the_one_possible_value` is reported as not null.
+ /// Valid for all types. Asserts the value is not undefined and not unreachable.
pub fn isNull(self: Value) bool {
return switch (self.tag()) {
.ty,
@@ -1221,7 +1246,7 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.zero,
- .the_one_possible_value,
+ .empty_array,
.bool_true,
.bool_false,
.function,
@@ -1238,9 +1263,11 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
+ .void_value,
=> false,
.undef => unreachable,
+ .unreachable_value => unreachable,
.null_value => true,
};
}
diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig
@@ -34,25 +34,63 @@ pub const Inst = struct {
/// These names are used directly as the instruction names in the text format.
pub const Tag = enum {
+ /// Arithmetic addition, asserts no integer overflow.
+ add,
+ /// Twos complement wrapping integer addition.
+ addwrap,
/// Allocates stack local memory. Its lifetime ends when the block ends that contains
- /// this instruction.
+ /// this instruction. The operand is the type of the allocated object.
alloc,
/// Same as `alloc` except the type is inferred.
alloc_inferred,
+ /// Array concatenation. `a ++ b`
+ array_cat,
+ /// Array multiplication `a ** b`
+ array_mul,
/// Function parameter value. These must be first in a function's main block,
/// in respective order with the parameters.
arg,
+ /// Type coercion.
+ as,
+ /// Inline assembly.
+ @"asm",
+ /// Bitwise AND. `&`
+ bitand,
+ /// TODO delete this instruction, it has no purpose.
+ bitcast,
+ /// An arbitrary typed pointer, which is to be used as an L-Value, is pointer-casted
+ /// to a new L-Value. The destination type is given by LHS. The cast is to be evaluated
+ /// as if it were a bit-cast operation from the operand pointer element type to the
+ /// provided destination type.
+ bitcast_lvalue,
/// A typed result location pointer is bitcasted to a new result location pointer.
/// The new result location pointer has an inferred type.
bitcast_result_ptr,
+ /// Bitwise OR. `|`
+ bitor,
/// A labeled block of code, which can return a value.
block,
+ /// Boolean NOT. See also `bitnot`.
+ boolnot,
/// Return a value from a `Block`.
@"break",
breakpoint,
/// Same as `break` but without an operand; the operand is assumed to be the void value.
breakvoid,
+ /// Function call.
call,
+ /// `<`
+ cmp_lt,
+ /// `<=`
+ cmp_lte,
+ /// `==`
+ cmp_eq,
+ /// `>=`
+ cmp_gte,
+ /// `>`
+ cmp_gt,
+ /// `!=`
+ cmp_neq,
/// Coerces a result location pointer to a new element type. It is evaluated "backwards"-
/// as type coercion from the new element type to the old element type.
/// LHS is destination element type, RHS is result pointer.
@@ -65,8 +103,12 @@ pub const Inst = struct {
coerce_to_ptr_elem,
/// Emit an error message and fail compilation.
compileerror,
+ /// Conditional branch. Splits control flow based on a boolean condition value.
+ condbr,
/// Special case, has no textual representation.
@"const",
+ /// Declares the beginning of a statement. Used for debug info.
+ dbg_stmt,
/// Represents a pointer to a global decl by name.
declref,
/// Represents a pointer to a global decl by string name.
@@ -76,61 +118,108 @@ pub const Inst = struct {
declval,
/// Same as declval but the parameter is a `*Module.Decl` rather than a name.
declval_in_module,
+ /// Load the value from a pointer.
+ deref,
+ /// Arithmetic division. Asserts no integer overflow.
+ div,
+ /// Given a pointer to an array, slice, or pointer, returns a pointer to the element at
+ /// the provided index.
+ elemptr,
/// Emits a compile error if the operand is not `void`.
ensure_result_used,
/// Emits a compile error if an error is ignored.
ensure_result_non_error,
- boolnot,
- /// Obtains a pointer to the return value.
- ret_ptr,
- /// Obtains the return type of the in-scope function.
- ret_type,
- /// Write a value to a pointer.
- store,
- /// String Literal. Makes an anonymous Decl and then takes a pointer to it.
- str,
- int,
- inttype,
- ptrtoint,
+ /// Export the provided Decl as the provided name in the compilation's output object file.
+ @"export",
+ /// Given a pointer to a struct or object that contains virtual fields, returns a pointer
+ /// to the named field.
fieldptr,
- deref,
- as,
- @"asm",
- @"unreachable",
- @"return",
- returnvoid,
+ /// Convert a larger float type to any other float type, possibly causing a loss of precision.
+ floatcast,
+ /// Declare a function body.
@"fn",
+ /// Returns a function type.
fntype,
- @"export",
+ /// Integer literal.
+ int,
+ /// Convert an integer value to another integer type, asserting that the destination type
+ /// can hold the same mathematical value.
+ intcast,
+ /// Make an integer type out of signedness and bit count.
+ inttype,
+ /// Return a boolean false if an optional is null. `x != null`
+ isnonnull,
+ /// Return a boolean true if an optional is null. `x == null`
+ isnull,
+ /// Ambiguously remainder division or modulus. If the computation would possibly have
+ /// a different value depending on whether the operation is remainder division or modulus,
+ /// a compile error is emitted. Otherwise the computation is performed.
+ mod_rem,
+ /// Arithmetic multiplication. Asserts no integer overflow.
+ mul,
+ /// Twos complement wrapping integer multiplication.
+ mulwrap,
/// Given a reference to a function and a parameter index, returns the
/// type of the parameter. TODO what happens when the parameter is `anytype`?
param_type,
+ /// An alternative to using `const` for simple primitive values such as `true` or `u8`.
+ /// TODO flatten so that each primitive has its own ZIR Inst Tag.
primitive,
- intcast,
- bitcast,
- floatcast,
- elemptr,
- add,
+ /// Convert a pointer to a `usize` integer.
+ ptrtoint,
+ /// Turns an R-Value into a const L-Value. In other words, it takes a value,
+ /// stores it in a memory location, and returns a const pointer to it. If the value
+ /// is `comptime`, the memory location is global static constant data. Otherwise,
+ /// the memory location is in the stack frame, local to the scope containing the
+ /// instruction.
+ ref,
+ /// Obtains a pointer to the return value.
+ ret_ptr,
+ /// Obtains the return type of the in-scope function.
+ ret_type,
+ /// Sends control flow back to the function's callee. Takes an operand as the return value.
+ @"return",
+ /// Same as `return` but there is no operand; the operand is implicitly the void value.
+ returnvoid,
+ /// Integer shift-left. Zeroes are shifted in from the right hand side.
+ shl,
+ /// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type.
+ shr,
+ /// Create a const pointer type based on the element type. `*const T`
+ single_const_ptr_type,
+ /// Create a mutable pointer type based on the element type. `*T`
+ single_mut_ptr_type,
+ /// Write a value to a pointer. For loading, see `deref`.
+ store,
+ /// String Literal. Makes an anonymous Decl and then takes a pointer to it.
+ str,
+ /// Arithmetic subtraction. Asserts no integer overflow.
sub,
- cmp_lt,
- cmp_lte,
- cmp_eq,
- cmp_gte,
- cmp_gt,
- cmp_neq,
- condbr,
- isnull,
- isnonnull,
+ /// Twos complement wrapping integer subtraction.
+ subwrap,
+ /// Returns the type of a value.
+ typeof,
+ /// Asserts control-flow will not reach this instruction. Not safety checked - the compiler
+ /// will assume the correctness of this instruction.
+ unreach_nocheck,
+ /// Asserts control-flow will not reach this instruction. In safety-checked modes,
+ /// this will generate a call to the panic function unless it can be proven unreachable
+ /// by the compiler.
+ @"unreachable",
+ /// Bitwise XOR. `^`
+ xor,
pub fn Type(tag: Tag) type {
return switch (tag) {
.arg,
.breakpoint,
- .@"unreachable",
+ .dbg_stmt,
.returnvoid,
.alloc_inferred,
.ret_ptr,
.ret_type,
+ .unreach_nocheck,
+ .@"unreachable",
=> NoOp,
.boolnot,
@@ -143,10 +232,28 @@ pub const Inst = struct {
.ensure_result_used,
.ensure_result_non_error,
.bitcast_result_ptr,
+ .ref,
+ .bitcast_lvalue,
+ .typeof,
+ .single_const_ptr_type,
+ .single_mut_ptr_type,
=> UnOp,
.add,
+ .addwrap,
+ .array_cat,
+ .array_mul,
+ .bitand,
+ .bitor,
+ .div,
+ .mod_rem,
+ .mul,
+ .mulwrap,
+ .shl,
+ .shr,
+ .store,
.sub,
+ .subwrap,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@@ -158,6 +265,7 @@ pub const Inst = struct {
.intcast,
.bitcast,
.coerce_result_ptr,
+ .xor,
=> BinOp,
.block => Block,
@@ -172,7 +280,6 @@ pub const Inst = struct {
.coerce_result_block_ptr => CoerceResultBlockPtr,
.compileerror => CompileError,
.@"const" => Const,
- .store => Store,
.str => Str,
.int => Int,
.inttype => IntType,
@@ -192,63 +299,83 @@ pub const Inst = struct {
/// Function calls do not count.
pub fn isNoReturn(tag: Tag) bool {
return switch (tag) {
+ .add,
+ .addwrap,
.alloc,
.alloc_inferred,
+ .array_cat,
+ .array_mul,
.arg,
+ .as,
+ .@"asm",
+ .bitand,
+ .bitcast,
+ .bitcast_lvalue,
.bitcast_result_ptr,
+ .bitor,
.block,
+ .boolnot,
.breakpoint,
.call,
+ .cmp_lt,
+ .cmp_lte,
+ .cmp_eq,
+ .cmp_gte,
+ .cmp_gt,
+ .cmp_neq,
.coerce_result_ptr,
.coerce_result_block_ptr,
.coerce_to_ptr_elem,
.@"const",
+ .dbg_stmt,
.declref,
.declref_str,
.declval,
.declval_in_module,
+ .deref,
+ .div,
+ .elemptr,
.ensure_result_used,
.ensure_result_non_error,
- .ret_ptr,
- .ret_type,
- .store,
- .str,
- .int,
- .inttype,
- .ptrtoint,
+ .@"export",
+ .floatcast,
.fieldptr,
- .deref,
- .as,
- .@"asm",
.@"fn",
.fntype,
- .@"export",
+ .int,
+ .intcast,
+ .inttype,
+ .isnonnull,
+ .isnull,
+ .mod_rem,
+ .mul,
+ .mulwrap,
.param_type,
.primitive,
- .intcast,
- .bitcast,
- .floatcast,
- .elemptr,
- .add,
+ .ptrtoint,
+ .ref,
+ .ret_ptr,
+ .ret_type,
+ .shl,
+ .shr,
+ .single_const_ptr_type,
+ .single_mut_ptr_type,
+ .store,
+ .str,
.sub,
- .cmp_lt,
- .cmp_lte,
- .cmp_eq,
- .cmp_gte,
- .cmp_gt,
- .cmp_neq,
- .isnull,
- .isnonnull,
- .boolnot,
+ .subwrap,
+ .typeof,
+ .xor,
=> false,
- .condbr,
- .@"unreachable",
- .@"return",
- .returnvoid,
.@"break",
.breakvoid,
+ .condbr,
.compileerror,
+ .@"return",
+ .returnvoid,
+ .unreach_nocheck,
+ .@"unreachable",
=> true,
};
}
@@ -430,17 +557,6 @@ pub const Inst = struct {
kw_args: struct {},
};
- pub const Store = struct {
- pub const base_tag = Tag.store;
- base: Inst,
-
- positionals: struct {
- ptr: *Inst,
- value: *Inst,
- },
- kw_args: struct {},
- };
-
pub const Str = struct {
pub const base_tag = Tag.str;
base: Inst,
@@ -630,7 +746,7 @@ pub const Inst = struct {
.@"false" => .{ .ty = Type.initTag(.bool), .val = Value.initTag(.bool_false) },
.@"null" => .{ .ty = Type.initTag(.@"null"), .val = Value.initTag(.null_value) },
.@"undefined" => .{ .ty = Type.initTag(.@"undefined"), .val = Value.initTag(.undef) },
- .void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.the_one_possible_value) },
+ .void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.void_value) },
};
}
};
@@ -1486,6 +1602,21 @@ const EmitZIR = struct {
const decl = decl_ref.decl;
return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl));
}
+ if (typed_value.val.isUndef()) {
+ const as_inst = try self.arena.allocator.create(Inst.BinOp);
+ as_inst.* = .{
+ .base = .{
+ .tag = .as,
+ .src = src,
+ },
+ .positionals = .{
+ .lhs = (try self.emitType(src, typed_value.ty)).inst,
+ .rhs = (try self.emitPrimitive(src, .@"undefined")).inst,
+ },
+ .kw_args = .{},
+ };
+ return self.emitUnnamedDecl(&as_inst.base);
+ }
switch (typed_value.ty.zigTypeTag()) {
.Pointer => {
const ptr_elem_type = typed_value.ty.elemType();
@@ -1716,15 +1847,19 @@ const EmitZIR = struct {
.breakpoint => try self.emitNoOp(inst.src, .breakpoint),
.unreach => try self.emitNoOp(inst.src, .@"unreachable"),
.retvoid => try self.emitNoOp(inst.src, .returnvoid),
+ .dbg_stmt => try self.emitNoOp(inst.src, .dbg_stmt),
.not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot),
.ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"),
.ptrtoint => try self.emitUnOp(inst.src, new_body, inst.castTag(.ptrtoint).?, .ptrtoint),
.isnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnull).?, .isnull),
.isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull),
+ .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref),
+ .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref),
.add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add),
.sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub),
+ .store => try self.emitBinOp(inst.src, new_body, inst.castTag(.store).?, .store),
.cmp_lt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lt).?, .cmp_lt),
.cmp_lte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lte).?, .cmp_lte),
.cmp_eq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_eq).?, .cmp_eq),
@@ -1736,6 +1871,21 @@ const EmitZIR = struct {
.intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast),
.floatcast => try self.emitCast(inst.src, new_body, inst.castTag(.floatcast).?, .floatcast),
+ .alloc => blk: {
+ const new_inst = try self.arena.allocator.create(Inst.UnOp);
+ new_inst.* = .{
+ .base = .{
+ .src = inst.src,
+ .tag = .alloc,
+ },
+ .positionals = .{
+ .operand = (try self.emitType(inst.src, inst.ty)).inst,
+ },
+ .kw_args = .{},
+ };
+ break :blk &new_inst.base;
+ },
+
.block => blk: {
const old_inst = inst.castTag(.block).?;
const new_inst = try self.arena.allocator.create(Inst.Block);
@@ -1973,6 +2123,25 @@ const EmitZIR = struct {
};
return self.emitUnnamedDecl(&inttype_inst.base);
},
+ .Pointer => {
+ if (ty.isSinglePointer()) {
+ const inst = try self.arena.allocator.create(Inst.UnOp);
+ const tag: Inst.Tag = if (ty.isConstPtr()) .single_const_ptr_type else .single_mut_ptr_type;
+ inst.* = .{
+ .base = .{
+ .src = src,
+ .tag = tag,
+ },
+ .positionals = .{
+ .operand = (try self.emitType(src, ty.elemType())).inst,
+ },
+ .kw_args = .{},
+ };
+ return self.emitUnnamedDecl(&inst.base);
+ } else {
+ std.debug.panic("TODO implement emitType for {}", .{ty});
+ }
+ },
else => std.debug.panic("TODO implement emitType for {}", .{ty}),
},
}
diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig
@@ -0,0 +1,1160 @@
+//! Semantic analysis of ZIR instructions.
+//! This file operates on a `Module` instance, transforming untyped ZIR
+//! instructions into semantically-analyzed IR instructions. It does type
+//! checking, comptime control flow, and safety-check generation. This is the
+//! the heart of the Zig compiler.
+//! When deciding if something goes into this file or into Module, here is a
+//! guiding principle: if it has to do with (untyped) ZIR instructions, it goes
+//! here. If the analysis operates on typed IR instructions, it goes in Module.
+
+const std = @import("std");
+const mem = std.mem;
+const Allocator = std.mem.Allocator;
+const Value = @import("value.zig").Value;
+const Type = @import("type.zig").Type;
+const TypedValue = @import("TypedValue.zig");
+const assert = std.debug.assert;
+const ir = @import("ir.zig");
+const zir = @import("zir.zig");
+const Module = @import("Module.zig");
+const Inst = ir.Inst;
+const Body = ir.Body;
+const trace = @import("tracy.zig").trace;
+const Scope = Module.Scope;
+const InnerError = Module.InnerError;
+const Decl = Module.Decl;
+
+pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst {
+ switch (old_inst.tag) {
+ .alloc => return analyzeInstAlloc(mod, scope, old_inst.castTag(.alloc).?),
+ .alloc_inferred => return analyzeInstAllocInferred(mod, scope, old_inst.castTag(.alloc_inferred).?),
+ .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?),
+ .bitcast_lvalue => return analyzeInstBitCastLValue(mod, scope, old_inst.castTag(.bitcast_lvalue).?),
+ .bitcast_result_ptr => return analyzeInstBitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?),
+ .block => return analyzeInstBlock(mod, scope, old_inst.castTag(.block).?),
+ .@"break" => return analyzeInstBreak(mod, scope, old_inst.castTag(.@"break").?),
+ .breakpoint => return analyzeInstBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?),
+ .breakvoid => return analyzeInstBreakVoid(mod, scope, old_inst.castTag(.breakvoid).?),
+ .call => return analyzeInstCall(mod, scope, old_inst.castTag(.call).?),
+ .coerce_result_block_ptr => return analyzeInstCoerceResultBlockPtr(mod, scope, old_inst.castTag(.coerce_result_block_ptr).?),
+ .coerce_result_ptr => return analyzeInstCoerceResultPtr(mod, scope, old_inst.castTag(.coerce_result_ptr).?),
+ .coerce_to_ptr_elem => return analyzeInstCoerceToPtrElem(mod, scope, old_inst.castTag(.coerce_to_ptr_elem).?),
+ .compileerror => return analyzeInstCompileError(mod, scope, old_inst.castTag(.compileerror).?),
+ .@"const" => return analyzeInstConst(mod, scope, old_inst.castTag(.@"const").?),
+ .dbg_stmt => return analyzeInstDbgStmt(mod, scope, old_inst.castTag(.dbg_stmt).?),
+ .declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?),
+ .declref_str => return analyzeInstDeclRefStr(mod, scope, old_inst.castTag(.declref_str).?),
+ .declval => return analyzeInstDeclVal(mod, scope, old_inst.castTag(.declval).?),
+ .declval_in_module => return analyzeInstDeclValInModule(mod, scope, old_inst.castTag(.declval_in_module).?),
+ .ensure_result_used => return analyzeInstEnsureResultUsed(mod, scope, old_inst.castTag(.ensure_result_used).?),
+ .ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?),
+ .ref => return analyzeInstRef(mod, scope, old_inst.castTag(.ref).?),
+ .ret_ptr => return analyzeInstRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?),
+ .ret_type => return analyzeInstRetType(mod, scope, old_inst.castTag(.ret_type).?),
+ .single_const_ptr_type => return analyzeInstSingleConstPtrType(mod, scope, old_inst.castTag(.single_const_ptr_type).?),
+ .single_mut_ptr_type => return analyzeInstSingleMutPtrType(mod, scope, old_inst.castTag(.single_mut_ptr_type).?),
+ .store => return analyzeInstStore(mod, scope, old_inst.castTag(.store).?),
+ .str => return analyzeInstStr(mod, scope, old_inst.castTag(.str).?),
+ .int => {
+ const big_int = old_inst.castTag(.int).?.positionals.int;
+ return mod.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int);
+ },
+ .inttype => return analyzeInstIntType(mod, scope, old_inst.castTag(.inttype).?),
+ .param_type => return analyzeInstParamType(mod, scope, old_inst.castTag(.param_type).?),
+ .ptrtoint => return analyzeInstPtrToInt(mod, scope, old_inst.castTag(.ptrtoint).?),
+ .fieldptr => return analyzeInstFieldPtr(mod, scope, old_inst.castTag(.fieldptr).?),
+ .deref => return analyzeInstDeref(mod, scope, old_inst.castTag(.deref).?),
+ .as => return analyzeInstAs(mod, scope, old_inst.castTag(.as).?),
+ .@"asm" => return analyzeInstAsm(mod, scope, old_inst.castTag(.@"asm").?),
+ .@"unreachable" => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.@"unreachable").?),
+ .unreach_nocheck => return analyzeInstUnreachNoChk(mod, scope, old_inst.castTag(.unreach_nocheck).?),
+ .@"return" => return analyzeInstRet(mod, scope, old_inst.castTag(.@"return").?),
+ .returnvoid => return analyzeInstRetVoid(mod, scope, old_inst.castTag(.returnvoid).?),
+ .@"fn" => return analyzeInstFn(mod, scope, old_inst.castTag(.@"fn").?),
+ .@"export" => return analyzeInstExport(mod, scope, old_inst.castTag(.@"export").?),
+ .primitive => return analyzeInstPrimitive(mod, scope, old_inst.castTag(.primitive).?),
+ .fntype => return analyzeInstFnType(mod, scope, old_inst.castTag(.fntype).?),
+ .intcast => return analyzeInstIntCast(mod, scope, old_inst.castTag(.intcast).?),
+ .bitcast => return analyzeInstBitCast(mod, scope, old_inst.castTag(.bitcast).?),
+ .floatcast => return analyzeInstFloatCast(mod, scope, old_inst.castTag(.floatcast).?),
+ .elemptr => return analyzeInstElemPtr(mod, scope, old_inst.castTag(.elemptr).?),
+ .add => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.add).?),
+ .addwrap => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.addwrap).?),
+ .sub => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.sub).?),
+ .subwrap => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.subwrap).?),
+ .mul => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.mul).?),
+ .mulwrap => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.mulwrap).?),
+ .div => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.div).?),
+ .mod_rem => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.mod_rem).?),
+ .array_cat => return analyzeInstArrayCat(mod, scope, old_inst.castTag(.array_cat).?),
+ .array_mul => return analyzeInstArrayMul(mod, scope, old_inst.castTag(.array_mul).?),
+ .bitand => return analyzeInstBitwise(mod, scope, old_inst.castTag(.bitand).?),
+ .bitor => return analyzeInstBitwise(mod, scope, old_inst.castTag(.bitor).?),
+ .xor => return analyzeInstBitwise(mod, scope, old_inst.castTag(.xor).?),
+ .shl => return analyzeInstShl(mod, scope, old_inst.castTag(.shl).?),
+ .shr => return analyzeInstShr(mod, scope, old_inst.castTag(.shr).?),
+ .cmp_lt => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_lt).?, .lt),
+ .cmp_lte => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_lte).?, .lte),
+ .cmp_eq => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_eq).?, .eq),
+ .cmp_gte => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_gte).?, .gte),
+ .cmp_gt => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_gt).?, .gt),
+ .cmp_neq => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_neq).?, .neq),
+ .condbr => return analyzeInstCondBr(mod, scope, old_inst.castTag(.condbr).?),
+ .isnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnull).?, true),
+ .isnonnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnonnull).?, false),
+ .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?),
+ .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?),
+ }
+}
+
+pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void {
+ for (body.instructions) |src_inst| {
+ src_inst.analyzed_inst = try analyzeInst(mod, scope, src_inst);
+ }
+}
+
+pub fn analyzeBodyValueAsType(mod: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type {
+ try analyzeBody(mod, &block_scope.base, body);
+ for (block_scope.instructions.items) |inst| {
+ if (inst.castTag(.ret)) |ret| {
+ const val = try mod.resolveConstValue(&block_scope.base, ret.operand);
+ return val.toType();
+ } else {
+ return mod.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});
+ }
+ }
+ unreachable;
+}
+
+pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool {
+ var decl_scope: Scope.DeclAnalysis = .{
+ .decl = decl,
+ .arena = std.heap.ArenaAllocator.init(mod.gpa),
+ };
+ errdefer decl_scope.arena.deinit();
+
+ decl.analysis = .in_progress;
+
+ const typed_value = try analyzeConstInst(mod, &decl_scope.base, src_decl.inst);
+ const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State);
+
+ var prev_type_has_bits = false;
+ var type_changed = true;
+
+ if (decl.typedValueManaged()) |tvm| {
+ prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits();
+ type_changed = !tvm.typed_value.ty.eql(typed_value.ty);
+
+ tvm.deinit(mod.gpa);
+ }
+
+ arena_state.* = decl_scope.arena.state;
+ decl.typed_value = .{
+ .most_recent = .{
+ .typed_value = typed_value,
+ .arena = arena_state,
+ },
+ };
+ decl.analysis = .complete;
+ decl.generation = mod.generation;
+ if (typed_value.ty.hasCodeGenBits()) {
+ // We don't fully codegen the decl until later, but we do need to reserve a global
+ // offset table index for it. This allows us to codegen decls out of dependency order,
+ // increasing how many computations can be done in parallel.
+ try mod.bin_file.allocateDeclIndexes(decl);
+ try mod.work_queue.writeItem(.{ .codegen_decl = decl });
+ } else if (prev_type_has_bits) {
+ mod.bin_file.freeDecl(decl);
+ }
+
+ return type_changed;
+}
+
+pub fn resolveZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl {
+ const zir_module = mod.root_scope.cast(Scope.ZIRModule).?;
+ const entry = zir_module.contents.module.findDecl(src_decl.name).?;
+ return resolveZirDeclHavingIndex(mod, scope, src_decl, entry.index);
+}
+
+fn resolveZirDeclHavingIndex(mod: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl {
+ const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name);
+ const decl = mod.decl_table.get(name_hash).?;
+ decl.src_index = src_index;
+ try mod.ensureDeclAnalyzed(decl);
+ return decl;
+}
+
+/// Declares a dependency on the decl.
+fn resolveCompleteZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl {
+ const decl = try resolveZirDecl(mod, scope, src_decl);
+ switch (decl.analysis) {
+ .unreferenced => unreachable,
+ .in_progress => unreachable,
+ .outdated => unreachable,
+
+ .dependency_failure,
+ .sema_failure,
+ .sema_failure_retryable,
+ .codegen_failure,
+ .codegen_failure_retryable,
+ => return error.AnalysisFail,
+
+ .complete => {},
+ }
+ return decl;
+}
+
+/// TODO Look into removing this function. The body is only needed for .zir files, not .zig files.
+pub fn resolveInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst {
+ if (old_inst.analyzed_inst) |inst| return inst;
+
+ // If this assert trips, the instruction that was referenced did not get properly
+ // analyzed before it was referenced.
+ const zir_module = scope.namespace().cast(Scope.ZIRModule).?;
+ const entry = if (old_inst.cast(zir.Inst.DeclVal)) |declval| blk: {
+ const decl_name = declval.positionals.name;
+ const entry = zir_module.contents.module.findDecl(decl_name) orelse
+ return mod.fail(scope, old_inst.src, "decl '{}' not found", .{decl_name});
+ break :blk entry;
+ } else blk: {
+ // If this assert trips, the instruction that was referenced did not get
+ // properly analyzed by a previous instruction analysis before it was
+ // referenced by the current one.
+ break :blk zir_module.contents.module.findInstDecl(old_inst).?;
+ };
+ const decl = try resolveCompleteZirDecl(mod, scope, entry.decl);
+ const decl_ref = try mod.analyzeDeclRef(scope, old_inst.src, decl);
+ // Note: it would be tempting here to store the result into old_inst.analyzed_inst field,
+ // but this would prevent the analyzeDeclRef from happening, which is needed to properly
+ // detect Decl dependencies and dependency failures on updates.
+ return mod.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src);
+}
+
+fn resolveConstString(mod: *Module, scope: *Scope, old_inst: *zir.Inst) ![]u8 {
+ const new_inst = try resolveInst(mod, scope, old_inst);
+ const wanted_type = Type.initTag(.const_slice_u8);
+ const coerced_inst = try mod.coerce(scope, wanted_type, new_inst);
+ const val = try mod.resolveConstValue(scope, coerced_inst);
+ return val.toAllocatedBytes(scope.arena());
+}
+
+fn resolveType(mod: *Module, scope: *Scope, old_inst: *zir.Inst) !Type {
+ const new_inst = try resolveInst(mod, scope, old_inst);
+ const wanted_type = Type.initTag(.@"type");
+ const coerced_inst = try mod.coerce(scope, wanted_type, new_inst);
+ const val = try mod.resolveConstValue(scope, coerced_inst);
+ return val.toType();
+}
+
+pub fn resolveInstConst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue {
+ const new_inst = try resolveInst(mod, scope, old_inst);
+ const val = try mod.resolveConstValue(scope, new_inst);
+ return TypedValue{
+ .ty = new_inst.ty,
+ .val = val,
+ };
+}
+
+fn analyzeInstConst(mod: *Module, scope: *Scope, const_inst: *zir.Inst.Const) InnerError!*Inst {
+ // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions
+ // after analysis.
+ const typed_value_copy = try const_inst.positionals.typed_value.copy(scope.arena());
+ return mod.constInst(scope, const_inst.base.src, typed_value_copy);
+}
+
+fn analyzeConstInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue {
+ const new_inst = try analyzeInst(mod, scope, old_inst);
+ return TypedValue{
+ .ty = new_inst.ty,
+ .val = try mod.resolveConstValue(scope, new_inst),
+ };
+}
+
+fn analyzeInstCoerceResultBlockPtr(
+ mod: *Module,
+ scope: *Scope,
+ inst: *zir.Inst.CoerceResultBlockPtr,
+) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultBlockPtr", .{});
+}
+
+fn analyzeInstBitCastLValue(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastLValue", .{});
+}
+
+fn analyzeInstBitCastResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastResultPtr", .{});
+}
+
+fn analyzeInstCoerceResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultPtr", .{});
+}
+
+/// Equivalent to `as(ptr_child_type(typeof(ptr)), value)`.
+fn analyzeInstCoerceToPtrElem(mod: *Module, scope: *Scope, inst: *zir.Inst.CoerceToPtrElem) InnerError!*Inst {
+ const ptr = try resolveInst(mod, scope, inst.positionals.ptr);
+ const operand = try resolveInst(mod, scope, inst.positionals.value);
+ return mod.coerce(scope, ptr.ty.elemType(), operand);
+}
+
+fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstRetPtr", .{});
+}
+
+fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const operand = try resolveInst(mod, scope, inst.positionals.operand);
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ const ptr_type = try mod.singleConstPtrType(scope, inst.base.src, operand.ty);
+ return mod.addUnOp(b, inst.base.src, ptr_type, .ref, operand);
+}
+
+fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
+ const ret_type = fn_ty.fnReturnType();
+ return mod.constType(scope, inst.base.src, ret_type);
+}
+
+fn analyzeInstEnsureResultUsed(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const operand = try resolveInst(mod, scope, inst.positionals.operand);
+ switch (operand.ty.zigTypeTag()) {
+ .Void, .NoReturn => return mod.constVoid(scope, operand.src),
+ else => return mod.fail(scope, operand.src, "expression value is ignored", .{}),
+ }
+}
+
+fn analyzeInstEnsureResultNonError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const operand = try resolveInst(mod, scope, inst.positionals.operand);
+ switch (operand.ty.zigTypeTag()) {
+ .ErrorSet, .ErrorUnion => return mod.fail(scope, operand.src, "error is discarded", .{}),
+ else => return mod.constVoid(scope, operand.src),
+ }
+}
+
+fn analyzeInstAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const var_type = try resolveType(mod, scope, inst.positionals.operand);
+ const ptr_type = try mod.singleMutPtrType(scope, inst.base.src, var_type);
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ return mod.addNoOp(b, inst.base.src, ptr_type, .alloc);
+}
+
+fn analyzeInstAllocInferred(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{});
+}
+
+fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ const ptr = try resolveInst(mod, scope, inst.positionals.lhs);
+ const value = try resolveInst(mod, scope, inst.positionals.rhs);
+ return mod.storePtr(scope, inst.base.src, ptr, value);
+}
+
+fn analyzeInstParamType(mod: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerError!*Inst {
+ const fn_inst = try resolveInst(mod, scope, inst.positionals.func);
+ const arg_index = inst.positionals.arg_index;
+
+ const fn_ty: Type = switch (fn_inst.ty.zigTypeTag()) {
+ .Fn => fn_inst.ty,
+ .BoundFn => {
+ return mod.fail(scope, fn_inst.src, "TODO implement analyzeInstParamType for method call syntax", .{});
+ },
+ else => {
+ return mod.fail(scope, fn_inst.src, "expected function, found '{}'", .{fn_inst.ty});
+ },
+ };
+
+ // TODO support C-style var args
+ const param_count = fn_ty.fnParamLen();
+ if (arg_index >= param_count) {
+ return mod.fail(scope, inst.base.src, "arg index {} out of bounds; '{}' has {} arguments", .{
+ arg_index,
+ fn_ty,
+ param_count,
+ });
+ }
+
+ // TODO support generic functions
+ const param_type = fn_ty.fnParamType(arg_index);
+ return mod.constType(scope, inst.base.src, param_type);
+}
+
+fn analyzeInstStr(mod: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst {
+ // The bytes references memory inside the ZIR module, which can get deallocated
+ // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena.
+ var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa);
+ const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes);
+
+ const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0);
+ ty_payload.* = .{ .len = arena_bytes.len };
+
+ const bytes_payload = try scope.arena().create(Value.Payload.Bytes);
+ bytes_payload.* = .{ .data = arena_bytes };
+
+ const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{
+ .ty = Type.initPayload(&ty_payload.base),
+ .val = Value.initPayload(&bytes_payload.base),
+ });
+ return mod.analyzeDeclRef(scope, str_inst.base.src, new_decl);
+}
+
+fn analyzeInstExport(mod: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst {
+ const symbol_name = try resolveConstString(mod, scope, export_inst.positionals.symbol_name);
+ const exported_decl = mod.lookupDeclName(scope, export_inst.positionals.decl_name) orelse
+ return mod.fail(scope, export_inst.base.src, "decl '{}' not found", .{export_inst.positionals.decl_name});
+ try mod.analyzeExport(scope, export_inst.base.src, symbol_name, exported_decl);
+ return mod.constVoid(scope, export_inst.base.src);
+}
+
+fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileError) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "{}", .{inst.positionals.msg});
+}
+
+fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty;
+ const param_index = b.instructions.items.len;
+ const param_count = fn_ty.fnParamLen();
+ if (param_index >= param_count) {
+ return mod.fail(scope, inst.base.src, "parameter index {} outside list of length {}", .{
+ param_index,
+ param_count,
+ });
+ }
+ const param_type = fn_ty.fnParamType(param_index);
+ return mod.addNoOp(b, inst.base.src, param_type, .arg);
+}
+
+fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst {
+ const parent_block = scope.cast(Scope.Block).?;
+
+ // Reserve space for a Block instruction so that generated Break instructions can
+ // point to it, even if it doesn't end up getting used because the code ends up being
+ // comptime evaluated.
+ const block_inst = try parent_block.arena.create(Inst.Block);
+ block_inst.* = .{
+ .base = .{
+ .tag = Inst.Block.base_tag,
+ .ty = undefined, // Set after analysis.
+ .src = inst.base.src,
+ },
+ .body = undefined,
+ };
+
+ var child_block: Scope.Block = .{
+ .parent = parent_block,
+ .func = parent_block.func,
+ .decl = parent_block.decl,
+ .instructions = .{},
+ .arena = parent_block.arena,
+ // TODO @as here is working around a miscompilation compiler bug :(
+ .label = @as(?Scope.Block.Label, Scope.Block.Label{
+ .zir_block = inst,
+ .results = .{},
+ .block_inst = block_inst,
+ }),
+ };
+ const label = &child_block.label.?;
+
+ defer child_block.instructions.deinit(mod.gpa);
+ defer label.results.deinit(mod.gpa);
+
+ try analyzeBody(mod, &child_block.base, inst.positionals.body);
+
+ // Blocks must terminate with noreturn instruction.
+ assert(child_block.instructions.items.len != 0);
+ assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn());
+
+ // Need to set the type and emit the Block instruction. This allows machine code generation
+ // to emit a jump instruction to after the block when it encounters the break.
+ try parent_block.instructions.append(mod.gpa, &block_inst.base);
+ block_inst.base.ty = try mod.resolvePeerTypes(scope, label.results.items);
+ block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) };
+ return &block_inst.base;
+}
+
+fn analyzeInstBreakpoint(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .breakpoint);
+}
+
+fn analyzeInstBreak(mod: *Module, scope: *Scope, inst: *zir.Inst.Break) InnerError!*Inst {
+ const operand = try resolveInst(mod, scope, inst.positionals.operand);
+ const block = inst.positionals.block;
+ return analyzeBreak(mod, scope, inst.base.src, block, operand);
+}
+
+fn analyzeInstBreakVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) InnerError!*Inst {
+ const block = inst.positionals.block;
+ const void_inst = try mod.constVoid(scope, inst.base.src);
+ return analyzeBreak(mod, scope, inst.base.src, block, void_inst);
+}
+
+fn analyzeInstDbgStmt(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .dbg_stmt);
+}
+
+fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst {
+ const decl_name = try resolveConstString(mod, scope, inst.positionals.name);
+ return mod.analyzeDeclRefByName(scope, inst.base.src, decl_name);
+}
+
+fn analyzeInstDeclRef(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst {
+ return mod.analyzeDeclRefByName(scope, inst.base.src, inst.positionals.name);
+}
+
+fn analyzeInstDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst {
+ const decl = try analyzeDeclVal(mod, scope, inst);
+ const ptr = try mod.analyzeDeclRef(scope, inst.base.src, decl);
+ return mod.analyzeDeref(scope, inst.base.src, ptr, inst.base.src);
+}
+
+fn analyzeInstDeclValInModule(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst {
+ const decl = inst.positionals.decl;
+ const ptr = try mod.analyzeDeclRef(scope, inst.base.src, decl);
+ return mod.analyzeDeref(scope, inst.base.src, ptr, inst.base.src);
+}
+
+fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
+ const func = try resolveInst(mod, scope, inst.positionals.func);
+ if (func.ty.zigTypeTag() != .Fn)
+ return mod.fail(scope, inst.positionals.func.src, "type '{}' not a function", .{func.ty});
+
+ const cc = func.ty.fnCallingConvention();
+ if (cc == .Naked) {
+ // TODO add error note: declared here
+ return mod.fail(
+ scope,
+ inst.positionals.func.src,
+ "unable to call function with naked calling convention",
+ .{},
+ );
+ }
+ const call_params_len = inst.positionals.args.len;
+ const fn_params_len = func.ty.fnParamLen();
+ if (func.ty.fnIsVarArgs()) {
+ if (call_params_len < fn_params_len) {
+ // TODO add error note: declared here
+ return mod.fail(
+ scope,
+ inst.positionals.func.src,
+ "expected at least {} arguments, found {}",
+ .{ fn_params_len, call_params_len },
+ );
+ }
+ return mod.fail(scope, inst.base.src, "TODO implement support for calling var args functions", .{});
+ } else if (fn_params_len != call_params_len) {
+ // TODO add error note: declared here
+ return mod.fail(
+ scope,
+ inst.positionals.func.src,
+ "expected {} arguments, found {}",
+ .{ fn_params_len, call_params_len },
+ );
+ }
+
+ if (inst.kw_args.modifier == .compile_time) {
+ return mod.fail(scope, inst.base.src, "TODO implement comptime function calls", .{});
+ }
+ if (inst.kw_args.modifier != .auto) {
+ return mod.fail(scope, inst.base.src, "TODO implement call with modifier {}", .{inst.kw_args.modifier});
+ }
+
+ // TODO handle function calls of generic functions
+
+ const fn_param_types = try mod.gpa.alloc(Type, fn_params_len);
+ defer mod.gpa.free(fn_param_types);
+ func.ty.fnParamTypes(fn_param_types);
+
+ const casted_args = try scope.arena().alloc(*Inst, fn_params_len);
+ for (inst.positionals.args) |src_arg, i| {
+ const uncasted_arg = try resolveInst(mod, scope, src_arg);
+ casted_args[i] = try mod.coerce(scope, fn_param_types[i], uncasted_arg);
+ }
+
+ const ret_type = func.ty.fnReturnType();
+
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ return mod.addCall(b, inst.base.src, ret_type, func, casted_args);
+}
+
+fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst {
+ const fn_type = try resolveType(mod, scope, fn_inst.positionals.fn_type);
+ const fn_zir = blk: {
+ var fn_arena = std.heap.ArenaAllocator.init(mod.gpa);
+ errdefer fn_arena.deinit();
+
+ const fn_zir = try scope.arena().create(Module.Fn.ZIR);
+ fn_zir.* = .{
+ .body = .{
+ .instructions = fn_inst.positionals.body.instructions,
+ },
+ .arena = fn_arena.state,
+ };
+ break :blk fn_zir;
+ };
+ const new_func = try scope.arena().create(Module.Fn);
+ new_func.* = .{
+ .analysis = .{ .queued = fn_zir },
+ .owner_decl = scope.decl().?,
+ };
+ const fn_payload = try scope.arena().create(Value.Payload.Function);
+ fn_payload.* = .{ .func = new_func };
+ return mod.constInst(scope, fn_inst.base.src, .{
+ .ty = fn_type,
+ .val = Value.initPayload(&fn_payload.base),
+ });
+}
+
+fn analyzeInstIntType(mod: *Module, scope: *Scope, inttype: *zir.Inst.IntType) InnerError!*Inst {
+ return mod.fail(scope, inttype.base.src, "TODO implement inttype", .{});
+}
+
+fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst {
+ const return_type = try resolveType(mod, scope, fntype.positionals.return_type);
+
+ // Hot path for some common function types.
+ if (fntype.positionals.param_types.len == 0) {
+ if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Unspecified) {
+ return mod.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args));
+ }
+
+ if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .Unspecified) {
+ return mod.constType(scope, fntype.base.src, Type.initTag(.fn_void_no_args));
+ }
+
+ if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Naked) {
+ return mod.constType(scope, fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args));
+ }
+
+ if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .C) {
+ return mod.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args));
+ }
+ }
+
+ const arena = scope.arena();
+ const param_types = try arena.alloc(Type, fntype.positionals.param_types.len);
+ for (fntype.positionals.param_types) |param_type, i| {
+ param_types[i] = try resolveType(mod, scope, param_type);
+ }
+
+ const payload = try arena.create(Type.Payload.Function);
+ payload.* = .{
+ .cc = fntype.kw_args.cc,
+ .return_type = return_type,
+ .param_types = param_types,
+ };
+ return mod.constType(scope, fntype.base.src, Type.initPayload(&payload.base));
+}
+
+fn analyzeInstPrimitive(mod: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst {
+ return mod.constInst(scope, primitive.base.src, primitive.positionals.tag.toTypedValue());
+}
+
+fn analyzeInstAs(mod: *Module, scope: *Scope, as: *zir.Inst.BinOp) InnerError!*Inst {
+ const dest_type = try resolveType(mod, scope, as.positionals.lhs);
+ const new_inst = try resolveInst(mod, scope, as.positionals.rhs);
+ return mod.coerce(scope, dest_type, new_inst);
+}
+
+fn analyzeInstPtrToInt(mod: *Module, scope: *Scope, ptrtoint: *zir.Inst.UnOp) InnerError!*Inst {
+ const ptr = try resolveInst(mod, scope, ptrtoint.positionals.operand);
+ if (ptr.ty.zigTypeTag() != .Pointer) {
+ return mod.fail(scope, ptrtoint.positionals.operand.src, "expected pointer, found '{}'", .{ptr.ty});
+ }
+ // TODO handle known-pointer-address
+ const b = try mod.requireRuntimeBlock(scope, ptrtoint.base.src);
+ const ty = Type.initTag(.usize);
+ return mod.addUnOp(b, ptrtoint.base.src, ty, .ptrtoint, ptr);
+}
+
+fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr) InnerError!*Inst {
+ const object_ptr = try resolveInst(mod, scope, fieldptr.positionals.object_ptr);
+ const field_name = try resolveConstString(mod, scope, fieldptr.positionals.field_name);
+
+ const elem_ty = switch (object_ptr.ty.zigTypeTag()) {
+ .Pointer => object_ptr.ty.elemType(),
+ else => return mod.fail(scope, fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
+ };
+ switch (elem_ty.zigTypeTag()) {
+ .Array => {
+ if (mem.eql(u8, field_name, "len")) {
+ const len_payload = try scope.arena().create(Value.Payload.Int_u64);
+ len_payload.* = .{ .int = elem_ty.arrayLen() };
+
+ const ref_payload = try scope.arena().create(Value.Payload.RefVal);
+ ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) };
+
+ return mod.constInst(scope, fieldptr.base.src, .{
+ .ty = Type.initTag(.single_const_pointer_to_comptime_int),
+ .val = Value.initPayload(&ref_payload.base),
+ });
+ } else {
+ return mod.fail(
+ scope,
+ fieldptr.positionals.field_name.src,
+ "no member named '{}' in '{}'",
+ .{ field_name, elem_ty },
+ );
+ }
+ },
+ else => return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}),
+ }
+}
+
+fn analyzeInstIntCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ const dest_type = try resolveType(mod, scope, inst.positionals.lhs);
+ const operand = try resolveInst(mod, scope, inst.positionals.rhs);
+
+ const dest_is_comptime_int = switch (dest_type.zigTypeTag()) {
+ .ComptimeInt => true,
+ .Int => false,
+ else => return mod.fail(
+ scope,
+ inst.positionals.lhs.src,
+ "expected integer type, found '{}'",
+ .{
+ dest_type,
+ },
+ ),
+ };
+
+ switch (operand.ty.zigTypeTag()) {
+ .ComptimeInt, .Int => {},
+ else => return mod.fail(
+ scope,
+ inst.positionals.rhs.src,
+ "expected integer type, found '{}'",
+ .{operand.ty},
+ ),
+ }
+
+ if (operand.value() != null) {
+ return mod.coerce(scope, dest_type, operand);
+ } else if (dest_is_comptime_int) {
+ return mod.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_int'", .{});
+ }
+
+ return mod.fail(scope, inst.base.src, "TODO implement analyze widen or shorten int", .{});
+}
+
+fn analyzeInstBitCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ const dest_type = try resolveType(mod, scope, inst.positionals.lhs);
+ const operand = try resolveInst(mod, scope, inst.positionals.rhs);
+ return mod.bitcast(scope, dest_type, operand);
+}
+
+fn analyzeInstFloatCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ const dest_type = try resolveType(mod, scope, inst.positionals.lhs);
+ const operand = try resolveInst(mod, scope, inst.positionals.rhs);
+
+ const dest_is_comptime_float = switch (dest_type.zigTypeTag()) {
+ .ComptimeFloat => true,
+ .Float => false,
+ else => return mod.fail(
+ scope,
+ inst.positionals.lhs.src,
+ "expected float type, found '{}'",
+ .{
+ dest_type,
+ },
+ ),
+ };
+
+ switch (operand.ty.zigTypeTag()) {
+ .ComptimeFloat, .Float, .ComptimeInt => {},
+ else => return mod.fail(
+ scope,
+ inst.positionals.rhs.src,
+ "expected float type, found '{}'",
+ .{operand.ty},
+ ),
+ }
+
+ if (operand.value() != null) {
+ return mod.coerce(scope, dest_type, operand);
+ } else if (dest_is_comptime_float) {
+ return mod.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_float'", .{});
+ }
+
+ return mod.fail(scope, inst.base.src, "TODO implement analyze widen or shorten float", .{});
+}
+
+fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) InnerError!*Inst {
+ const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr);
+ const uncasted_index = try resolveInst(mod, scope, inst.positionals.index);
+ const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index);
+
+ if (array_ptr.ty.isSinglePointer() and array_ptr.ty.elemType().zigTypeTag() == .Array) {
+ if (array_ptr.value()) |array_ptr_val| {
+ if (elem_index.value()) |index_val| {
+ // Both array pointer and index are compile-time known.
+ const index_u64 = index_val.toUnsignedInt();
+ // @intCast here because it would have been impossible to construct a value that
+ // required a larger index.
+ const elem_ptr = try array_ptr_val.elemPtr(scope.arena(), @intCast(usize, index_u64));
+
+ const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer);
+ type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() };
+
+ return mod.constInst(scope, inst.base.src, .{
+ .ty = Type.initPayload(&type_payload.base),
+ .val = elem_ptr,
+ });
+ }
+ }
+ }
+
+ return mod.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{});
+}
+
+fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{});
+}
+
+fn analyzeInstShr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShr", .{});
+}
+
+fn analyzeInstBitwise(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitwise", .{});
+}
+
+fn analyzeInstArrayCat(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstArrayCat", .{});
+}
+
+fn analyzeInstArrayMul(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ return mod.fail(scope, inst.base.src, "TODO implement analyzeInstArrayMul", .{});
+}
+
+fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const lhs = try resolveInst(mod, scope, inst.positionals.lhs);
+ const rhs = try resolveInst(mod, scope, inst.positionals.rhs);
+
+ const instructions = &[_]*Inst{ lhs, rhs };
+ const resolved_type = try mod.resolvePeerTypes(scope, instructions);
+ const casted_lhs = try mod.coerce(scope, resolved_type, lhs);
+ const casted_rhs = try mod.coerce(scope, resolved_type, rhs);
+
+ const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
+ resolved_type.elemType()
+ else
+ resolved_type;
+
+ const scalar_tag = scalar_type.zigTypeTag();
+
+ if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) {
+ if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
+ return mod.fail(scope, inst.base.src, "vector length mismatch: {} and {}", .{
+ lhs.ty.arrayLen(),
+ rhs.ty.arrayLen(),
+ });
+ }
+ return mod.fail(scope, inst.base.src, "TODO implement support for vectors in analyzeInstBinOp", .{});
+ } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) {
+ return mod.fail(scope, inst.base.src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{
+ lhs.ty,
+ rhs.ty,
+ });
+ }
+
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+ const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat;
+
+ if (!is_int and !(is_float and floatOpAllowed(inst.base.tag))) {
+ return mod.fail(scope, inst.base.src, "invalid operands to binary expression: '{}' and '{}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) });
+ }
+
+ if (casted_lhs.value()) |lhs_val| {
+ if (casted_rhs.value()) |rhs_val| {
+ return analyzeInstComptimeOp(mod, scope, scalar_type, inst, lhs_val, rhs_val);
+ }
+ }
+
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ const ir_tag = switch (inst.base.tag) {
+ .add => Inst.Tag.add,
+ .sub => Inst.Tag.sub,
+ else => return mod.fail(scope, inst.base.src, "TODO implement arithmetic for operand '{}''", .{@tagName(inst.base.tag)}),
+ };
+
+ return mod.addBinOp(b, inst.base.src, scalar_type, ir_tag, casted_lhs, casted_rhs);
+}
+
+/// Analyzes operands that are known at comptime
+fn analyzeInstComptimeOp(mod: *Module, scope: *Scope, res_type: Type, inst: *zir.Inst.BinOp, lhs_val: Value, rhs_val: Value) InnerError!*Inst {
+ // incase rhs is 0, simply return lhs without doing any calculations
+ // TODO Once division is implemented we should throw an error when dividing by 0.
+ if (rhs_val.compareWithZero(.eq)) {
+ return mod.constInst(scope, inst.base.src, .{
+ .ty = res_type,
+ .val = lhs_val,
+ });
+ }
+ const is_int = res_type.isInt() or res_type.zigTypeTag() == .ComptimeInt;
+
+ const value = try switch (inst.base.tag) {
+ .add => blk: {
+ const val = if (is_int)
+ Module.intAdd(scope.arena(), lhs_val, rhs_val)
+ else
+ mod.floatAdd(scope, res_type, inst.base.src, lhs_val, rhs_val);
+ break :blk val;
+ },
+ .sub => blk: {
+ const val = if (is_int)
+ Module.intSub(scope.arena(), lhs_val, rhs_val)
+ else
+ mod.floatSub(scope, res_type, inst.base.src, lhs_val, rhs_val);
+ break :blk val;
+ },
+ else => return mod.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{}'", .{@tagName(inst.base.tag)}),
+ };
+
+ return mod.constInst(scope, inst.base.src, .{
+ .ty = res_type,
+ .val = value,
+ });
+}
+
+fn analyzeInstDeref(mod: *Module, scope: *Scope, deref: *zir.Inst.UnOp) InnerError!*Inst {
+ const ptr = try resolveInst(mod, scope, deref.positionals.operand);
+ return mod.analyzeDeref(scope, deref.base.src, ptr, deref.positionals.operand.src);
+}
+
+fn analyzeInstAsm(mod: *Module, scope: *Scope, assembly: *zir.Inst.Asm) InnerError!*Inst {
+ const return_type = try resolveType(mod, scope, assembly.positionals.return_type);
+ const asm_source = try resolveConstString(mod, scope, assembly.positionals.asm_source);
+ const output = if (assembly.kw_args.output) |o| try resolveConstString(mod, scope, o) else null;
+
+ const inputs = try scope.arena().alloc([]const u8, assembly.kw_args.inputs.len);
+ const clobbers = try scope.arena().alloc([]const u8, assembly.kw_args.clobbers.len);
+ const args = try scope.arena().alloc(*Inst, assembly.kw_args.args.len);
+
+ for (inputs) |*elem, i| {
+ elem.* = try resolveConstString(mod, scope, assembly.kw_args.inputs[i]);
+ }
+ for (clobbers) |*elem, i| {
+ elem.* = try resolveConstString(mod, scope, assembly.kw_args.clobbers[i]);
+ }
+ for (args) |*elem, i| {
+ const arg = try resolveInst(mod, scope, assembly.kw_args.args[i]);
+ elem.* = try mod.coerce(scope, Type.initTag(.usize), arg);
+ }
+
+ const b = try mod.requireRuntimeBlock(scope, assembly.base.src);
+ const inst = try b.arena.create(Inst.Assembly);
+ inst.* = .{
+ .base = .{
+ .tag = .assembly,
+ .ty = return_type,
+ .src = assembly.base.src,
+ },
+ .asm_source = asm_source,
+ .is_volatile = assembly.kw_args.@"volatile",
+ .output = output,
+ .inputs = inputs,
+ .clobbers = clobbers,
+ .args = args,
+ };
+ try b.instructions.append(mod.gpa, &inst.base);
+ return &inst.base;
+}
+
+fn analyzeInstCmp(
+ mod: *Module,
+ scope: *Scope,
+ inst: *zir.Inst.BinOp,
+ op: std.math.CompareOperator,
+) InnerError!*Inst {
+ const lhs = try resolveInst(mod, scope, inst.positionals.lhs);
+ const rhs = try resolveInst(mod, scope, inst.positionals.rhs);
+
+ const is_equality_cmp = switch (op) {
+ .eq, .neq => true,
+ else => false,
+ };
+ const lhs_ty_tag = lhs.ty.zigTypeTag();
+ const rhs_ty_tag = rhs.ty.zigTypeTag();
+ if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {
+ // null == null, null != null
+ return mod.constBool(scope, inst.base.src, op == .eq);
+ } else if (is_equality_cmp and
+ ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
+ rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
+ {
+ // comparing null with optionals
+ const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs;
+ if (opt_operand.value()) |opt_val| {
+ const is_null = opt_val.isNull();
+ return mod.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null);
+ }
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ const inst_tag: Inst.Tag = switch (op) {
+ .eq => .isnull,
+ .neq => .isnonnull,
+ else => unreachable,
+ };
+ return mod.addUnOp(b, inst.base.src, Type.initTag(.bool), inst_tag, opt_operand);
+ } else if (is_equality_cmp and
+ ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))
+ {
+ return mod.fail(scope, inst.base.src, "TODO implement C pointer cmp", .{});
+ } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
+ const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty;
+ return mod.fail(scope, inst.base.src, "comparison of '{}' with null", .{non_null_type});
+ } else if (is_equality_cmp and
+ ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or
+ (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))
+ {
+ return mod.fail(scope, inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
+ } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
+ if (!is_equality_cmp) {
+ return mod.fail(scope, inst.base.src, "{} operator not allowed for errors", .{@tagName(op)});
+ }
+ return mod.fail(scope, inst.base.src, "TODO implement equality comparison between errors", .{});
+ } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) {
+ // This operation allows any combination of integer and float types, regardless of the
+ // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
+ // numeric types.
+ return mod.cmpNumeric(scope, inst.base.src, lhs, rhs, op);
+ }
+ return mod.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{});
+}
+
+fn analyzeInstTypeOf(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const operand = try resolveInst(mod, scope, inst.positionals.operand);
+ return mod.constType(scope, inst.base.src, operand.ty);
+}
+
+fn analyzeInstBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const uncasted_operand = try resolveInst(mod, scope, inst.positionals.operand);
+ const bool_type = Type.initTag(.bool);
+ const operand = try mod.coerce(scope, bool_type, uncasted_operand);
+ if (try mod.resolveDefinedValue(scope, operand)) |val| {
+ return mod.constBool(scope, inst.base.src, !val.toBool());
+ }
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ return mod.addUnOp(b, inst.base.src, bool_type, .not, operand);
+}
+
+fn analyzeInstIsNonNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst {
+ const operand = try resolveInst(mod, scope, inst.positionals.operand);
+ return mod.analyzeIsNull(scope, inst.base.src, operand, invert_logic);
+}
+
+fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst {
+ const uncasted_cond = try resolveInst(mod, scope, inst.positionals.condition);
+ const cond = try mod.coerce(scope, Type.initTag(.bool), uncasted_cond);
+
+ if (try mod.resolveDefinedValue(scope, cond)) |cond_val| {
+ const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body;
+ try analyzeBody(mod, scope, body.*);
+ return mod.constVoid(scope, inst.base.src);
+ }
+
+ const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);
+
+ var true_block: Scope.Block = .{
+ .parent = parent_block,
+ .func = parent_block.func,
+ .decl = parent_block.decl,
+ .instructions = .{},
+ .arena = parent_block.arena,
+ };
+ defer true_block.instructions.deinit(mod.gpa);
+ try analyzeBody(mod, &true_block.base, inst.positionals.then_body);
+
+ var false_block: Scope.Block = .{
+ .parent = parent_block,
+ .func = parent_block.func,
+ .decl = parent_block.decl,
+ .instructions = .{},
+ .arena = parent_block.arena,
+ };
+ defer false_block.instructions.deinit(mod.gpa);
+ try analyzeBody(mod, &false_block.base, inst.positionals.else_body);
+
+ const then_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) };
+ const else_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) };
+ return mod.addCondBr(parent_block, inst.base.src, cond, then_body, else_body);
+}
+
+fn analyzeInstUnreachNoChk(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst {
+ return mod.analyzeUnreach(scope, unreach.base.src);
+}
+
+fn analyzeInstUnreachable(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst {
+ const b = try mod.requireRuntimeBlock(scope, unreach.base.src);
+ // TODO Add compile error for @optimizeFor occurring too late in a scope.
+ if (mod.wantSafety(scope)) {
+ // TODO Once we have a panic function to call, call it here instead of this.
+ _ = try mod.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint);
+ }
+ return mod.analyzeUnreach(scope, unreach.base.src);
+}
+
+fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const operand = try resolveInst(mod, scope, inst.positionals.operand);
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand);
+}
+
+fn analyzeInstRetVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
+ const b = try mod.requireRuntimeBlock(scope, inst.base.src);
+ return mod.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid);
+}
+
+fn floatOpAllowed(tag: zir.Inst.Tag) bool {
+ // extend this swich as additional operators are implemented
+ return switch (tag) {
+ .add, .sub => true,
+ else => false,
+ };
+}
+
+fn analyzeBreak(
+ mod: *Module,
+ scope: *Scope,
+ src: usize,
+ zir_block: *zir.Inst.Block,
+ operand: *Inst,
+) InnerError!*Inst {
+ var opt_block = scope.cast(Scope.Block);
+ while (opt_block) |block| {
+ if (block.label) |*label| {
+ if (label.zir_block == zir_block) {
+ try label.results.append(mod.gpa, operand);
+ const b = try mod.requireRuntimeBlock(scope, src);
+ return mod.addBr(b, src, label.block_inst, operand);
+ }
+ }
+ opt_block = block.parent;
+ } else unreachable;
+}
+
+fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl {
+ const decl_name = inst.positionals.name;
+ const zir_module = scope.namespace().cast(Scope.ZIRModule).?;
+ const src_decl = zir_module.contents.module.findDecl(decl_name) orelse
+ return mod.fail(scope, inst.base.src, "use of undeclared identifier '{}'", .{decl_name});
+
+ const decl = try resolveCompleteZirDecl(mod, scope, src_decl.decl);
+
+ return decl;
+}
+
+fn analyzeInstSingleConstPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const elem_type = try resolveType(mod, scope, inst.positionals.operand);
+ const ty = try mod.singleConstPtrType(scope, inst.base.src, elem_type);
+ return mod.constType(scope, inst.base.src, ty);
+}
+
+fn analyzeInstSingleMutPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+ const elem_type = try resolveType(mod, scope, inst.positionals.operand);
+ const ty = try mod.singleMutPtrType(scope, inst.base.src, elem_type);
+ return mod.constType(scope, inst.base.src, ty);
+}
diff --git a/src/all_types.hpp b/src/all_types.hpp
@@ -4110,7 +4110,7 @@ struct IrInstSrcCheckSwitchProngs {
IrInstSrc *target_value;
IrInstSrcCheckSwitchProngsRange *ranges;
size_t range_count;
- bool have_else_prong;
+ AstNode* else_prong;
bool have_underscore_prong;
};
diff --git a/src/analyze.cpp b/src/analyze.cpp
@@ -1490,6 +1490,20 @@ static OnePossibleValue type_val_resolve_has_one_possible_value(CodeGen *g, ZigV
}
ZigType *analyze_type_expr(CodeGen *g, Scope *scope, AstNode *node) {
+ Error err;
+ // Hot path for simple identifiers, to avoid unnecessary memory allocations.
+ if (node->type == NodeTypeSymbol) {
+ Buf *variable_name = node->data.symbol_expr.symbol;
+ if (buf_eql_str(variable_name, "_"))
+ goto abort_hot_path;
+ ZigType *primitive_type;
+ if ((err = get_primitive_type(g, variable_name, &primitive_type))) {
+ goto abort_hot_path;
+ } else {
+ return primitive_type;
+ }
+abort_hot_path:;
+ }
ZigValue *result = analyze_const_value(g, scope, node, g->builtin_types.entry_type,
nullptr, UndefBad);
if (type_is_invalid(result->type))
diff --git a/src/ir.cpp b/src/ir.cpp
@@ -4300,14 +4300,14 @@ static IrInstGen *ir_build_err_to_int_gen(IrAnalyze *ira, Scope *scope, AstNode
static IrInstSrc *ir_build_check_switch_prongs(IrBuilderSrc *irb, Scope *scope, AstNode *source_node,
IrInstSrc *target_value, IrInstSrcCheckSwitchProngsRange *ranges, size_t range_count,
- bool have_else_prong, bool have_underscore_prong)
+ AstNode* else_prong, bool have_underscore_prong)
{
IrInstSrcCheckSwitchProngs *instruction = ir_build_instruction<IrInstSrcCheckSwitchProngs>(
irb, scope, source_node);
instruction->target_value = target_value;
instruction->ranges = ranges;
instruction->range_count = range_count;
- instruction->have_else_prong = have_else_prong;
+ instruction->else_prong = else_prong;
instruction->have_underscore_prong = have_underscore_prong;
ir_ref_instruction(target_value, irb->current_basic_block);
@@ -9347,7 +9347,7 @@ static IrInstSrc *ir_gen_switch_expr(IrBuilderSrc *irb, Scope *scope, AstNode *n
}
IrInstSrc *switch_prongs_void = ir_build_check_switch_prongs(irb, scope, node, target_value,
- check_ranges.items, check_ranges.length, else_prong != nullptr, underscore_prong != nullptr);
+ check_ranges.items, check_ranges.length, else_prong, underscore_prong != nullptr);
IrInstSrc *br_instruction;
if (cases.length == 0) {
@@ -20604,17 +20604,25 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,
return ira->codegen->invalid_inst_gen;
}
+ ZigType *expected_return_type = result_loc->value->type->data.pointer.child_type;
+
IrInstGen *dummy_value = ir_const(ira, source_instr, return_type);
dummy_value->value->special = ConstValSpecialRuntime;
IrInstGen *dummy_result = ir_implicit_cast2(ira, source_instr,
- dummy_value, result_loc->value->type->data.pointer.child_type);
- if (type_is_invalid(dummy_result->value->type))
+ dummy_value, expected_return_type);
+ if (type_is_invalid(dummy_result->value->type)) {
+ if ((return_type->id == ZigTypeIdErrorUnion || return_type->id == ZigTypeIdErrorSet) &&
+ expected_return_type->id != ZigTypeIdErrorUnion && expected_return_type->id != ZigTypeIdErrorSet)
+ {
+ add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg,
+ ira->explicit_return_type_source_node, buf_create_from_str("function cannot return an error"));
+ }
return ira->codegen->invalid_inst_gen;
- ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
- if (res_child_type == ira->codegen->builtin_types.entry_anytype) {
- res_child_type = return_type;
}
- if (!handle_is_ptr(ira->codegen, res_child_type)) {
+ if (expected_return_type == ira->codegen->builtin_types.entry_anytype) {
+ expected_return_type = return_type;
+ }
+ if (!handle_is_ptr(ira->codegen, expected_return_type)) {
ir_reset_result(call_result_loc);
result_loc = nullptr;
}
@@ -28828,7 +28836,7 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
buf_ptr(enum_field->name)));
}
}
- } else if (!instruction->have_else_prong) {
+ } else if (instruction->else_prong == nullptr) {
if (switch_type->data.enumeration.non_exhaustive) {
ir_add_error(ira, &instruction->base.base,
buf_sprintf("switch on non-exhaustive enum must include `else` or `_` prong"));
@@ -28843,6 +28851,10 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
buf_ptr(enum_field->name)));
}
}
+ } else if(!switch_type->data.enumeration.non_exhaustive && switch_type->data.enumeration.src_field_count == instruction->range_count) {
+ ir_add_error_node(ira, instruction->else_prong,
+ buf_sprintf("unreachable else prong, all cases already handled"));
+ return ira->codegen->invalid_inst_gen;
}
} else if (switch_type->id == ZigTypeIdErrorSet) {
if (!resolve_inferred_error_set(ira->codegen, switch_type, target_value->base.source_node)) {
@@ -28889,7 +28901,7 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
}
field_prev_uses[start_index] = start_value->base.source_node;
}
- if (!instruction->have_else_prong) {
+ if (instruction->else_prong == nullptr) {
if (type_is_global_error_set(switch_type)) {
ir_add_error(ira, &instruction->base.base,
buf_sprintf("else prong required when switching on type 'anyerror'"));
@@ -28951,16 +28963,20 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
return ira->codegen->invalid_inst_gen;
}
}
- if (!instruction->have_else_prong) {
+
BigInt min_val;
eval_min_max_value_int(ira->codegen, switch_type, &min_val, false);
BigInt max_val;
eval_min_max_value_int(ira->codegen, switch_type, &max_val, true);
- if (!rangeset_spans(&rs, &min_val, &max_val)) {
+ bool handles_all_cases = rangeset_spans(&rs, &min_val, &max_val);
+ if (!handles_all_cases && instruction->else_prong == nullptr) {
ir_add_error(ira, &instruction->base.base, buf_sprintf("switch must handle all possibilities"));
return ira->codegen->invalid_inst_gen;
+ } else if(handles_all_cases && instruction->else_prong != nullptr) {
+ ir_add_error_node(ira, instruction->else_prong,
+ buf_sprintf("unreachable else prong, all cases already handled"));
+ return ira->codegen->invalid_inst_gen;
}
- }
} else if (switch_type->id == ZigTypeIdBool) {
int seenTrue = 0;
int seenFalse = 0;
@@ -28990,11 +29006,17 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
return ira->codegen->invalid_inst_gen;
}
}
- if (((seenTrue < 1) || (seenFalse < 1)) && !instruction->have_else_prong) {
+ if (((seenTrue < 1) || (seenFalse < 1)) && instruction->else_prong == nullptr) {
ir_add_error(ira, &instruction->base.base, buf_sprintf("switch must handle all possibilities"));
return ira->codegen->invalid_inst_gen;
}
- } else if (!instruction->have_else_prong) {
+
+ if(seenTrue == 1 && seenFalse == 1 && instruction->else_prong != nullptr) {
+ ir_add_error_node(ira, instruction->else_prong,
+ buf_sprintf("unreachable else prong, all cases already handled"));
+ return ira->codegen->invalid_inst_gen;
+ }
+ } else if (instruction->else_prong == nullptr) {
ir_add_error(ira, &instruction->base.base,
buf_sprintf("else prong required when switching on type '%s'", buf_ptr(&switch_type->name)));
return ira->codegen->invalid_inst_gen;
@@ -29077,6 +29099,19 @@ static IrInstGen *ir_align_cast(IrAnalyze *ira, IrInstGen *target, uint32_t alig
ZigType *result_type;
uint32_t old_align_bytes;
+ ZigType *actual_ptr = target_type;
+ if (actual_ptr->id == ZigTypeIdOptional) {
+ actual_ptr = actual_ptr->data.maybe.child_type;
+ } else if (is_slice(actual_ptr)) {
+ actual_ptr = actual_ptr->data.structure.fields[slice_ptr_index]->type_entry;
+ }
+
+ if (safety_check_on && !type_has_bits(ira->codegen, actual_ptr)) {
+ ir_add_error(ira, &target->base,
+ buf_sprintf("cannot adjust alignment of zero sized type '%s'", buf_ptr(&target_type->name)));
+ return ira->codegen->invalid_inst_gen;
+ }
+
if (target_type->id == ZigTypeIdPointer) {
result_type = adjust_ptr_align(ira->codegen, target_type, align_bytes);
if ((err = resolve_ptr_align(ira, target_type, &old_align_bytes)))
@@ -30894,6 +30929,13 @@ static IrInstGen *ir_analyze_instruction_end_expr(IrAnalyze *ira, IrInstSrcEndEx
IrInstGen *store_ptr = ir_analyze_store_ptr(ira, &instruction->base.base, result_loc, value,
instruction->result_loc->allow_write_through_const);
if (type_is_invalid(store_ptr->value->type)) {
+ if (instruction->result_loc->id == ResultLocIdReturn &&
+ (value->value->type->id == ZigTypeIdErrorUnion || value->value->type->id == ZigTypeIdErrorSet) &&
+ ira->explicit_return_type->id != ZigTypeIdErrorUnion && ira->explicit_return_type->id != ZigTypeIdErrorSet)
+ {
+ add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg,
+ ira->explicit_return_type_source_node, buf_create_from_str("function cannot return an error"));
+ }
return ira->codegen->invalid_inst_gen;
}
}
diff --git a/src/ir_print.cpp b/src/ir_print.cpp
@@ -2175,7 +2175,7 @@ static void ir_print_check_switch_prongs(IrPrintSrc *irp, IrInstSrcCheckSwitchPr
fprintf(irp->f, "...");
ir_print_other_inst_src(irp, instruction->ranges[i].end);
}
- const char *have_else_str = instruction->have_else_prong ? "yes" : "no";
+ const char *have_else_str = instruction->else_prong != nullptr ? "yes" : "no";
fprintf(irp->f, ")else:%s", have_else_str);
}
diff --git a/src/link.cpp b/src/link.cpp
@@ -2107,6 +2107,10 @@ static void construct_linker_job_wasm(LinkJob *lj) {
lj->args.append("-z");
lj->args.append(buf_ptr(buf_sprintf("stack-size=%" ZIG_PRI_usize, stack_size)));
+ // put stack before globals so that stack overflow results in segfault immediately before corrupting globals
+ // see https://github.com/ziglang/zig/issues/4496
+ lj->args.append("--stack-first");
+
if (g->out_type != OutTypeExe) {
lj->args.append("--no-entry"); // So lld doesn't look for _start.
diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp
@@ -2823,6 +2823,14 @@ struct ZigClangSourceLocation ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(
return bitcast(casted->getBeginLoc());
}
+
+enum ZigClangUnaryExprOrTypeTrait_Kind ZigClangUnaryExprOrTypeTraitExpr_getKind(
+ const struct ZigClangUnaryExprOrTypeTraitExpr *self)
+{
+ auto casted = reinterpret_cast<const clang::UnaryExprOrTypeTraitExpr *>(self);
+ return (ZigClangUnaryExprOrTypeTrait_Kind)casted->getKind();
+}
+
const struct ZigClangStmt *ZigClangDoStmt_getBody(const struct ZigClangDoStmt *self) {
auto casted = reinterpret_cast<const clang::DoStmt *>(self);
return reinterpret_cast<const struct ZigClangStmt *>(casted->getBody());
diff --git a/src/zig_clang.h b/src/zig_clang.h
@@ -901,6 +901,14 @@ enum ZigClangExpr_ConstExprUsage {
ZigClangExpr_EvaluateForMangling,
};
+enum ZigClangUnaryExprOrTypeTrait_Kind {
+ ZigClangUnaryExprOrTypeTrait_KindSizeOf,
+ ZigClangUnaryExprOrTypeTrait_KindAlignOf,
+ ZigClangUnaryExprOrTypeTrait_KindVecStep,
+ ZigClangUnaryExprOrTypeTrait_KindOpenMPRequiredSimdAlign,
+ ZigClangUnaryExprOrTypeTrait_KindPreferredAlignOf,
+};
+
ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangSourceManager_getSpellingLoc(const struct ZigClangSourceManager *,
struct ZigClangSourceLocation Loc);
ZIG_EXTERN_C const char *ZigClangSourceManager_getFilename(const struct ZigClangSourceManager *,
@@ -1190,6 +1198,7 @@ ZIG_EXTERN_C const struct ZigClangExpr *ZigClangArraySubscriptExpr_getIdx(const
ZIG_EXTERN_C struct ZigClangQualType ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(const struct ZigClangUnaryExprOrTypeTraitExpr *);
ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(const struct ZigClangUnaryExprOrTypeTraitExpr *);
+ZIG_EXTERN_C enum ZigClangUnaryExprOrTypeTrait_Kind ZigClangUnaryExprOrTypeTraitExpr_getKind(const struct ZigClangUnaryExprOrTypeTraitExpr *);
ZIG_EXTERN_C const struct ZigClangStmt *ZigClangDoStmt_getBody(const struct ZigClangDoStmt *);
ZIG_EXTERN_C const struct ZigClangExpr *ZigClangDoStmt_getCond(const struct ZigClangDoStmt *);
diff --git a/test/compile_errors.zig b/test/compile_errors.zig
@@ -2,6 +2,32 @@ const tests = @import("tests.zig");
const std = @import("std");
pub fn addCases(cases: *tests.CompileErrorContext) void {
+ cases.addTest("@alignCast of zero sized types",
+ \\export fn foo() void {
+ \\ const a: *void = undefined;
+ \\ _ = @alignCast(2, a);
+ \\}
+ \\export fn bar() void {
+ \\ const a: ?*void = undefined;
+ \\ _ = @alignCast(2, a);
+ \\}
+ \\export fn baz() void {
+ \\ const a: []void = undefined;
+ \\ _ = @alignCast(2, a);
+ \\}
+ \\export fn qux() void {
+ \\ const a = struct {
+ \\ fn a(comptime b: u32) void {}
+ \\ }.a;
+ \\ _ = @alignCast(2, a);
+ \\}
+ , &[_][]const u8{
+ "tmp.zig:3:23: error: cannot adjust alignment of zero sized type '*void'",
+ "tmp.zig:7:23: error: cannot adjust alignment of zero sized type '?*void'",
+ "tmp.zig:11:23: error: cannot adjust alignment of zero sized type '[]void'",
+ "tmp.zig:17:23: error: cannot adjust alignment of zero sized type 'fn(u32) anytype'",
+ });
+
cases.addTest("invalid pointer with @Type",
\\export fn entry() void {
\\ _ = @Type(.{ .Pointer = .{
@@ -18,6 +44,28 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
"tmp.zig:2:16: error: sentinels are only allowed on slices and unknown-length pointers",
});
+ cases.addTest("helpful return type error message",
+ \\export fn foo() u32 {
+ \\ return error.Ohno;
+ \\}
+ \\fn bar() !u32 {
+ \\ return error.Ohno;
+ \\}
+ \\export fn baz() void {
+ \\ try bar();
+ \\}
+ \\export fn quux() u32 {
+ \\ return bar();
+ \\}
+ , &[_][]const u8{
+ "tmp.zig:2:17: error: expected type 'u32', found 'error{Ohno}'",
+ "tmp.zig:1:17: note: function cannot return an error",
+ "tmp.zig:8:5: error: expected type 'void', found '@TypeOf(bar).ReturnType.ErrorSet'",
+ "tmp.zig:7:17: note: function cannot return an error",
+ "tmp.zig:11:15: error: expected type 'u32', found '@TypeOf(bar).ReturnType.ErrorSet!u32'",
+ "tmp.zig:10:18: note: function cannot return an error",
+ });
+
cases.addTest("int/float conversion to comptime_int/float",
\\export fn foo() void {
\\ var a: f32 = 2;
@@ -509,6 +557,102 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
"tmp.zig:12:5: error: switch on non-exhaustive enum must include `else` or `_` prong",
});
+ cases.add("switch expression - unreachable else prong (bool)",
+ \\fn foo(x: bool) void {
+ \\ switch (x) {
+ \\ true => {},
+ \\ false => {},
+ \\ else => {},
+ \\ }
+ \\}
+ \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
+ , &[_][]const u8{
+ "tmp.zig:5:9: error: unreachable else prong, all cases already handled",
+ });
+
+ cases.add("switch expression - unreachable else prong (u1)",
+ \\fn foo(x: u1) void {
+ \\ switch (x) {
+ \\ 0 => {},
+ \\ 1 => {},
+ \\ else => {},
+ \\ }
+ \\}
+ \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
+ , &[_][]const u8{
+ "tmp.zig:5:9: error: unreachable else prong, all cases already handled",
+ });
+
+ cases.add("switch expression - unreachable else prong (u2)",
+ \\fn foo(x: u2) void {
+ \\ switch (x) {
+ \\ 0 => {},
+ \\ 1 => {},
+ \\ 2 => {},
+ \\ 3 => {},
+ \\ else => {},
+ \\ }
+ \\}
+ \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
+ , &[_][]const u8{
+ "tmp.zig:7:9: error: unreachable else prong, all cases already handled",
+ });
+
+ cases.add("switch expression - unreachable else prong (range u8)",
+ \\fn foo(x: u8) void {
+ \\ switch (x) {
+ \\ 0 => {},
+ \\ 1 => {},
+ \\ 2 => {},
+ \\ 3 => {},
+ \\ 4...255 => {},
+ \\ else => {},
+ \\ }
+ \\}
+ \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
+ , &[_][]const u8{
+ "tmp.zig:8:9: error: unreachable else prong, all cases already handled",
+ });
+
+ cases.add("switch expression - unreachable else prong (range i8)",
+ \\fn foo(x: i8) void {
+ \\ switch (x) {
+ \\ -128...0 => {},
+ \\ 1 => {},
+ \\ 2 => {},
+ \\ 3 => {},
+ \\ 4...127 => {},
+ \\ else => {},
+ \\ }
+ \\}
+ \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
+ , &[_][]const u8{
+ "tmp.zig:8:9: error: unreachable else prong, all cases already handled",
+ });
+
+ cases.add("switch expression - unreachable else prong (enum)",
+ \\const TestEnum = enum{ T1, T2 };
+ \\
+ \\fn err(x: u8) TestEnum {
+ \\ switch (x) {
+ \\ 0 => return TestEnum.T1,
+ \\ else => return TestEnum.T2,
+ \\ }
+ \\}
+ \\
+ \\fn foo(x: u8) void {
+ \\ switch (err(x)) {
+ \\ TestEnum.T1 => {},
+ \\ TestEnum.T2 => {},
+ \\ else => {},
+ \\ }
+ \\}
+ \\
+ \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
+ , &[_][]const u8{
+ "tmp.zig:14:9: error: unreachable else prong, all cases already handled",
+ });
+
cases.addTest("@export with empty name string",
\\pub export fn entry() void { }
\\comptime {
diff --git a/test/stage1/behavior/bugs/1111.zig b/test/stage1/behavior/bugs/1111.zig
@@ -7,6 +7,5 @@ test "issue 1111 fixed" {
switch (v) {
Foo.Bar => return,
- else => return,
}
}
diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig
@@ -7,6 +7,11 @@ const linux_x64 = std.zig.CrossTarget{
.os_tag = .linux,
};
+const linux_riscv64 = std.zig.CrossTarget{
+ .cpu_arch = .riscv64,
+ .os_tag = .linux,
+};
+
pub fn addCases(ctx: *TestContext) !void {
if (std.Target.current.os.tag != .linux or
std.Target.current.cpu.arch != .x86_64)
@@ -118,6 +123,42 @@ pub fn addCases(ctx: *TestContext) !void {
\\
);
}
+
+ {
+ var case = ctx.exe("hello world", linux_riscv64);
+ // Regular old hello world
+ case.addCompareOutput(
+ \\export fn _start() noreturn {
+ \\ print();
+ \\
+ \\ exit();
+ \\}
+ \\
+ \\fn print() void {
+ \\ asm volatile ("ecall"
+ \\ :
+ \\ : [number] "{a7}" (64),
+ \\ [arg1] "{a0}" (1),
+ \\ [arg2] "{a1}" (@ptrToInt("Hello, World!\n")),
+ \\ [arg3] "{a2}" ("Hello, World!\n".len)
+ \\ : "rcx", "r11", "memory"
+ \\ );
+ \\ return;
+ \\}
+ \\
+ \\fn exit() noreturn {
+ \\ asm volatile ("ecall"
+ \\ :
+ \\ : [number] "{a7}" (94),
+ \\ [arg1] "{a0}" (0)
+ \\ : "rcx", "r11", "memory"
+ \\ );
+ \\ unreachable;
+ \\}
+ ,
+ "Hello, World!\n",
+ );
+ }
{
var case = ctx.exe("adding numbers at comptime", linux_x64);
@@ -333,5 +374,69 @@ pub fn addCases(ctx: *TestContext) !void {
,
"",
);
+
+ // Now we test integer return values.
+ case.addCompareOutput(
+ \\export fn _start() noreturn {
+ \\ assert(add(3, 4) == 7);
+ \\ assert(add(20, 10) == 30);
+ \\
+ \\ exit();
+ \\}
+ \\
+ \\fn add(a: u32, b: u32) u32 {
+ \\ return a + b;
+ \\}
+ \\
+ \\pub fn assert(ok: bool) void {
+ \\ if (!ok) unreachable; // assertion failure
+ \\}
+ \\
+ \\fn exit() noreturn {
+ \\ asm volatile ("syscall"
+ \\ :
+ \\ : [number] "{rax}" (231),
+ \\ [arg1] "{rdi}" (0)
+ \\ : "rcx", "r11", "memory"
+ \\ );
+ \\ unreachable;
+ \\}
+ ,
+ "",
+ );
+
+ // Local mutable variables.
+ case.addCompareOutput(
+ \\export fn _start() noreturn {
+ \\ assert(add(3, 4) == 7);
+ \\ assert(add(20, 10) == 30);
+ \\
+ \\ exit();
+ \\}
+ \\
+ \\fn add(a: u32, b: u32) u32 {
+ \\ var x: u32 = undefined;
+ \\ x = 0;
+ \\ x += a;
+ \\ x += b;
+ \\ return x;
+ \\}
+ \\
+ \\pub fn assert(ok: bool) void {
+ \\ if (!ok) unreachable; // assertion failure
+ \\}
+ \\
+ \\fn exit() noreturn {
+ \\ asm volatile ("syscall"
+ \\ :
+ \\ : [number] "{rax}" (231),
+ \\ [arg1] "{rdi}" (0)
+ \\ : "rcx", "r11", "memory"
+ \\ );
+ \\ unreachable;
+ \\}
+ ,
+ "",
+ );
}
}
diff --git a/test/translate_c.zig b/test/translate_c.zig
@@ -3,6 +3,16 @@ const std = @import("std");
const CrossTarget = std.zig.CrossTarget;
pub fn addCases(cases: *tests.TranslateCContext) void {
+ cases.add("alignof",
+ \\int main() {
+ \\ int a = _Alignof(int);
+ \\}
+ , &[_][]const u8{
+ \\pub export fn main() c_int {
+ \\ var a: c_int = @bitCast(c_int, @truncate(c_uint, @alignOf(c_int)));
+ \\}
+ });
+
cases.add("initializer list macro",
\\typedef struct Color {
\\ unsigned char r;
@@ -2715,6 +2725,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\pub const BAR = (@import("std").meta.cast(?*c_void, a));
});
+ cases.add("macro with cast to unsigned short, long, and long long",
+ \\#define CURLAUTH_BASIC_BUT_USHORT ((unsigned short) 1)
+ \\#define CURLAUTH_BASIC ((unsigned long) 1)
+ \\#define CURLAUTH_BASIC_BUT_ULONGLONG ((unsigned long long) 1)
+ , &[_][]const u8{
+ \\pub const CURLAUTH_BASIC_BUT_USHORT = (@import("std").meta.cast(c_ushort, 1));
+ \\pub const CURLAUTH_BASIC = (@import("std").meta.cast(c_ulong, 1));
+ \\pub const CURLAUTH_BASIC_BUT_ULONGLONG = (@import("std").meta.cast(c_ulonglong, 1));
+ });
+
cases.add("macro conditional operator",
\\#define FOO a ? b : c
, &[_][]const u8{