| author | |
| committer | |
| log | c6e0df6213af1ecf5734bc6bcb4d7c29a4b41351 |
| tree | 42d358704ab86a506d1c53c0fc6da73738560fa7 |
| parent | 41a8b6f57b3bd50b2ed6fdced74fba9130eac3d3 |
| parent | d61a9e37ae8f140407d1369500d21efbe2b198ab |
69 files changed, 6721 insertions(+), 3221 deletions(-)
build.zig+9| ... | @@ -77,6 +77,9 @@ pub fn build(b: *Builder) !void { | ... | @@ -77,6 +77,9 @@ pub fn build(b: *Builder) !void { |
| 77 | const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false; | 77 | const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false; |
| 78 | if (link_libc) exe.linkLibC(); | 78 | if (link_libc) exe.linkLibC(); |
| 79 | 79 | ||
| 80 | const log_scopes = b.option([]const []const u8, "log", "Which log scopes to enable") orelse &[0][]const u8{}; | ||
| 81 | |||
| 82 | exe.addBuildOption([]const []const u8, "log_scopes", log_scopes); | ||
| 80 | exe.addBuildOption(bool, "enable_tracy", tracy != null); | 83 | exe.addBuildOption(bool, "enable_tracy", tracy != null); |
| 81 | if (tracy) |tracy_path| { | 84 | if (tracy) |tracy_path| { |
| 82 | const client_cpp = fs.path.join( | 85 | const client_cpp = fs.path.join( |
| ... | @@ -104,6 +107,12 @@ pub fn build(b: *Builder) !void { | ... | @@ -104,6 +107,12 @@ pub fn build(b: *Builder) !void { |
| 104 | const is_wasmtime_enabled = b.option(bool, "enable-wasmtime", "Use Wasmtime to enable and run WASI libstd tests") orelse false; | 107 | const is_wasmtime_enabled = b.option(bool, "enable-wasmtime", "Use Wasmtime to enable and run WASI libstd tests") orelse false; |
| 105 | const glibc_multi_dir = b.option([]const u8, "enable-foreign-glibc", "Provide directory with glibc installations to run cross compiled tests that link glibc"); | 108 | const glibc_multi_dir = b.option([]const u8, "enable-foreign-glibc", "Provide directory with glibc installations to run cross compiled tests that link glibc"); |
| 106 | 109 | ||
| 110 | |||
| 111 | test_stage2.addBuildOption(bool, "enable_qemu", is_qemu_enabled); | ||
| 112 | test_stage2.addBuildOption(bool, "enable_wine", is_wine_enabled); | ||
| 113 | test_stage2.addBuildOption(bool, "enable_wasmtime", is_wasmtime_enabled); | ||
| 114 | test_stage2.addBuildOption(?[]const u8, "glibc_multi_install_dir", glibc_multi_dir); | ||
| 115 | |||
| 107 | const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests"); | 116 | const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests"); |
| 108 | test_stage2_step.dependOn(&test_stage2.step); | 117 | test_stage2_step.dependOn(&test_stage2.step); |
| 109 | test_step.dependOn(test_stage2_step); | 118 | test_step.dependOn(test_stage2_step); |
ci/azure/pipelines.yml+1-1| ... | @@ -41,7 +41,7 @@ jobs: | ... | @@ -41,7 +41,7 @@ jobs: |
| 41 | 41 | ||
| 42 | steps: | 42 | steps: |
| 43 | - powershell: | | 43 | - powershell: | |
| 44 | (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") | 44 | (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") |
| 45 | .\sfx.exe -y -o\ | 45 | .\sfx.exe -y -o\ |
| 46 | del sfx.exe | 46 | del sfx.exe |
| 47 | displayName: Download/Extract/Install MSYS2 | 47 | displayName: Download/Extract/Install MSYS2 |
doc/langref.html.in+2-1| ... | @@ -2,6 +2,7 @@ | ... | @@ -2,6 +2,7 @@ |
| 2 | <html lang="en"> | 2 | <html lang="en"> |
| 3 | <head> | 3 | <head> |
| 4 | <meta charset="utf-8"> | 4 | <meta charset="utf-8"> |
| 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| 5 | <title>Documentation - The Zig Programming Language</title> | 6 | <title>Documentation - The Zig Programming Language</title> |
| 6 | <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgklEQVR4AWMYWuD7EllJIM4G4g4g5oIJ/odhOJ8wToOxSTXgNxDHoeiBMfA4+wGShjyYOCkG/IGqWQziEzYAoUAeiF9D5U+DxEg14DRU7jWIT5IBIOdCxf+A+CQZAAoopEB7QJwBCBwHiip8UYmRdrAlDpIMgApwQZNnNii5Dq0MBgCxxycBnwEd+wAAAABJRU5ErkJggg=="/> | 7 | <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgklEQVR4AWMYWuD7EllJIM4G4g4g5oIJ/odhOJ8wToOxSTXgNxDHoeiBMfA4+wGShjyYOCkG/IGqWQziEzYAoUAeiF9D5U+DxEg14DRU7jWIT5IBIOdCxf+A+CQZAAoopEB7QJwBCBwHiip8UYmRdrAlDpIMgApwQZNnNii5Dq0MBgCxxycBnwEd+wAAAABJRU5ErkJggg=="/> |
| 7 | <style> | 8 | <style> |
| ... | @@ -7473,7 +7474,7 @@ export fn @"A function name that is a complete sentence."() void {} | ... | @@ -7473,7 +7474,7 @@ export fn @"A function name that is a complete sentence."() void {} |
| 7473 | <p> | 7474 | <p> |
| 7474 | When looking at the resulting object, you can see the symbol is used verbatim: | 7475 | When looking at the resulting object, you can see the symbol is used verbatim: |
| 7475 | </p> | 7476 | </p> |
| 7476 | <pre>00000000000001f0 T A function name that is a complete sentence.</pre> | 7477 | <pre><code>00000000000001f0 T A function name that is a complete sentence.</code></pre> |
| 7477 | {#see_also|Exporting a C Library#} | 7478 | {#see_also|Exporting a C Library#} |
| 7478 | {#header_close#} | 7479 | {#header_close#} |
| 7479 | 7480 |
lib/std/array_list.zig+86| ... | @@ -108,6 +108,33 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { | ... | @@ -108,6 +108,33 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { |
| 108 | mem.copy(T, self.items[i .. i + items.len], items); | 108 | mem.copy(T, self.items[i .. i + items.len], items); |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | /// Replace range of elements `list[start..start+len]` with `new_items` | ||
| 112 | /// grows list if `len < new_items.len`. may allocate | ||
| 113 | /// shrinks list if `len > new_items.len` | ||
| 114 | pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: SliceConst) !void { | ||
| 115 | const after_range = start + len; | ||
| 116 | const range = self.items[start..after_range]; | ||
| 117 | |||
| 118 | if (range.len == new_items.len) | ||
| 119 | mem.copy(T, range, new_items) | ||
| 120 | else if (range.len < new_items.len) { | ||
| 121 | const first = new_items[0..range.len]; | ||
| 122 | const rest = new_items[range.len..]; | ||
| 123 | |||
| 124 | mem.copy(T, range, first); | ||
| 125 | try self.insertSlice(after_range, rest); | ||
| 126 | } else { | ||
| 127 | mem.copy(T, range, new_items); | ||
| 128 | const after_subrange = start + new_items.len; | ||
| 129 | |||
| 130 | for (self.items[after_range..]) |item, i| { | ||
| 131 | self.items[after_subrange..][i] = item; | ||
| 132 | } | ||
| 133 | |||
| 134 | self.items.len -= len - new_items.len; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 111 | /// Extend the list by 1 element. Allocates more memory as necessary. | 138 | /// Extend the list by 1 element. Allocates more memory as necessary. |
| 112 | pub fn append(self: *Self, item: T) !void { | 139 | pub fn append(self: *Self, item: T) !void { |
| 113 | const new_item_ptr = try self.addOne(); | 140 | const new_item_ptr = try self.addOne(); |
| ... | @@ -189,6 +216,15 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { | ... | @@ -189,6 +216,15 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { |
| 189 | mem.set(T, self.items[old_len..self.items.len], value); | 216 | mem.set(T, self.items[old_len..self.items.len], value); |
| 190 | } | 217 | } |
| 191 | 218 | ||
| 219 | /// Append a value to the list `n` times. | ||
| 220 | /// Asserts the capacity is enough. | ||
| 221 | pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void { | ||
| 222 | const new_len = self.items.len + n; | ||
| 223 | assert(new_len <= self.capacity); | ||
| 224 | mem.set(T, self.items.ptr[self.items.len..new_len], value); | ||
| 225 | self.items.len = new_len; | ||
| 226 | } | ||
| 227 | |||
| 192 | /// Adjust the list's length to `new_len`. | 228 | /// Adjust the list's length to `new_len`. |
| 193 | /// Does not initialize added items if any. | 229 | /// Does not initialize added items if any. |
| 194 | pub fn resize(self: *Self, new_len: usize) !void { | 230 | pub fn resize(self: *Self, new_len: usize) !void { |
| ... | @@ -366,6 +402,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ | ... | @@ -366,6 +402,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ |
| 366 | mem.copy(T, self.items[i .. i + items.len], items); | 402 | mem.copy(T, self.items[i .. i + items.len], items); |
| 367 | } | 403 | } |
| 368 | 404 | ||
| 405 | /// Replace range of elements `list[start..start+len]` with `new_items` | ||
| 406 | /// grows list if `len < new_items.len`. may allocate | ||
| 407 | /// shrinks list if `len > new_items.len` | ||
| 408 | pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: SliceConst) !void { | ||
| 409 | var managed = self.toManaged(allocator); | ||
| 410 | try managed.replaceRange(start, len, new_items); | ||
| 411 | self.* = managed.toUnmanaged(); | ||
| 412 | } | ||
| 413 | |||
| 369 | /// Extend the list by 1 element. Allocates more memory as necessary. | 414 | /// Extend the list by 1 element. Allocates more memory as necessary. |
| 370 | pub fn append(self: *Self, allocator: *Allocator, item: T) !void { | 415 | pub fn append(self: *Self, allocator: *Allocator, item: T) !void { |
| 371 | const new_item_ptr = try self.addOne(allocator); | 416 | const new_item_ptr = try self.addOne(allocator); |
| ... | @@ -437,6 +482,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ | ... | @@ -437,6 +482,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ |
| 437 | mem.set(T, self.items[old_len..self.items.len], value); | 482 | mem.set(T, self.items[old_len..self.items.len], value); |
| 438 | } | 483 | } |
| 439 | 484 | ||
| 485 | /// Append a value to the list `n` times. | ||
| 486 | /// Asserts the capacity is enough. | ||
| 487 | pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void { | ||
| 488 | const new_len = self.items.len + n; | ||
| 489 | assert(new_len <= self.capacity); | ||
| 490 | mem.set(T, self.items.ptr[self.items.len..new_len], value); | ||
| 491 | self.items.len = new_len; | ||
| 492 | } | ||
| 493 | |||
| 440 | /// Adjust the list's length to `new_len`. | 494 | /// Adjust the list's length to `new_len`. |
| 441 | /// Does not initialize added items if any. | 495 | /// Does not initialize added items if any. |
| 442 | pub fn resize(self: *Self, allocator: *Allocator, new_len: usize) !void { | 496 | pub fn resize(self: *Self, allocator: *Allocator, new_len: usize) !void { |
| ... | @@ -714,6 +768,38 @@ test "std.ArrayList.insertSlice" { | ... | @@ -714,6 +768,38 @@ test "std.ArrayList.insertSlice" { |
| 714 | testing.expect(list.items[0] == 1); | 768 | testing.expect(list.items[0] == 1); |
| 715 | } | 769 | } |
| 716 | 770 | ||
| 771 | test "std.ArrayList.replaceRange" { | ||
| 772 | var arena = std.heap.ArenaAllocator.init(testing.allocator); | ||
| 773 | defer arena.deinit(); | ||
| 774 | |||
| 775 | const alloc = &arena.allocator; | ||
| 776 | const init = [_]i32{ 1, 2, 3, 4, 5 }; | ||
| 777 | const new = [_]i32{ 0, 0, 0 }; | ||
| 778 | |||
| 779 | var list_zero = ArrayList(i32).init(alloc); | ||
| 780 | var list_eq = ArrayList(i32).init(alloc); | ||
| 781 | var list_lt = ArrayList(i32).init(alloc); | ||
| 782 | var list_gt = ArrayList(i32).init(alloc); | ||
| 783 | |||
| 784 | try list_zero.appendSlice(&init); | ||
| 785 | try list_eq.appendSlice(&init); | ||
| 786 | try list_lt.appendSlice(&init); | ||
| 787 | try list_gt.appendSlice(&init); | ||
| 788 | |||
| 789 | try list_zero.replaceRange(1, 0, &new); | ||
| 790 | try list_eq.replaceRange(1, 3, &new); | ||
| 791 | try list_lt.replaceRange(1, 2, &new); | ||
| 792 | |||
| 793 | // after_range > new_items.len in function body | ||
| 794 | testing.expect(1 + 4 > new.len); | ||
| 795 | try list_gt.replaceRange(1, 4, &new); | ||
| 796 | |||
| 797 | testing.expectEqualSlices(i32, list_zero.items, &[_]i32{ 1, 0, 0, 0, 2, 3, 4, 5 }); | ||
| 798 | testing.expectEqualSlices(i32, list_eq.items, &[_]i32{ 1, 0, 0, 0, 5 }); | ||
| 799 | testing.expectEqualSlices(i32, list_lt.items, &[_]i32{ 1, 0, 0, 0, 4, 5 }); | ||
| 800 | testing.expectEqualSlices(i32, list_gt.items, &[_]i32{ 1, 0, 0, 0 }); | ||
| 801 | } | ||
| 802 | |||
| 717 | const Item = struct { | 803 | const Item = struct { |
| 718 | integer: i32, | 804 | integer: i32, |
| 719 | sub_items: ArrayList(Item), | 805 | sub_items: ArrayList(Item), |
lib/std/build.zig+41-24| ... | @@ -430,9 +430,9 @@ pub const Builder = struct { | ... | @@ -430,9 +430,9 @@ pub const Builder = struct { |
| 430 | const entry = self.user_input_options.getEntry(name) orelse return null; | 430 | const entry = self.user_input_options.getEntry(name) orelse return null; |
| 431 | entry.value.used = true; | 431 | entry.value.used = true; |
| 432 | switch (type_id) { | 432 | switch (type_id) { |
| 433 | TypeId.Bool => switch (entry.value.value) { | 433 | .Bool => switch (entry.value.value) { |
| 434 | UserValue.Flag => return true, | 434 | .Flag => return true, |
| 435 | UserValue.Scalar => |s| { | 435 | .Scalar => |s| { |
| 436 | if (mem.eql(u8, s, "true")) { | 436 | if (mem.eql(u8, s, "true")) { |
| 437 | return true; | 437 | return true; |
| 438 | } else if (mem.eql(u8, s, "false")) { | 438 | } else if (mem.eql(u8, s, "false")) { |
| ... | @@ -443,21 +443,21 @@ pub const Builder = struct { | ... | @@ -443,21 +443,21 @@ pub const Builder = struct { |
| 443 | return null; | 443 | return null; |
| 444 | } | 444 | } |
| 445 | }, | 445 | }, |
| 446 | UserValue.List => { | 446 | .List => { |
| 447 | warn("Expected -D{} to be a boolean, but received a list.\n", .{name}); | 447 | warn("Expected -D{} to be a boolean, but received a list.\n", .{name}); |
| 448 | self.markInvalidUserInput(); | 448 | self.markInvalidUserInput(); |
| 449 | return null; | 449 | return null; |
| 450 | }, | 450 | }, |
| 451 | }, | 451 | }, |
| 452 | TypeId.Int => panic("TODO integer options to build script", .{}), | 452 | .Int => panic("TODO integer options to build script", .{}), |
| 453 | TypeId.Float => panic("TODO float options to build script", .{}), | 453 | .Float => panic("TODO float options to build script", .{}), |
| 454 | TypeId.Enum => switch (entry.value.value) { | 454 | .Enum => switch (entry.value.value) { |
| 455 | UserValue.Flag => { | 455 | .Flag => { |
| 456 | warn("Expected -D{} to be a string, but received a boolean.\n", .{name}); | 456 | warn("Expected -D{} to be a string, but received a boolean.\n", .{name}); |
| 457 | self.markInvalidUserInput(); | 457 | self.markInvalidUserInput(); |
| 458 | return null; | 458 | return null; |
| 459 | }, | 459 | }, |
| 460 | UserValue.Scalar => |s| { | 460 | .Scalar => |s| { |
| 461 | if (std.meta.stringToEnum(T, s)) |enum_lit| { | 461 | if (std.meta.stringToEnum(T, s)) |enum_lit| { |
| 462 | return enum_lit; | 462 | return enum_lit; |
| 463 | } else { | 463 | } else { |
| ... | @@ -466,33 +466,35 @@ pub const Builder = struct { | ... | @@ -466,33 +466,35 @@ pub const Builder = struct { |
| 466 | return null; | 466 | return null; |
| 467 | } | 467 | } |
| 468 | }, | 468 | }, |
| 469 | UserValue.List => { | 469 | .List => { |
| 470 | warn("Expected -D{} to be a string, but received a list.\n", .{name}); | 470 | warn("Expected -D{} to be a string, but received a list.\n", .{name}); |
| 471 | self.markInvalidUserInput(); | 471 | self.markInvalidUserInput(); |
| 472 | return null; | 472 | return null; |
| 473 | }, | 473 | }, |
| 474 | }, | 474 | }, |
| 475 | TypeId.String => switch (entry.value.value) { | 475 | .String => switch (entry.value.value) { |
| 476 | UserValue.Flag => { | 476 | .Flag => { |
| 477 | warn("Expected -D{} to be a string, but received a boolean.\n", .{name}); | 477 | warn("Expected -D{} to be a string, but received a boolean.\n", .{name}); |
| 478 | self.markInvalidUserInput(); | 478 | self.markInvalidUserInput(); |
| 479 | return null; | 479 | return null; |
| 480 | }, | 480 | }, |
| 481 | UserValue.List => { | 481 | .List => { |
| 482 | warn("Expected -D{} to be a string, but received a list.\n", .{name}); | 482 | warn("Expected -D{} to be a string, but received a list.\n", .{name}); |
| 483 | self.markInvalidUserInput(); | 483 | self.markInvalidUserInput(); |
| 484 | return null; | 484 | return null; |
| 485 | }, | 485 | }, |
| 486 | UserValue.Scalar => |s| return s, | 486 | .Scalar => |s| return s, |
| 487 | }, | 487 | }, |
| 488 | TypeId.List => switch (entry.value.value) { | 488 | .List => switch (entry.value.value) { |
| 489 | UserValue.Flag => { | 489 | .Flag => { |
| 490 | warn("Expected -D{} to be a list, but received a boolean.\n", .{name}); | 490 | warn("Expected -D{} to be a list, but received a boolean.\n", .{name}); |
| 491 | self.markInvalidUserInput(); | 491 | self.markInvalidUserInput(); |
| 492 | return null; | 492 | return null; |
| 493 | }, | 493 | }, |
| 494 | UserValue.Scalar => |s| return &[_][]const u8{s}, | 494 | .Scalar => |s| { |
| 495 | UserValue.List => |lst| return lst.span(), | 495 | return self.allocator.dupe([]const u8, &[_][]const u8{s}) catch unreachable; |
| 496 | }, | ||
| 497 | .List => |lst| return lst.span(), | ||
| 496 | }, | 498 | }, |
| 497 | } | 499 | } |
| 498 | } | 500 | } |
| ... | @@ -1151,6 +1153,7 @@ pub const LibExeObjStep = struct { | ... | @@ -1151,6 +1153,7 @@ pub const LibExeObjStep = struct { |
| 1151 | bundle_compiler_rt: bool, | 1153 | bundle_compiler_rt: bool, |
| 1152 | disable_stack_probing: bool, | 1154 | disable_stack_probing: bool, |
| 1153 | disable_sanitize_c: bool, | 1155 | disable_sanitize_c: bool, |
| 1156 | rdynamic: bool, | ||
| 1154 | c_std: Builder.CStd, | 1157 | c_std: Builder.CStd, |
| 1155 | override_lib_dir: ?[]const u8, | 1158 | override_lib_dir: ?[]const u8, |
| 1156 | main_pkg_path: ?[]const u8, | 1159 | main_pkg_path: ?[]const u8, |
| ... | @@ -1311,6 +1314,7 @@ pub const LibExeObjStep = struct { | ... | @@ -1311,6 +1314,7 @@ pub const LibExeObjStep = struct { |
| 1311 | .bundle_compiler_rt = false, | 1314 | .bundle_compiler_rt = false, |
| 1312 | .disable_stack_probing = false, | 1315 | .disable_stack_probing = false, |
| 1313 | .disable_sanitize_c = false, | 1316 | .disable_sanitize_c = false, |
| 1317 | .rdynamic = false, | ||
| 1314 | .output_dir = null, | 1318 | .output_dir = null, |
| 1315 | .single_threaded = false, | 1319 | .single_threaded = false, |
| 1316 | .installed_path = null, | 1320 | .installed_path = null, |
| ... | @@ -1704,13 +1708,23 @@ pub const LibExeObjStep = struct { | ... | @@ -1704,13 +1708,23 @@ pub const LibExeObjStep = struct { |
| 1704 | 1708 | ||
| 1705 | pub fn addBuildOption(self: *LibExeObjStep, comptime T: type, name: []const u8, value: T) void { | 1709 | pub fn addBuildOption(self: *LibExeObjStep, comptime T: type, name: []const u8, value: T) void { |
| 1706 | const out = self.build_options_contents.outStream(); | 1710 | const out = self.build_options_contents.outStream(); |
| 1711 | if (T == []const []const u8) { | ||
| 1712 | out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{name}) catch unreachable; | ||
| 1713 | for (value) |slice| { | ||
| 1714 | out.writeAll(" ") catch unreachable; | ||
| 1715 | std.zig.renderStringLiteral(slice, out) catch unreachable; | ||
| 1716 | out.writeAll(",\n") catch unreachable; | ||
| 1717 | } | ||
| 1718 | out.writeAll("};\n") catch unreachable; | ||
| 1719 | return; | ||
| 1720 | } | ||
| 1707 | switch (@typeInfo(T)) { | 1721 | switch (@typeInfo(T)) { |
| 1708 | .Enum => |enum_info| { | 1722 | .Enum => |enum_info| { |
| 1709 | out.print("const {} = enum {{\n", .{@typeName(T)}) catch unreachable; | 1723 | out.print("pub const {} = enum {{\n", .{@typeName(T)}) catch unreachable; |
| 1710 | inline for (enum_info.fields) |field| { | 1724 | inline for (enum_info.fields) |field| { |
| 1711 | out.print(" {},\n", .{field.name}) catch unreachable; | 1725 | out.print(" {},\n", .{field.name}) catch unreachable; |
| 1712 | } | 1726 | } |
| 1713 | out.print("}};\n", .{}) catch unreachable; | 1727 | out.writeAll("};\n") catch unreachable; |
| 1714 | }, | 1728 | }, |
| 1715 | else => {}, | 1729 | else => {}, |
| 1716 | } | 1730 | } |
| ... | @@ -1843,10 +1857,10 @@ pub const LibExeObjStep = struct { | ... | @@ -1843,10 +1857,10 @@ pub const LibExeObjStep = struct { |
| 1843 | zig_args.append(builder.zig_exe) catch unreachable; | 1857 | zig_args.append(builder.zig_exe) catch unreachable; |
| 1844 | 1858 | ||
| 1845 | const cmd = switch (self.kind) { | 1859 | const cmd = switch (self.kind) { |
| 1846 | Kind.Lib => "build-lib", | 1860 | .Lib => "build-lib", |
| 1847 | Kind.Exe => "build-exe", | 1861 | .Exe => "build-exe", |
| 1848 | Kind.Obj => "build-obj", | 1862 | .Obj => "build-obj", |
| 1849 | Kind.Test => "test", | 1863 | .Test => "test", |
| 1850 | }; | 1864 | }; |
| 1851 | zig_args.append(cmd) catch unreachable; | 1865 | zig_args.append(cmd) catch unreachable; |
| 1852 | 1866 | ||
| ... | @@ -1994,6 +2008,9 @@ pub const LibExeObjStep = struct { | ... | @@ -1994,6 +2008,9 @@ pub const LibExeObjStep = struct { |
| 1994 | if (self.disable_sanitize_c) { | 2008 | if (self.disable_sanitize_c) { |
| 1995 | try zig_args.append("-fno-sanitize-c"); | 2009 | try zig_args.append("-fno-sanitize-c"); |
| 1996 | } | 2010 | } |
| 2011 | if (self.rdynamic) { | ||
| 2012 | try zig_args.append("-rdynamic"); | ||
| 2013 | } | ||
| 1997 | 2014 | ||
| 1998 | if (self.code_model != .default) { | 2015 | if (self.code_model != .default) { |
| 1999 | try zig_args.append("-code-model"); | 2016 | try zig_args.append("-code-model"); |
lib/std/build/emit_raw.zig+5-3| ... | @@ -46,9 +46,10 @@ const BinaryElfOutput = struct { | ... | @@ -46,9 +46,10 @@ const BinaryElfOutput = struct { |
| 46 | .segments = ArrayList(*BinaryElfSegment).init(allocator), | 46 | .segments = ArrayList(*BinaryElfSegment).init(allocator), |
| 47 | .sections = ArrayList(*BinaryElfSection).init(allocator), | 47 | .sections = ArrayList(*BinaryElfSection).init(allocator), |
| 48 | }; | 48 | }; |
| 49 | const elf_hdrs = try std.elf.readAllHeaders(allocator, elf_file); | 49 | const elf_hdr = try std.elf.readHeader(elf_file); |
| 50 | 50 | ||
| 51 | for (elf_hdrs.section_headers) |section, i| { | 51 | var section_headers = elf_hdr.section_header_iterator(elf_file); |
| 52 | while (try section_headers.next()) |section| { | ||
| 52 | if (sectionValidForOutput(section)) { | 53 | if (sectionValidForOutput(section)) { |
| 53 | const newSection = try allocator.create(BinaryElfSection); | 54 | const newSection = try allocator.create(BinaryElfSection); |
| 54 | 55 | ||
| ... | @@ -61,7 +62,8 @@ const BinaryElfOutput = struct { | ... | @@ -61,7 +62,8 @@ const BinaryElfOutput = struct { |
| 61 | } | 62 | } |
| 62 | } | 63 | } |
| 63 | 64 | ||
| 64 | for (elf_hdrs.program_headers) |phdr, i| { | 65 | var program_headers = elf_hdr.program_header_iterator(elf_file); |
| 66 | while (try program_headers.next()) |phdr| { | ||
| 65 | if (phdr.p_type == elf.PT_LOAD) { | 67 | if (phdr.p_type == elf.PT_LOAD) { |
| 66 | const newSegment = try allocator.create(BinaryElfSegment); | 68 | const newSegment = try allocator.create(BinaryElfSegment); |
| 67 | 69 |
lib/std/build/run.zig+18| ... | @@ -4,6 +4,7 @@ const build = std.build; | ... | @@ -4,6 +4,7 @@ const build = std.build; |
| 4 | const Step = build.Step; | 4 | const Step = build.Step; |
| 5 | const Builder = build.Builder; | 5 | const Builder = build.Builder; |
| 6 | const LibExeObjStep = build.LibExeObjStep; | 6 | const LibExeObjStep = build.LibExeObjStep; |
| 7 | const WriteFileStep = build.WriteFileStep; | ||
| 7 | const fs = std.fs; | 8 | const fs = std.fs; |
| 8 | const mem = std.mem; | 9 | const mem = std.mem; |
| 9 | const process = std.process; | 10 | const process = std.process; |
| ... | @@ -42,6 +43,10 @@ pub const RunStep = struct { | ... | @@ -42,6 +43,10 @@ pub const RunStep = struct { |
| 42 | 43 | ||
| 43 | pub const Arg = union(enum) { | 44 | pub const Arg = union(enum) { |
| 44 | Artifact: *LibExeObjStep, | 45 | Artifact: *LibExeObjStep, |
| 46 | WriteFile: struct { | ||
| 47 | step: *WriteFileStep, | ||
| 48 | file_name: []const u8, | ||
| 49 | }, | ||
| 45 | Bytes: []u8, | 50 | Bytes: []u8, |
| 46 | }; | 51 | }; |
| 47 | 52 | ||
| ... | @@ -62,6 +67,16 @@ pub const RunStep = struct { | ... | @@ -62,6 +67,16 @@ pub const RunStep = struct { |
| 62 | self.step.dependOn(&artifact.step); | 67 | self.step.dependOn(&artifact.step); |
| 63 | } | 68 | } |
| 64 | 69 | ||
| 70 | pub fn addWriteFileArg(self: *RunStep, write_file: *WriteFileStep, file_name: []const u8) void { | ||
| 71 | self.argv.append(Arg{ | ||
| 72 | .WriteFile = .{ | ||
| 73 | .step = write_file, | ||
| 74 | .file_name = file_name, | ||
| 75 | }, | ||
| 76 | }) catch unreachable; | ||
| 77 | self.step.dependOn(&write_file.step); | ||
| 78 | } | ||
| 79 | |||
| 65 | pub fn addArg(self: *RunStep, arg: []const u8) void { | 80 | pub fn addArg(self: *RunStep, arg: []const u8) void { |
| 66 | self.argv.append(Arg{ .Bytes = self.builder.dupe(arg) }) catch unreachable; | 81 | self.argv.append(Arg{ .Bytes = self.builder.dupe(arg) }) catch unreachable; |
| 67 | } | 82 | } |
| ... | @@ -142,6 +157,9 @@ pub const RunStep = struct { | ... | @@ -142,6 +157,9 @@ pub const RunStep = struct { |
| 142 | for (self.argv.span()) |arg| { | 157 | for (self.argv.span()) |arg| { |
| 143 | switch (arg) { | 158 | switch (arg) { |
| 144 | Arg.Bytes => |bytes| try argv_list.append(bytes), | 159 | Arg.Bytes => |bytes| try argv_list.append(bytes), |
| 160 | Arg.WriteFile => |file| { | ||
| 161 | try argv_list.append(file.step.getOutputPath(file.file_name)); | ||
| 162 | }, | ||
| 145 | Arg.Artifact => |artifact| { | 163 | Arg.Artifact => |artifact| { |
| 146 | if (artifact.target.isWindows()) { | 164 | if (artifact.target.isWindows()) { |
| 147 | // On Windows we don't have rpaths so we have to add .dll search paths to PATH | 165 | // On Windows we don't have rpaths so we have to add .dll search paths to PATH |
lib/std/c.zig+4| ... | @@ -8,6 +8,10 @@ pub const Tokenizer = tokenizer.Tokenizer; | ... | @@ -8,6 +8,10 @@ pub const Tokenizer = tokenizer.Tokenizer; |
| 8 | pub const parse = @import("c/parse.zig").parse; | 8 | pub const parse = @import("c/parse.zig").parse; |
| 9 | pub const ast = @import("c/ast.zig"); | 9 | pub const ast = @import("c/ast.zig"); |
| 10 | 10 | ||
| 11 | test "" { | ||
| 12 | _ = tokenizer; | ||
| 13 | } | ||
| 14 | |||
| 11 | pub usingnamespace @import("os/bits.zig"); | 15 | pub usingnamespace @import("os/bits.zig"); |
| 12 | 16 | ||
| 13 | pub usingnamespace switch (std.Target.current.os.tag) { | 17 | pub usingnamespace switch (std.Target.current.os.tag) { |
lib/std/c/tokenizer.zig+76-115| ... | @@ -1,19 +1,10 @@ | ... | @@ -1,19 +1,10 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const mem = std.mem; | 2 | const mem = std.mem; |
| 3 | 3 | ||
| 4 | pub const Source = struct { | ||
| 5 | buffer: []const u8, | ||
| 6 | file_name: []const u8, | ||
| 7 | tokens: TokenList, | ||
| 8 | |||
| 9 | pub const TokenList = std.SegmentedList(Token, 64); | ||
| 10 | }; | ||
| 11 | |||
| 12 | pub const Token = struct { | 4 | pub const Token = struct { |
| 13 | id: Id, | 5 | id: Id, |
| 14 | start: usize, | 6 | start: usize, |
| 15 | end: usize, | 7 | end: usize, |
| 16 | source: *Source, | ||
| 17 | 8 | ||
| 18 | pub const Id = union(enum) { | 9 | pub const Id = union(enum) { |
| 19 | Invalid, | 10 | Invalid, |
| ... | @@ -251,31 +242,6 @@ pub const Token = struct { | ... | @@ -251,31 +242,6 @@ pub const Token = struct { |
| 251 | } | 242 | } |
| 252 | }; | 243 | }; |
| 253 | 244 | ||
| 254 | pub fn eql(a: Token, b: Token) bool { | ||
| 255 | // do we really need this cast here | ||
| 256 | if (@as(@TagType(Id), a.id) != b.id) return false; | ||
| 257 | return mem.eql(u8, a.slice(), b.slice()); | ||
| 258 | } | ||
| 259 | |||
| 260 | pub fn slice(tok: Token) []const u8 { | ||
| 261 | return tok.source.buffer[tok.start..tok.end]; | ||
| 262 | } | ||
| 263 | |||
| 264 | pub const Keyword = struct { | ||
| 265 | bytes: []const u8, | ||
| 266 | id: Id, | ||
| 267 | hash: u32, | ||
| 268 | |||
| 269 | fn init(bytes: []const u8, id: Id) Keyword { | ||
| 270 | @setEvalBranchQuota(2000); | ||
| 271 | return .{ | ||
| 272 | .bytes = bytes, | ||
| 273 | .id = id, | ||
| 274 | .hash = std.hash_map.hashString(bytes), | ||
| 275 | }; | ||
| 276 | } | ||
| 277 | }; | ||
| 278 | |||
| 279 | // TODO extensions | 245 | // TODO extensions |
| 280 | pub const keywords = std.ComptimeStringMap(Id, .{ | 246 | pub const keywords = std.ComptimeStringMap(Id, .{ |
| 281 | .{ "auto", .Keyword_auto }, | 247 | .{ "auto", .Keyword_auto }, |
| ... | @@ -355,26 +321,26 @@ pub const Token = struct { | ... | @@ -355,26 +321,26 @@ pub const Token = struct { |
| 355 | } | 321 | } |
| 356 | 322 | ||
| 357 | pub const NumSuffix = enum { | 323 | pub const NumSuffix = enum { |
| 358 | None, | 324 | none, |
| 359 | F, | 325 | f, |
| 360 | L, | 326 | l, |
| 361 | U, | 327 | u, |
| 362 | LU, | 328 | lu, |
| 363 | LL, | 329 | ll, |
| 364 | LLU, | 330 | llu, |
| 365 | }; | 331 | }; |
| 366 | 332 | ||
| 367 | pub const StrKind = enum { | 333 | pub const StrKind = enum { |
| 368 | None, | 334 | none, |
| 369 | Wide, | 335 | wide, |
| 370 | Utf8, | 336 | utf_8, |
| 371 | Utf16, | 337 | utf_16, |
| 372 | Utf32, | 338 | utf_32, |
| 373 | }; | 339 | }; |
| 374 | }; | 340 | }; |
| 375 | 341 | ||
| 376 | pub const Tokenizer = struct { | 342 | pub const Tokenizer = struct { |
| 377 | source: *Source, | 343 | buffer: []const u8, |
| 378 | index: usize = 0, | 344 | index: usize = 0, |
| 379 | prev_tok_id: @TagType(Token.Id) = .Invalid, | 345 | prev_tok_id: @TagType(Token.Id) = .Invalid, |
| 380 | pp_directive: bool = false, | 346 | pp_directive: bool = false, |
| ... | @@ -385,7 +351,6 @@ pub const Tokenizer = struct { | ... | @@ -385,7 +351,6 @@ pub const Tokenizer = struct { |
| 385 | .id = .Eof, | 351 | .id = .Eof, |
| 386 | .start = self.index, | 352 | .start = self.index, |
| 387 | .end = undefined, | 353 | .end = undefined, |
| 388 | .source = self.source, | ||
| 389 | }; | 354 | }; |
| 390 | var state: enum { | 355 | var state: enum { |
| 391 | Start, | 356 | Start, |
| ... | @@ -446,8 +411,8 @@ pub const Tokenizer = struct { | ... | @@ -446,8 +411,8 @@ pub const Tokenizer = struct { |
| 446 | } = .Start; | 411 | } = .Start; |
| 447 | var string = false; | 412 | var string = false; |
| 448 | var counter: u32 = 0; | 413 | var counter: u32 = 0; |
| 449 | while (self.index < self.source.buffer.len) : (self.index += 1) { | 414 | while (self.index < self.buffer.len) : (self.index += 1) { |
| 450 | const c = self.source.buffer[self.index]; | 415 | const c = self.buffer[self.index]; |
| 451 | switch (state) { | 416 | switch (state) { |
| 452 | .Start => switch (c) { | 417 | .Start => switch (c) { |
| 453 | '\n' => { | 418 | '\n' => { |
| ... | @@ -460,11 +425,11 @@ pub const Tokenizer = struct { | ... | @@ -460,11 +425,11 @@ pub const Tokenizer = struct { |
| 460 | state = .Cr; | 425 | state = .Cr; |
| 461 | }, | 426 | }, |
| 462 | '"' => { | 427 | '"' => { |
| 463 | result.id = .{ .StringLiteral = .None }; | 428 | result.id = .{ .StringLiteral = .none }; |
| 464 | state = .StringLiteral; | 429 | state = .StringLiteral; |
| 465 | }, | 430 | }, |
| 466 | '\'' => { | 431 | '\'' => { |
| 467 | result.id = .{ .CharLiteral = .None }; | 432 | result.id = .{ .CharLiteral = .none }; |
| 468 | state = .CharLiteralStart; | 433 | state = .CharLiteralStart; |
| 469 | }, | 434 | }, |
| 470 | 'u' => { | 435 | 'u' => { |
| ... | @@ -641,11 +606,11 @@ pub const Tokenizer = struct { | ... | @@ -641,11 +606,11 @@ pub const Tokenizer = struct { |
| 641 | state = .u8; | 606 | state = .u8; |
| 642 | }, | 607 | }, |
| 643 | '\'' => { | 608 | '\'' => { |
| 644 | result.id = .{ .CharLiteral = .Utf16 }; | 609 | result.id = .{ .CharLiteral = .utf_16 }; |
| 645 | state = .CharLiteralStart; | 610 | state = .CharLiteralStart; |
| 646 | }, | 611 | }, |
| 647 | '\"' => { | 612 | '\"' => { |
| 648 | result.id = .{ .StringLiteral = .Utf16 }; | 613 | result.id = .{ .StringLiteral = .utf_16 }; |
| 649 | state = .StringLiteral; | 614 | state = .StringLiteral; |
| 650 | }, | 615 | }, |
| 651 | else => { | 616 | else => { |
| ... | @@ -655,7 +620,7 @@ pub const Tokenizer = struct { | ... | @@ -655,7 +620,7 @@ pub const Tokenizer = struct { |
| 655 | }, | 620 | }, |
| 656 | .u8 => switch (c) { | 621 | .u8 => switch (c) { |
| 657 | '\"' => { | 622 | '\"' => { |
| 658 | result.id = .{ .StringLiteral = .Utf8 }; | 623 | result.id = .{ .StringLiteral = .utf_8 }; |
| 659 | state = .StringLiteral; | 624 | state = .StringLiteral; |
| 660 | }, | 625 | }, |
| 661 | else => { | 626 | else => { |
| ... | @@ -665,11 +630,11 @@ pub const Tokenizer = struct { | ... | @@ -665,11 +630,11 @@ pub const Tokenizer = struct { |
| 665 | }, | 630 | }, |
| 666 | .U => switch (c) { | 631 | .U => switch (c) { |
| 667 | '\'' => { | 632 | '\'' => { |
| 668 | result.id = .{ .CharLiteral = .Utf32 }; | 633 | result.id = .{ .CharLiteral = .utf_32 }; |
| 669 | state = .CharLiteralStart; | 634 | state = .CharLiteralStart; |
| 670 | }, | 635 | }, |
| 671 | '\"' => { | 636 | '\"' => { |
| 672 | result.id = .{ .StringLiteral = .Utf32 }; | 637 | result.id = .{ .StringLiteral = .utf_32 }; |
| 673 | state = .StringLiteral; | 638 | state = .StringLiteral; |
| 674 | }, | 639 | }, |
| 675 | else => { | 640 | else => { |
| ... | @@ -679,11 +644,11 @@ pub const Tokenizer = struct { | ... | @@ -679,11 +644,11 @@ pub const Tokenizer = struct { |
| 679 | }, | 644 | }, |
| 680 | .L => switch (c) { | 645 | .L => switch (c) { |
| 681 | '\'' => { | 646 | '\'' => { |
| 682 | result.id = .{ .CharLiteral = .Wide }; | 647 | result.id = .{ .CharLiteral = .wide }; |
| 683 | state = .CharLiteralStart; | 648 | state = .CharLiteralStart; |
| 684 | }, | 649 | }, |
| 685 | '\"' => { | 650 | '\"' => { |
| 686 | result.id = .{ .StringLiteral = .Wide }; | 651 | result.id = .{ .StringLiteral = .wide }; |
| 687 | state = .StringLiteral; | 652 | state = .StringLiteral; |
| 688 | }, | 653 | }, |
| 689 | else => { | 654 | else => { |
| ... | @@ -808,7 +773,7 @@ pub const Tokenizer = struct { | ... | @@ -808,7 +773,7 @@ pub const Tokenizer = struct { |
| 808 | .Identifier => switch (c) { | 773 | .Identifier => switch (c) { |
| 809 | 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, | 774 | 'a'...'z', 'A'...'Z', '_', '0'...'9' => {}, |
| 810 | else => { | 775 | else => { |
| 811 | result.id = Token.getKeyword(self.source.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier; | 776 | result.id = Token.getKeyword(self.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier; |
| 812 | if (self.prev_tok_id == .Hash) | 777 | if (self.prev_tok_id == .Hash) |
| 813 | self.pp_directive = true; | 778 | self.pp_directive = true; |
| 814 | break; | 779 | break; |
| ... | @@ -1137,7 +1102,7 @@ pub const Tokenizer = struct { | ... | @@ -1137,7 +1102,7 @@ pub const Tokenizer = struct { |
| 1137 | state = .IntegerSuffixL; | 1102 | state = .IntegerSuffixL; |
| 1138 | }, | 1103 | }, |
| 1139 | else => { | 1104 | else => { |
| 1140 | result.id = .{ .IntegerLiteral = .None }; | 1105 | result.id = .{ .IntegerLiteral = .none }; |
| 1141 | break; | 1106 | break; |
| 1142 | }, | 1107 | }, |
| 1143 | }, | 1108 | }, |
| ... | @@ -1146,7 +1111,7 @@ pub const Tokenizer = struct { | ... | @@ -1146,7 +1111,7 @@ pub const Tokenizer = struct { |
| 1146 | state = .IntegerSuffixUL; | 1111 | state = .IntegerSuffixUL; |
| 1147 | }, | 1112 | }, |
| 1148 | else => { | 1113 | else => { |
| 1149 | result.id = .{ .IntegerLiteral = .U }; | 1114 | result.id = .{ .IntegerLiteral = .u }; |
| 1150 | break; | 1115 | break; |
| 1151 | }, | 1116 | }, |
| 1152 | }, | 1117 | }, |
| ... | @@ -1155,34 +1120,34 @@ pub const Tokenizer = struct { | ... | @@ -1155,34 +1120,34 @@ pub const Tokenizer = struct { |
| 1155 | state = .IntegerSuffixLL; | 1120 | state = .IntegerSuffixLL; |
| 1156 | }, | 1121 | }, |
| 1157 | 'u', 'U' => { | 1122 | 'u', 'U' => { |
| 1158 | result.id = .{ .IntegerLiteral = .LU }; | 1123 | result.id = .{ .IntegerLiteral = .lu }; |
| 1159 | self.index += 1; | 1124 | self.index += 1; |
| 1160 | break; | 1125 | break; |
| 1161 | }, | 1126 | }, |
| 1162 | else => { | 1127 | else => { |
| 1163 | result.id = .{ .IntegerLiteral = .L }; | 1128 | result.id = .{ .IntegerLiteral = .l }; |
| 1164 | break; | 1129 | break; |
| 1165 | }, | 1130 | }, |
| 1166 | }, | 1131 | }, |
| 1167 | .IntegerSuffixLL => switch (c) { | 1132 | .IntegerSuffixLL => switch (c) { |
| 1168 | 'u', 'U' => { | 1133 | 'u', 'U' => { |
| 1169 | result.id = .{ .IntegerLiteral = .LLU }; | 1134 | result.id = .{ .IntegerLiteral = .llu }; |
| 1170 | self.index += 1; | 1135 | self.index += 1; |
| 1171 | break; | 1136 | break; |
| 1172 | }, | 1137 | }, |
| 1173 | else => { | 1138 | else => { |
| 1174 | result.id = .{ .IntegerLiteral = .LL }; | 1139 | result.id = .{ .IntegerLiteral = .ll }; |
| 1175 | break; | 1140 | break; |
| 1176 | }, | 1141 | }, |
| 1177 | }, | 1142 | }, |
| 1178 | .IntegerSuffixUL => switch (c) { | 1143 | .IntegerSuffixUL => switch (c) { |
| 1179 | 'l', 'L' => { | 1144 | 'l', 'L' => { |
| 1180 | result.id = .{ .IntegerLiteral = .LLU }; | 1145 | result.id = .{ .IntegerLiteral = .llu }; |
| 1181 | self.index += 1; | 1146 | self.index += 1; |
| 1182 | break; | 1147 | break; |
| 1183 | }, | 1148 | }, |
| 1184 | else => { | 1149 | else => { |
| 1185 | result.id = .{ .IntegerLiteral = .LU }; | 1150 | result.id = .{ .IntegerLiteral = .lu }; |
| 1186 | break; | 1151 | break; |
| 1187 | }, | 1152 | }, |
| 1188 | }, | 1153 | }, |
| ... | @@ -1230,26 +1195,26 @@ pub const Tokenizer = struct { | ... | @@ -1230,26 +1195,26 @@ pub const Tokenizer = struct { |
| 1230 | }, | 1195 | }, |
| 1231 | .FloatSuffix => switch (c) { | 1196 | .FloatSuffix => switch (c) { |
| 1232 | 'l', 'L' => { | 1197 | 'l', 'L' => { |
| 1233 | result.id = .{ .FloatLiteral = .L }; | 1198 | result.id = .{ .FloatLiteral = .l }; |
| 1234 | self.index += 1; | 1199 | self.index += 1; |
| 1235 | break; | 1200 | break; |
| 1236 | }, | 1201 | }, |
| 1237 | 'f', 'F' => { | 1202 | 'f', 'F' => { |
| 1238 | result.id = .{ .FloatLiteral = .F }; | 1203 | result.id = .{ .FloatLiteral = .f }; |
| 1239 | self.index += 1; | 1204 | self.index += 1; |
| 1240 | break; | 1205 | break; |
| 1241 | }, | 1206 | }, |
| 1242 | else => { | 1207 | else => { |
| 1243 | result.id = .{ .FloatLiteral = .None }; | 1208 | result.id = .{ .FloatLiteral = .none }; |
| 1244 | break; | 1209 | break; |
| 1245 | }, | 1210 | }, |
| 1246 | }, | 1211 | }, |
| 1247 | } | 1212 | } |
| 1248 | } else if (self.index == self.source.buffer.len) { | 1213 | } else if (self.index == self.buffer.len) { |
| 1249 | switch (state) { | 1214 | switch (state) { |
| 1250 | .Start => {}, | 1215 | .Start => {}, |
| 1251 | .u, .u8, .U, .L, .Identifier => { | 1216 | .u, .u8, .U, .L, .Identifier => { |
| 1252 | result.id = Token.getKeyword(self.source.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier; | 1217 | result.id = Token.getKeyword(self.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier; |
| 1253 | }, | 1218 | }, |
| 1254 | 1219 | ||
| 1255 | .Cr, | 1220 | .Cr, |
| ... | @@ -1270,11 +1235,11 @@ pub const Tokenizer = struct { | ... | @@ -1270,11 +1235,11 @@ pub const Tokenizer = struct { |
| 1270 | .MacroString, | 1235 | .MacroString, |
| 1271 | => result.id = .Invalid, | 1236 | => result.id = .Invalid, |
| 1272 | 1237 | ||
| 1273 | .FloatExponentDigits => result.id = if (counter == 0) .Invalid else .{ .FloatLiteral = .None }, | 1238 | .FloatExponentDigits => result.id = if (counter == 0) .Invalid else .{ .FloatLiteral = .none }, |
| 1274 | 1239 | ||
| 1275 | .FloatFraction, | 1240 | .FloatFraction, |
| 1276 | .FloatFractionHex, | 1241 | .FloatFractionHex, |
| 1277 | => result.id = .{ .FloatLiteral = .None }, | 1242 | => result.id = .{ .FloatLiteral = .none }, |
| 1278 | 1243 | ||
| 1279 | .IntegerLiteralOct, | 1244 | .IntegerLiteralOct, |
| 1280 | .IntegerLiteralBinary, | 1245 | .IntegerLiteralBinary, |
| ... | @@ -1282,13 +1247,13 @@ pub const Tokenizer = struct { | ... | @@ -1282,13 +1247,13 @@ pub const Tokenizer = struct { |
| 1282 | .IntegerLiteral, | 1247 | .IntegerLiteral, |
| 1283 | .IntegerSuffix, | 1248 | .IntegerSuffix, |
| 1284 | .Zero, | 1249 | .Zero, |
| 1285 | => result.id = .{ .IntegerLiteral = .None }, | 1250 | => result.id = .{ .IntegerLiteral = .none }, |
| 1286 | .IntegerSuffixU => result.id = .{ .IntegerLiteral = .U }, | 1251 | .IntegerSuffixU => result.id = .{ .IntegerLiteral = .u }, |
| 1287 | .IntegerSuffixL => result.id = .{ .IntegerLiteral = .L }, | 1252 | .IntegerSuffixL => result.id = .{ .IntegerLiteral = .l }, |
| 1288 | .IntegerSuffixLL => result.id = .{ .IntegerLiteral = .LL }, | 1253 | .IntegerSuffixLL => result.id = .{ .IntegerLiteral = .ll }, |
| 1289 | .IntegerSuffixUL => result.id = .{ .IntegerLiteral = .LU }, | 1254 | .IntegerSuffixUL => result.id = .{ .IntegerLiteral = .lu }, |
| 1290 | 1255 | ||
| 1291 | .FloatSuffix => result.id = .{ .FloatLiteral = .None }, | 1256 | .FloatSuffix => result.id = .{ .FloatLiteral = .none }, |
| 1292 | .Equal => result.id = .Equal, | 1257 | .Equal => result.id = .Equal, |
| 1293 | .Bang => result.id = .Bang, | 1258 | .Bang => result.id = .Bang, |
| 1294 | .Minus => result.id = .Minus, | 1259 | .Minus => result.id = .Minus, |
| ... | @@ -1466,7 +1431,7 @@ test "preprocessor keywords" { | ... | @@ -1466,7 +1431,7 @@ test "preprocessor keywords" { |
| 1466 | .Hash, | 1431 | .Hash, |
| 1467 | .Identifier, | 1432 | .Identifier, |
| 1468 | .AngleBracketLeft, | 1433 | .AngleBracketLeft, |
| 1469 | .{ .IntegerLiteral = .None }, | 1434 | .{ .IntegerLiteral = .none }, |
| 1470 | .Nl, | 1435 | .Nl, |
| 1471 | .Hash, | 1436 | .Hash, |
| 1472 | .Keyword_ifdef, | 1437 | .Keyword_ifdef, |
| ... | @@ -1499,18 +1464,18 @@ test "line continuation" { | ... | @@ -1499,18 +1464,18 @@ test "line continuation" { |
| 1499 | .Identifier, | 1464 | .Identifier, |
| 1500 | .Identifier, | 1465 | .Identifier, |
| 1501 | .Nl, | 1466 | .Nl, |
| 1502 | .{ .StringLiteral = .None }, | 1467 | .{ .StringLiteral = .none }, |
| 1503 | .Nl, | 1468 | .Nl, |
| 1504 | .Hash, | 1469 | .Hash, |
| 1505 | .Keyword_define, | 1470 | .Keyword_define, |
| 1506 | .{ .StringLiteral = .None }, | 1471 | .{ .StringLiteral = .none }, |
| 1507 | .Nl, | 1472 | .Nl, |
| 1508 | .{ .StringLiteral = .None }, | 1473 | .{ .StringLiteral = .none }, |
| 1509 | .Nl, | 1474 | .Nl, |
| 1510 | .Hash, | 1475 | .Hash, |
| 1511 | .Keyword_define, | 1476 | .Keyword_define, |
| 1512 | .{ .StringLiteral = .None }, | 1477 | .{ .StringLiteral = .none }, |
| 1513 | .{ .StringLiteral = .None }, | 1478 | .{ .StringLiteral = .none }, |
| 1514 | }); | 1479 | }); |
| 1515 | } | 1480 | } |
| 1516 | 1481 | ||
| ... | @@ -1527,23 +1492,23 @@ test "string prefix" { | ... | @@ -1527,23 +1492,23 @@ test "string prefix" { |
| 1527 | \\L'foo' | 1492 | \\L'foo' |
| 1528 | \\ | 1493 | \\ |
| 1529 | , &[_]Token.Id{ | 1494 | , &[_]Token.Id{ |
| 1530 | .{ .StringLiteral = .None }, | 1495 | .{ .StringLiteral = .none }, |
| 1531 | .Nl, | 1496 | .Nl, |
| 1532 | .{ .StringLiteral = .Utf16 }, | 1497 | .{ .StringLiteral = .utf_16 }, |
| 1533 | .Nl, | 1498 | .Nl, |
| 1534 | .{ .StringLiteral = .Utf8 }, | 1499 | .{ .StringLiteral = .utf_8 }, |
| 1535 | .Nl, | 1500 | .Nl, |
| 1536 | .{ .StringLiteral = .Utf32 }, | 1501 | .{ .StringLiteral = .utf_32 }, |
| 1537 | .Nl, | 1502 | .Nl, |
| 1538 | .{ .StringLiteral = .Wide }, | 1503 | .{ .StringLiteral = .wide }, |
| 1539 | .Nl, | 1504 | .Nl, |
| 1540 | .{ .CharLiteral = .None }, | 1505 | .{ .CharLiteral = .none }, |
| 1541 | .Nl, | 1506 | .Nl, |
| 1542 | .{ .CharLiteral = .Utf16 }, | 1507 | .{ .CharLiteral = .utf_16 }, |
| 1543 | .Nl, | 1508 | .Nl, |
| 1544 | .{ .CharLiteral = .Utf32 }, | 1509 | .{ .CharLiteral = .utf_32 }, |
| 1545 | .Nl, | 1510 | .Nl, |
| 1546 | .{ .CharLiteral = .Wide }, | 1511 | .{ .CharLiteral = .wide }, |
| 1547 | .Nl, | 1512 | .Nl, |
| 1548 | }); | 1513 | }); |
| 1549 | } | 1514 | } |
| ... | @@ -1555,33 +1520,29 @@ test "num suffixes" { | ... | @@ -1555,33 +1520,29 @@ test "num suffixes" { |
| 1555 | \\ 1u 1ul 1ull 1 | 1520 | \\ 1u 1ul 1ull 1 |
| 1556 | \\ | 1521 | \\ |
| 1557 | , &[_]Token.Id{ | 1522 | , &[_]Token.Id{ |
| 1558 | .{ .FloatLiteral = .F }, | 1523 | .{ .FloatLiteral = .f }, |
| 1559 | .{ .FloatLiteral = .L }, | 1524 | .{ .FloatLiteral = .l }, |
| 1560 | .{ .FloatLiteral = .None }, | 1525 | .{ .FloatLiteral = .none }, |
| 1561 | .{ .FloatLiteral = .None }, | 1526 | .{ .FloatLiteral = .none }, |
| 1562 | .{ .FloatLiteral = .None }, | 1527 | .{ .FloatLiteral = .none }, |
| 1563 | .Nl, | 1528 | .Nl, |
| 1564 | .{ .IntegerLiteral = .L }, | 1529 | .{ .IntegerLiteral = .l }, |
| 1565 | .{ .IntegerLiteral = .LU }, | 1530 | .{ .IntegerLiteral = .lu }, |
| 1566 | .{ .IntegerLiteral = .LL }, | 1531 | .{ .IntegerLiteral = .ll }, |
| 1567 | .{ .IntegerLiteral = .LLU }, | 1532 | .{ .IntegerLiteral = .llu }, |
| 1568 | .{ .IntegerLiteral = .None }, | 1533 | .{ .IntegerLiteral = .none }, |
| 1569 | .Nl, | 1534 | .Nl, |
| 1570 | .{ .IntegerLiteral = .U }, | 1535 | .{ .IntegerLiteral = .u }, |
| 1571 | .{ .IntegerLiteral = .LU }, | 1536 | .{ .IntegerLiteral = .lu }, |
| 1572 | .{ .IntegerLiteral = .LLU }, | 1537 | .{ .IntegerLiteral = .llu }, |
| 1573 | .{ .IntegerLiteral = .None }, | 1538 | .{ .IntegerLiteral = .none }, |
| 1574 | .Nl, | 1539 | .Nl, |
| 1575 | }); | 1540 | }); |
| 1576 | } | 1541 | } |
| 1577 | 1542 | ||
| 1578 | fn expectTokens(source: []const u8, expected_tokens: []const Token.Id) void { | 1543 | fn expectTokens(source: []const u8, expected_tokens: []const Token.Id) void { |
| 1579 | var tokenizer = Tokenizer{ | 1544 | var tokenizer = Tokenizer{ |
| 1580 | .source = &Source{ | 1545 | .buffer = source, |
| 1581 | .buffer = source, | ||
| 1582 | .file_name = undefined, | ||
| 1583 | .tokens = undefined, | ||
| 1584 | }, | ||
| 1585 | }; | 1546 | }; |
| 1586 | for (expected_tokens) |expected_token_id| { | 1547 | for (expected_tokens) |expected_token_id| { |
| 1587 | const token = tokenizer.next(); | 1548 | const token = tokenizer.next(); |
lib/std/child_process.zig+9-13| ... | @@ -364,6 +364,7 @@ pub const ChildProcess = struct { | ... | @@ -364,6 +364,7 @@ pub const ChildProcess = struct { |
| 364 | error.FileTooBig => unreachable, | 364 | error.FileTooBig => unreachable, |
| 365 | error.DeviceBusy => unreachable, | 365 | error.DeviceBusy => unreachable, |
| 366 | error.FileLocksNotSupported => unreachable, | 366 | error.FileLocksNotSupported => unreachable, |
| 367 | error.BadPathName => unreachable, // Windows-only | ||
| 367 | else => |e| return e, | 368 | else => |e| return e, |
| 368 | } | 369 | } |
| 369 | else | 370 | else |
| ... | @@ -480,25 +481,20 @@ pub const ChildProcess = struct { | ... | @@ -480,25 +481,20 @@ pub const ChildProcess = struct { |
| 480 | 481 | ||
| 481 | const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); | 482 | const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); |
| 482 | 483 | ||
| 483 | // TODO use CreateFileW here since we are using a string literal for the path | ||
| 484 | const nul_handle = if (any_ignore) | 484 | const nul_handle = if (any_ignore) |
| 485 | windows.CreateFile( | 485 | windows.OpenFile(&[_]u16{ 'N', 'U', 'L' }, .{ |
| 486 | "NUL", | 486 | .dir = std.fs.cwd().fd, |
| 487 | windows.GENERIC_READ, | 487 | .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, |
| 488 | windows.FILE_SHARE_READ, | 488 | .share_access = windows.FILE_SHARE_READ, |
| 489 | null, | 489 | .creation = windows.OPEN_EXISTING, |
| 490 | windows.OPEN_EXISTING, | 490 | .io_mode = .blocking, |
| 491 | windows.FILE_ATTRIBUTE_NORMAL, | 491 | }) catch |err| switch (err) { |
| 492 | null, | ||
| 493 | ) catch |err| switch (err) { | ||
| 494 | error.SharingViolation => unreachable, // not possible for "NUL" | ||
| 495 | error.PathAlreadyExists => unreachable, // not possible for "NUL" | 492 | error.PathAlreadyExists => unreachable, // not possible for "NUL" |
| 496 | error.PipeBusy => unreachable, // not possible for "NUL" | 493 | error.PipeBusy => unreachable, // not possible for "NUL" |
| 497 | error.InvalidUtf8 => unreachable, // not possible for "NUL" | ||
| 498 | error.BadPathName => unreachable, // not possible for "NUL" | ||
| 499 | error.FileNotFound => unreachable, // not possible for "NUL" | 494 | error.FileNotFound => unreachable, // not possible for "NUL" |
| 500 | error.AccessDenied => unreachable, // not possible for "NUL" | 495 | error.AccessDenied => unreachable, // not possible for "NUL" |
| 501 | error.NameTooLong => unreachable, // not possible for "NUL" | 496 | error.NameTooLong => unreachable, // not possible for "NUL" |
| 497 | error.WouldBlock => unreachable, // not possible for "NUL" | ||
| 502 | else => |e| return e, | 498 | else => |e| return e, |
| 503 | } | 499 | } |
| 504 | else | 500 | else |
lib/std/crypto/blake2.zig+224-27| ... | @@ -1,8 +1,7 @@ | ... | @@ -1,8 +1,7 @@ |
| 1 | const mem = @import("../mem.zig"); | 1 | const mem = @import("../mem.zig"); |
| 2 | const math = @import("../math.zig"); | ||
| 3 | const endian = @import("../endian.zig"); | ||
| 4 | const debug = @import("../debug.zig"); | ||
| 5 | const builtin = @import("builtin"); | 2 | const builtin = @import("builtin"); |
| 3 | const debug = @import("../debug.zig"); | ||
| 4 | const math = @import("../math.zig"); | ||
| 6 | const htest = @import("test.zig"); | 5 | const htest = @import("test.zig"); |
| 7 | 6 | ||
| 8 | const RoundParam = struct { | 7 | const RoundParam = struct { |
| ... | @@ -31,7 +30,7 @@ fn Rp(a: usize, b: usize, c: usize, d: usize, x: usize, y: usize) RoundParam { | ... | @@ -31,7 +30,7 @@ fn Rp(a: usize, b: usize, c: usize, d: usize, x: usize, y: usize) RoundParam { |
| 31 | pub const Blake2s224 = Blake2s(224); | 30 | pub const Blake2s224 = Blake2s(224); |
| 32 | pub const Blake2s256 = Blake2s(256); | 31 | pub const Blake2s256 = Blake2s(256); |
| 33 | 32 | ||
| 34 | fn Blake2s(comptime out_len: usize) type { | 33 | pub fn Blake2s(comptime out_len: usize) type { |
| 35 | return struct { | 34 | return struct { |
| 36 | const Self = @This(); | 35 | const Self = @This(); |
| 37 | pub const block_length = 64; | 36 | pub const block_length = 64; |
| ... | @@ -67,10 +66,17 @@ fn Blake2s(comptime out_len: usize) type { | ... | @@ -67,10 +66,17 @@ fn Blake2s(comptime out_len: usize) type { |
| 67 | buf: [64]u8, | 66 | buf: [64]u8, |
| 68 | buf_len: u8, | 67 | buf_len: u8, |
| 69 | 68 | ||
| 69 | key: []const u8, | ||
| 70 | |||
| 70 | pub fn init() Self { | 71 | pub fn init() Self { |
| 72 | return init_keyed(""); | ||
| 73 | } | ||
| 74 | |||
| 75 | pub fn init_keyed(key: []const u8) Self { | ||
| 71 | debug.assert(8 <= out_len and out_len <= 512); | 76 | debug.assert(8 <= out_len and out_len <= 512); |
| 72 | 77 | ||
| 73 | var s: Self = undefined; | 78 | var s: Self = undefined; |
| 79 | s.key = key; | ||
| 74 | s.reset(); | 80 | s.reset(); |
| 75 | return s; | 81 | return s; |
| 76 | } | 82 | } |
| ... | @@ -78,14 +84,24 @@ fn Blake2s(comptime out_len: usize) type { | ... | @@ -78,14 +84,24 @@ fn Blake2s(comptime out_len: usize) type { |
| 78 | pub fn reset(d: *Self) void { | 84 | pub fn reset(d: *Self) void { |
| 79 | mem.copy(u32, d.h[0..], iv[0..]); | 85 | mem.copy(u32, d.h[0..], iv[0..]); |
| 80 | 86 | ||
| 81 | // No key plus default parameters | 87 | // default parameters |
| 82 | d.h[0] ^= 0x01010000 ^ @intCast(u32, out_len >> 3); | 88 | d.h[0] ^= 0x01010000 ^ @truncate(u32, d.key.len << 8) ^ @intCast(u32, out_len >> 3); |
| 83 | d.t = 0; | 89 | d.t = 0; |
| 84 | d.buf_len = 0; | 90 | d.buf_len = 0; |
| 91 | |||
| 92 | if (d.key.len > 0) { | ||
| 93 | mem.set(u8, d.buf[d.key.len..], 0); | ||
| 94 | d.update(d.key); | ||
| 95 | d.buf_len = 64; | ||
| 96 | } | ||
| 85 | } | 97 | } |
| 86 | 98 | ||
| 87 | pub fn hash(b: []const u8, out: []u8) void { | 99 | pub fn hash(b: []const u8, out: []u8) void { |
| 88 | var d = Self.init(); | 100 | Self.hash_keyed("", b, out); |
| 101 | } | ||
| 102 | |||
| 103 | pub fn hash_keyed(key: []const u8, b: []const u8, out: []u8) void { | ||
| 104 | var d = Self.init_keyed(key); | ||
| 89 | d.update(b); | 105 | d.update(b); |
| 90 | d.final(out); | 106 | d.final(out); |
| 91 | } | 107 | } |
| ... | @@ -94,7 +110,7 @@ fn Blake2s(comptime out_len: usize) type { | ... | @@ -94,7 +110,7 @@ fn Blake2s(comptime out_len: usize) type { |
| 94 | var off: usize = 0; | 110 | var off: usize = 0; |
| 95 | 111 | ||
| 96 | // Partial buffer exists from previous update. Copy into buffer then hash. | 112 | // Partial buffer exists from previous update. Copy into buffer then hash. |
| 97 | if (d.buf_len != 0 and d.buf_len + b.len >= 64) { | 113 | if (d.buf_len != 0 and d.buf_len + b.len > 64) { |
| 98 | off += 64 - d.buf_len; | 114 | off += 64 - d.buf_len; |
| 99 | mem.copy(u8, d.buf[d.buf_len..], b[0..off]); | 115 | mem.copy(u8, d.buf[d.buf_len..], b[0..off]); |
| 100 | d.t += 64; | 116 | d.t += 64; |
| ... | @@ -103,7 +119,7 @@ fn Blake2s(comptime out_len: usize) type { | ... | @@ -103,7 +119,7 @@ fn Blake2s(comptime out_len: usize) type { |
| 103 | } | 119 | } |
| 104 | 120 | ||
| 105 | // Full middle blocks. | 121 | // Full middle blocks. |
| 106 | while (off + 64 <= b.len) : (off += 64) { | 122 | while (off + 64 < b.len) : (off += 64) { |
| 107 | d.t += 64; | 123 | d.t += 64; |
| 108 | d.round(b[off .. off + 64], false); | 124 | d.round(b[off .. off + 64], false); |
| 109 | } | 125 | } |
| ... | @@ -123,7 +139,7 @@ fn Blake2s(comptime out_len: usize) type { | ... | @@ -123,7 +139,7 @@ fn Blake2s(comptime out_len: usize) type { |
| 123 | const rr = d.h[0 .. out_len / 32]; | 139 | const rr = d.h[0 .. out_len / 32]; |
| 124 | 140 | ||
| 125 | for (rr) |s, j| { | 141 | for (rr) |s, j| { |
| 126 | mem.writeIntLittle(u32, out[4 * j ..][0..4], s); | 142 | mem.writeIntSliceLittle(u32, out[4 * j ..], s); |
| 127 | } | 143 | } |
| 128 | } | 144 | } |
| 129 | 145 | ||
| ... | @@ -188,6 +204,9 @@ test "blake2s224 single" { | ... | @@ -188,6 +204,9 @@ test "blake2s224 single" { |
| 188 | 204 | ||
| 189 | const h3 = "e4e5cb6c7cae41982b397bf7b7d2d9d1949823ae78435326e8db4912"; | 205 | const h3 = "e4e5cb6c7cae41982b397bf7b7d2d9d1949823ae78435326e8db4912"; |
| 190 | htest.assertEqualHash(Blake2s224, h3, "The quick brown fox jumps over the lazy dog"); | 206 | htest.assertEqualHash(Blake2s224, h3, "The quick brown fox jumps over the lazy dog"); |
| 207 | |||
| 208 | const h4 = "557381a78facd2b298640f4e32113e58967d61420af1aa939d0cfe01"; | ||
| 209 | htest.assertEqualHash(Blake2s224, h4, "a" ** 32 ++ "b" ** 32); | ||
| 191 | } | 210 | } |
| 192 | 211 | ||
| 193 | test "blake2s224 streaming" { | 212 | test "blake2s224 streaming" { |
| ... | @@ -212,6 +231,37 @@ test "blake2s224 streaming" { | ... | @@ -212,6 +231,37 @@ test "blake2s224 streaming" { |
| 212 | h.update("c"); | 231 | h.update("c"); |
| 213 | h.final(out[0..]); | 232 | h.final(out[0..]); |
| 214 | htest.assertEqual(h2, out[0..]); | 233 | htest.assertEqual(h2, out[0..]); |
| 234 | |||
| 235 | const h3 = "557381a78facd2b298640f4e32113e58967d61420af1aa939d0cfe01"; | ||
| 236 | |||
| 237 | h.reset(); | ||
| 238 | h.update("a" ** 32); | ||
| 239 | h.update("b" ** 32); | ||
| 240 | h.final(out[0..]); | ||
| 241 | htest.assertEqual(h3, out[0..]); | ||
| 242 | |||
| 243 | h.reset(); | ||
| 244 | h.update("a" ** 32 ++ "b" ** 32); | ||
| 245 | h.final(out[0..]); | ||
| 246 | htest.assertEqual(h3, out[0..]); | ||
| 247 | } | ||
| 248 | |||
| 249 | test "comptime blake2s224" { | ||
| 250 | comptime { | ||
| 251 | @setEvalBranchQuota(6000); | ||
| 252 | var block = [_]u8{0} ** Blake2s224.block_length; | ||
| 253 | var out: [Blake2s224.digest_length]u8 = undefined; | ||
| 254 | |||
| 255 | const h1 = "86b7611563293f8c73627df7a6d6ba25ca0548c2a6481f7d116ee576"; | ||
| 256 | |||
| 257 | htest.assertEqualHash(Blake2s224, h1, block[0..]); | ||
| 258 | |||
| 259 | var h = Blake2s224.init(); | ||
| 260 | h.update(&block); | ||
| 261 | h.final(out[0..]); | ||
| 262 | |||
| 263 | htest.assertEqual(h1, out[0..]); | ||
| 264 | } | ||
| 215 | } | 265 | } |
| 216 | 266 | ||
| 217 | test "blake2s256 single" { | 267 | test "blake2s256 single" { |
| ... | @@ -223,6 +273,9 @@ test "blake2s256 single" { | ... | @@ -223,6 +273,9 @@ test "blake2s256 single" { |
| 223 | 273 | ||
| 224 | const h3 = "606beeec743ccbeff6cbcdf5d5302aa855c256c29b88c8ed331ea1a6bf3c8812"; | 274 | const h3 = "606beeec743ccbeff6cbcdf5d5302aa855c256c29b88c8ed331ea1a6bf3c8812"; |
| 225 | htest.assertEqualHash(Blake2s256, h3, "The quick brown fox jumps over the lazy dog"); | 275 | htest.assertEqualHash(Blake2s256, h3, "The quick brown fox jumps over the lazy dog"); |
| 276 | |||
| 277 | const h4 = "8d8711dade07a6b92b9a3ea1f40bee9b2c53ff3edd2a273dec170b0163568977"; | ||
| 278 | htest.assertEqualHash(Blake2s256, h4, "a" ** 32 ++ "b" ** 32); | ||
| 226 | } | 279 | } |
| 227 | 280 | ||
| 228 | test "blake2s256 streaming" { | 281 | test "blake2s256 streaming" { |
| ... | @@ -247,15 +300,60 @@ test "blake2s256 streaming" { | ... | @@ -247,15 +300,60 @@ test "blake2s256 streaming" { |
| 247 | h.update("c"); | 300 | h.update("c"); |
| 248 | h.final(out[0..]); | 301 | h.final(out[0..]); |
| 249 | htest.assertEqual(h2, out[0..]); | 302 | htest.assertEqual(h2, out[0..]); |
| 303 | |||
| 304 | const h3 = "8d8711dade07a6b92b9a3ea1f40bee9b2c53ff3edd2a273dec170b0163568977"; | ||
| 305 | |||
| 306 | h.reset(); | ||
| 307 | h.update("a" ** 32); | ||
| 308 | h.update("b" ** 32); | ||
| 309 | h.final(out[0..]); | ||
| 310 | htest.assertEqual(h3, out[0..]); | ||
| 311 | |||
| 312 | h.reset(); | ||
| 313 | h.update("a" ** 32 ++ "b" ** 32); | ||
| 314 | h.final(out[0..]); | ||
| 315 | htest.assertEqual(h3, out[0..]); | ||
| 250 | } | 316 | } |
| 251 | 317 | ||
| 252 | test "blake2s256 aligned final" { | 318 | test "blake2s256 keyed" { |
| 253 | var block = [_]u8{0} ** Blake2s256.block_length; | 319 | var out: [32]u8 = undefined; |
| 254 | var out: [Blake2s256.digest_length]u8 = undefined; | 320 | |
| 321 | const h1 = "10f918da4d74fab3302e48a5d67d03804b1ec95372a62a0f33b7c9fa28ba1ae6"; | ||
| 322 | const key = "secret_key"; | ||
| 255 | 323 | ||
| 256 | var h = Blake2s256.init(); | 324 | Blake2s256.hash_keyed(key, "a" ** 64 ++ "b" ** 64, &out); |
| 257 | h.update(&block); | 325 | htest.assertEqual(h1, out[0..]); |
| 326 | |||
| 327 | var h = Blake2s256.init_keyed(key); | ||
| 328 | h.update("a" ** 64 ++ "b" ** 64); | ||
| 329 | h.final(out[0..]); | ||
| 330 | |||
| 331 | htest.assertEqual(h1, out[0..]); | ||
| 332 | |||
| 333 | h.reset(); | ||
| 334 | h.update("a" ** 64); | ||
| 335 | h.update("b" ** 64); | ||
| 258 | h.final(out[0..]); | 336 | h.final(out[0..]); |
| 337 | |||
| 338 | htest.assertEqual(h1, out[0..]); | ||
| 339 | } | ||
| 340 | |||
| 341 | test "comptime blake2s256" { | ||
| 342 | comptime { | ||
| 343 | @setEvalBranchQuota(6000); | ||
| 344 | var block = [_]u8{0} ** Blake2s256.block_length; | ||
| 345 | var out: [Blake2s256.digest_length]u8 = undefined; | ||
| 346 | |||
| 347 | const h1 = "ae09db7cd54f42b490ef09b6bc541af688e4959bb8c53f359a6f56e38ab454a3"; | ||
| 348 | |||
| 349 | htest.assertEqualHash(Blake2s256, h1, block[0..]); | ||
| 350 | |||
| 351 | var h = Blake2s256.init(); | ||
| 352 | h.update(&block); | ||
| 353 | h.final(out[0..]); | ||
| 354 | |||
| 355 | htest.assertEqual(h1, out[0..]); | ||
| 356 | } | ||
| 259 | } | 357 | } |
| 260 | 358 | ||
| 261 | ///////////////////// | 359 | ///////////////////// |
| ... | @@ -264,7 +362,7 @@ test "blake2s256 aligned final" { | ... | @@ -264,7 +362,7 @@ test "blake2s256 aligned final" { |
| 264 | pub const Blake2b384 = Blake2b(384); | 362 | pub const Blake2b384 = Blake2b(384); |
| 265 | pub const Blake2b512 = Blake2b(512); | 363 | pub const Blake2b512 = Blake2b(512); |
| 266 | 364 | ||
| 267 | fn Blake2b(comptime out_len: usize) type { | 365 | pub fn Blake2b(comptime out_len: usize) type { |
| 268 | return struct { | 366 | return struct { |
| 269 | const Self = @This(); | 367 | const Self = @This(); |
| 270 | pub const block_length = 128; | 368 | pub const block_length = 128; |
| ... | @@ -302,10 +400,17 @@ fn Blake2b(comptime out_len: usize) type { | ... | @@ -302,10 +400,17 @@ fn Blake2b(comptime out_len: usize) type { |
| 302 | buf: [128]u8, | 400 | buf: [128]u8, |
| 303 | buf_len: u8, | 401 | buf_len: u8, |
| 304 | 402 | ||
| 403 | key: []const u8, | ||
| 404 | |||
| 305 | pub fn init() Self { | 405 | pub fn init() Self { |
| 406 | return init_keyed(""); | ||
| 407 | } | ||
| 408 | |||
| 409 | pub fn init_keyed(key: []const u8) Self { | ||
| 306 | debug.assert(8 <= out_len and out_len <= 512); | 410 | debug.assert(8 <= out_len and out_len <= 512); |
| 307 | 411 | ||
| 308 | var s: Self = undefined; | 412 | var s: Self = undefined; |
| 413 | s.key = key; | ||
| 309 | s.reset(); | 414 | s.reset(); |
| 310 | return s; | 415 | return s; |
| 311 | } | 416 | } |
| ... | @@ -313,14 +418,24 @@ fn Blake2b(comptime out_len: usize) type { | ... | @@ -313,14 +418,24 @@ fn Blake2b(comptime out_len: usize) type { |
| 313 | pub fn reset(d: *Self) void { | 418 | pub fn reset(d: *Self) void { |
| 314 | mem.copy(u64, d.h[0..], iv[0..]); | 419 | mem.copy(u64, d.h[0..], iv[0..]); |
| 315 | 420 | ||
| 316 | // No key plus default parameters | 421 | // default parameters |
| 317 | d.h[0] ^= 0x01010000 ^ (out_len >> 3); | 422 | d.h[0] ^= 0x01010000 ^ (d.key.len << 8) ^ (out_len >> 3); |
| 318 | d.t = 0; | 423 | d.t = 0; |
| 319 | d.buf_len = 0; | 424 | d.buf_len = 0; |
| 425 | |||
| 426 | if (d.key.len > 0) { | ||
| 427 | mem.set(u8, d.buf[d.key.len..], 0); | ||
| 428 | d.update(d.key); | ||
| 429 | d.buf_len = 128; | ||
| 430 | } | ||
| 320 | } | 431 | } |
| 321 | 432 | ||
| 322 | pub fn hash(b: []const u8, out: []u8) void { | 433 | pub fn hash(b: []const u8, out: []u8) void { |
| 323 | var d = Self.init(); | 434 | Self.hash_keyed("", b, out); |
| 435 | } | ||
| 436 | |||
| 437 | pub fn hash_keyed(key: []const u8, b: []const u8, out: []u8) void { | ||
| 438 | var d = Self.init_keyed(key); | ||
| 324 | d.update(b); | 439 | d.update(b); |
| 325 | d.final(out); | 440 | d.final(out); |
| 326 | } | 441 | } |
| ... | @@ -329,7 +444,7 @@ fn Blake2b(comptime out_len: usize) type { | ... | @@ -329,7 +444,7 @@ fn Blake2b(comptime out_len: usize) type { |
| 329 | var off: usize = 0; | 444 | var off: usize = 0; |
| 330 | 445 | ||
| 331 | // Partial buffer exists from previous update. Copy into buffer then hash. | 446 | // Partial buffer exists from previous update. Copy into buffer then hash. |
| 332 | if (d.buf_len != 0 and d.buf_len + b.len >= 128) { | 447 | if (d.buf_len != 0 and d.buf_len + b.len > 128) { |
| 333 | off += 128 - d.buf_len; | 448 | off += 128 - d.buf_len; |
| 334 | mem.copy(u8, d.buf[d.buf_len..], b[0..off]); | 449 | mem.copy(u8, d.buf[d.buf_len..], b[0..off]); |
| 335 | d.t += 128; | 450 | d.t += 128; |
| ... | @@ -338,7 +453,7 @@ fn Blake2b(comptime out_len: usize) type { | ... | @@ -338,7 +453,7 @@ fn Blake2b(comptime out_len: usize) type { |
| 338 | } | 453 | } |
| 339 | 454 | ||
| 340 | // Full middle blocks. | 455 | // Full middle blocks. |
| 341 | while (off + 128 <= b.len) : (off += 128) { | 456 | while (off + 128 < b.len) : (off += 128) { |
| 342 | d.t += 128; | 457 | d.t += 128; |
| 343 | d.round(b[off .. off + 128], false); | 458 | d.round(b[off .. off + 128], false); |
| 344 | } | 459 | } |
| ... | @@ -356,7 +471,7 @@ fn Blake2b(comptime out_len: usize) type { | ... | @@ -356,7 +471,7 @@ fn Blake2b(comptime out_len: usize) type { |
| 356 | const rr = d.h[0 .. out_len / 64]; | 471 | const rr = d.h[0 .. out_len / 64]; |
| 357 | 472 | ||
| 358 | for (rr) |s, j| { | 473 | for (rr) |s, j| { |
| 359 | mem.writeIntLittle(u64, out[8 * j ..][0..8], s); | 474 | mem.writeIntSliceLittle(u64, out[8 * j ..], s); |
| 360 | } | 475 | } |
| 361 | } | 476 | } |
| 362 | 477 | ||
| ... | @@ -421,6 +536,9 @@ test "blake2b384 single" { | ... | @@ -421,6 +536,9 @@ test "blake2b384 single" { |
| 421 | 536 | ||
| 422 | const h3 = "b7c81b228b6bd912930e8f0b5387989691c1cee1e65aade4da3b86a3c9f678fc8018f6ed9e2906720c8d2a3aeda9c03d"; | 537 | const h3 = "b7c81b228b6bd912930e8f0b5387989691c1cee1e65aade4da3b86a3c9f678fc8018f6ed9e2906720c8d2a3aeda9c03d"; |
| 423 | htest.assertEqualHash(Blake2b384, h3, "The quick brown fox jumps over the lazy dog"); | 538 | htest.assertEqualHash(Blake2b384, h3, "The quick brown fox jumps over the lazy dog"); |
| 539 | |||
| 540 | const h4 = "b7283f0172fecbbd7eca32ce10d8a6c06b453cb3cf675b33eb4246f0da2bb94a6c0bdd6eec0b5fd71ec4fd51be80bf4c"; | ||
| 541 | htest.assertEqualHash(Blake2b384, h4, "a" ** 64 ++ "b" ** 64); | ||
| 424 | } | 542 | } |
| 425 | 543 | ||
| 426 | test "blake2b384 streaming" { | 544 | test "blake2b384 streaming" { |
| ... | @@ -445,6 +563,37 @@ test "blake2b384 streaming" { | ... | @@ -445,6 +563,37 @@ test "blake2b384 streaming" { |
| 445 | h.update("c"); | 563 | h.update("c"); |
| 446 | h.final(out[0..]); | 564 | h.final(out[0..]); |
| 447 | htest.assertEqual(h2, out[0..]); | 565 | htest.assertEqual(h2, out[0..]); |
| 566 | |||
| 567 | const h3 = "b7283f0172fecbbd7eca32ce10d8a6c06b453cb3cf675b33eb4246f0da2bb94a6c0bdd6eec0b5fd71ec4fd51be80bf4c"; | ||
| 568 | |||
| 569 | h.reset(); | ||
| 570 | h.update("a" ** 64 ++ "b" ** 64); | ||
| 571 | h.final(out[0..]); | ||
| 572 | htest.assertEqual(h3, out[0..]); | ||
| 573 | |||
| 574 | h.reset(); | ||
| 575 | h.update("a" ** 64); | ||
| 576 | h.update("b" ** 64); | ||
| 577 | h.final(out[0..]); | ||
| 578 | htest.assertEqual(h3, out[0..]); | ||
| 579 | } | ||
| 580 | |||
| 581 | test "comptime blake2b384" { | ||
| 582 | comptime { | ||
| 583 | @setEvalBranchQuota(7000); | ||
| 584 | var block = [_]u8{0} ** Blake2b384.block_length; | ||
| 585 | var out: [Blake2b384.digest_length]u8 = undefined; | ||
| 586 | |||
| 587 | const h1 = "e8aa1931ea0422e4446fecdd25c16cf35c240b10cb4659dd5c776eddcaa4d922397a589404b46eb2e53d78132d05fd7d"; | ||
| 588 | |||
| 589 | htest.assertEqualHash(Blake2b384, h1, block[0..]); | ||
| 590 | |||
| 591 | var h = Blake2b384.init(); | ||
| 592 | h.update(&block); | ||
| 593 | h.final(out[0..]); | ||
| 594 | |||
| 595 | htest.assertEqual(h1, out[0..]); | ||
| 596 | } | ||
| 448 | } | 597 | } |
| 449 | 598 | ||
| 450 | test "blake2b512 single" { | 599 | test "blake2b512 single" { |
| ... | @@ -456,6 +605,9 @@ test "blake2b512 single" { | ... | @@ -456,6 +605,9 @@ test "blake2b512 single" { |
| 456 | 605 | ||
| 457 | const h3 = "a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918"; | 606 | const h3 = "a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918"; |
| 458 | htest.assertEqualHash(Blake2b512, h3, "The quick brown fox jumps over the lazy dog"); | 607 | htest.assertEqualHash(Blake2b512, h3, "The quick brown fox jumps over the lazy dog"); |
| 608 | |||
| 609 | const h4 = "049980af04d6a2cf16b4b49793c3ed7e40732073788806f2c989ebe9547bda0541d63abe298ec8955d08af48ae731f2e8a0bd6d201655a5473b4aa79d211b920"; | ||
| 610 | htest.assertEqualHash(Blake2b512, h4, "a" ** 64 ++ "b" ** 64); | ||
| 459 | } | 611 | } |
| 460 | 612 | ||
| 461 | test "blake2b512 streaming" { | 613 | test "blake2b512 streaming" { |
| ... | @@ -480,13 +632,58 @@ test "blake2b512 streaming" { | ... | @@ -480,13 +632,58 @@ test "blake2b512 streaming" { |
| 480 | h.update("c"); | 632 | h.update("c"); |
| 481 | h.final(out[0..]); | 633 | h.final(out[0..]); |
| 482 | htest.assertEqual(h2, out[0..]); | 634 | htest.assertEqual(h2, out[0..]); |
| 635 | |||
| 636 | const h3 = "049980af04d6a2cf16b4b49793c3ed7e40732073788806f2c989ebe9547bda0541d63abe298ec8955d08af48ae731f2e8a0bd6d201655a5473b4aa79d211b920"; | ||
| 637 | |||
| 638 | h.reset(); | ||
| 639 | h.update("a" ** 64 ++ "b" ** 64); | ||
| 640 | h.final(out[0..]); | ||
| 641 | htest.assertEqual(h3, out[0..]); | ||
| 642 | |||
| 643 | h.reset(); | ||
| 644 | h.update("a" ** 64); | ||
| 645 | h.update("b" ** 64); | ||
| 646 | h.final(out[0..]); | ||
| 647 | htest.assertEqual(h3, out[0..]); | ||
| 483 | } | 648 | } |
| 484 | 649 | ||
| 485 | test "blake2b512 aligned final" { | 650 | test "blake2b512 keyed" { |
| 486 | var block = [_]u8{0} ** Blake2b512.block_length; | 651 | var out: [64]u8 = undefined; |
| 487 | var out: [Blake2b512.digest_length]u8 = undefined; | ||
| 488 | 652 | ||
| 489 | var h = Blake2b512.init(); | 653 | const h1 = "8a978060ccaf582f388f37454363071ac9a67e3a704585fd879fb8a419a447e389c7c6de790faa20a7a7dccf197de736bc5b40b98a930b36df5bee7555750c4d"; |
| 490 | h.update(&block); | 654 | const key = "secret_key"; |
| 655 | |||
| 656 | Blake2b512.hash_keyed(key, "a" ** 64 ++ "b" ** 64, &out); | ||
| 657 | htest.assertEqual(h1, out[0..]); | ||
| 658 | |||
| 659 | var h = Blake2b512.init_keyed(key); | ||
| 660 | h.update("a" ** 64 ++ "b" ** 64); | ||
| 661 | h.final(out[0..]); | ||
| 662 | |||
| 663 | htest.assertEqual(h1, out[0..]); | ||
| 664 | |||
| 665 | h.reset(); | ||
| 666 | h.update("a" ** 64); | ||
| 667 | h.update("b" ** 64); | ||
| 491 | h.final(out[0..]); | 668 | h.final(out[0..]); |
| 669 | |||
| 670 | htest.assertEqual(h1, out[0..]); | ||
| 671 | } | ||
| 672 | |||
| 673 | test "comptime blake2b512" { | ||
| 674 | comptime { | ||
| 675 | @setEvalBranchQuota(8000); | ||
| 676 | var block = [_]u8{0} ** Blake2b512.block_length; | ||
| 677 | var out: [Blake2b512.digest_length]u8 = undefined; | ||
| 678 | |||
| 679 | const h1 = "865939e120e6805438478841afb739ae4250cf372653078a065cdcfffca4caf798e6d462b65d658fc165782640eded70963449ae1500fb0f24981d7727e22c41"; | ||
| 680 | |||
| 681 | htest.assertEqualHash(Blake2b512, h1, block[0..]); | ||
| 682 | |||
| 683 | var h = Blake2b512.init(); | ||
| 684 | h.update(&block); | ||
| 685 | h.final(out[0..]); | ||
| 686 | |||
| 687 | htest.assertEqual(h1, out[0..]); | ||
| 688 | } | ||
| 492 | } | 689 | } |
lib/std/debug/leb128.zig+44| ... | @@ -159,6 +159,50 @@ pub fn writeILEB128Mem(ptr: []u8, int_value: anytype) !usize { | ... | @@ -159,6 +159,50 @@ pub fn writeILEB128Mem(ptr: []u8, int_value: anytype) !usize { |
| 159 | return buf.pos; | 159 | return buf.pos; |
| 160 | } | 160 | } |
| 161 | 161 | ||
| 162 | /// This is an "advanced" function. It allows one to use a fixed amount of memory to store a | ||
| 163 | /// ULEB128. This defeats the entire purpose of using this data encoding; it will no longer use | ||
| 164 | /// fewer bytes to store smaller numbers. The advantage of using a fixed width is that it makes | ||
| 165 | /// fields have a predictable size and so depending on the use case this tradeoff can be worthwhile. | ||
| 166 | /// An example use case of this is in emitting DWARF info where one wants to make a ULEB128 field | ||
| 167 | /// "relocatable", meaning that it becomes possible to later go back and patch the number to be a | ||
| 168 | /// different value without shifting all the following code. | ||
| 169 | pub fn writeUnsignedFixed(comptime l: usize, ptr: *[l]u8, int: std.meta.Int(false, l * 7)) void { | ||
| 170 | const T = @TypeOf(int); | ||
| 171 | const U = if (T.bit_count < 8) u8 else T; | ||
| 172 | var value = @intCast(U, int); | ||
| 173 | |||
| 174 | comptime var i = 0; | ||
| 175 | inline while (i < (l - 1)) : (i += 1) { | ||
| 176 | const byte = @truncate(u8, value) | 0b1000_0000; | ||
| 177 | value >>= 7; | ||
| 178 | ptr[i] = byte; | ||
| 179 | } | ||
| 180 | ptr[i] = @truncate(u8, value); | ||
| 181 | } | ||
| 182 | |||
| 183 | test "writeUnsignedFixed" { | ||
| 184 | { | ||
| 185 | var buf: [4]u8 = undefined; | ||
| 186 | writeUnsignedFixed(4, &buf, 0); | ||
| 187 | testing.expect((try test_read_uleb128(u64, &buf)) == 0); | ||
| 188 | } | ||
| 189 | { | ||
| 190 | var buf: [4]u8 = undefined; | ||
| 191 | writeUnsignedFixed(4, &buf, 1); | ||
| 192 | testing.expect((try test_read_uleb128(u64, &buf)) == 1); | ||
| 193 | } | ||
| 194 | { | ||
| 195 | var buf: [4]u8 = undefined; | ||
| 196 | writeUnsignedFixed(4, &buf, 1000); | ||
| 197 | testing.expect((try test_read_uleb128(u64, &buf)) == 1000); | ||
| 198 | } | ||
| 199 | { | ||
| 200 | var buf: [4]u8 = undefined; | ||
| 201 | writeUnsignedFixed(4, &buf, 10000000); | ||
| 202 | testing.expect((try test_read_uleb128(u64, &buf)) == 10000000); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 162 | // tests | 206 | // tests |
| 163 | fn test_read_stream_ileb128(comptime T: type, encoded: []const u8) !T { | 207 | fn test_read_stream_ileb128(comptime T: type, encoded: []const u8) !T { |
| 164 | var reader = std.io.fixedBufferStream(encoded); | 208 | var reader = std.io.fixedBufferStream(encoded); |
lib/std/dwarf.zig+1-1| ... | @@ -9,7 +9,7 @@ const leb = @import("debug/leb128.zig"); | ... | @@ -9,7 +9,7 @@ const leb = @import("debug/leb128.zig"); |
| 9 | 9 | ||
| 10 | const ArrayList = std.ArrayList; | 10 | const ArrayList = std.ArrayList; |
| 11 | 11 | ||
| 12 | usingnamespace @import("dwarf_bits.zig"); | 12 | pub usingnamespace @import("dwarf_bits.zig"); |
| 13 | 13 | ||
| 14 | const PcRange = struct { | 14 | const PcRange = struct { |
| 15 | start: u64, | 15 | start: u64, |
lib/std/dwarf_bits.zig+17| ... | @@ -680,3 +680,20 @@ pub const LANG_HP_Basic91 = 0x8004; | ... | @@ -680,3 +680,20 @@ pub const LANG_HP_Basic91 = 0x8004; |
| 680 | pub const LANG_HP_Pascal91 = 0x8005; | 680 | pub const LANG_HP_Pascal91 = 0x8005; |
| 681 | pub const LANG_HP_IMacro = 0x8006; | 681 | pub const LANG_HP_IMacro = 0x8006; |
| 682 | pub const LANG_HP_Assembler = 0x8007; | 682 | pub const LANG_HP_Assembler = 0x8007; |
| 683 | |||
| 684 | pub const UT_compile = 0x01; | ||
| 685 | pub const UT_type = 0x02; | ||
| 686 | pub const UT_partial = 0x03; | ||
| 687 | pub const UT_skeleton = 0x04; | ||
| 688 | pub const UT_split_compile = 0x05; | ||
| 689 | pub const UT_split_type = 0x06; | ||
| 690 | pub const UT_lo_user = 0x80; | ||
| 691 | pub const UT_hi_user = 0xff; | ||
| 692 | |||
| 693 | pub const LNCT_path = 0x1; | ||
| 694 | pub const LNCT_directory_index = 0x2; | ||
| 695 | pub const LNCT_timestamp = 0x3; | ||
| 696 | pub const LNCT_size = 0x4; | ||
| 697 | pub const LNCT_MD5 = 0x5; | ||
| 698 | pub const LNCT_lo_user = 0x2000; | ||
| 699 | pub const LNCT_hi_user = 0x3fff; |
lib/std/elf.zig+136-129| ... | @@ -341,6 +341,20 @@ const Header = struct { | ... | @@ -341,6 +341,20 @@ const Header = struct { |
| 341 | shentsize: u16, | 341 | shentsize: u16, |
| 342 | shnum: u16, | 342 | shnum: u16, |
| 343 | shstrndx: u16, | 343 | shstrndx: u16, |
| 344 | |||
| 345 | pub fn program_header_iterator(self: Header, file: File) ProgramHeaderIterator { | ||
| 346 | return .{ | ||
| 347 | .elf_header = self, | ||
| 348 | .file = file, | ||
| 349 | }; | ||
| 350 | } | ||
| 351 | |||
| 352 | pub fn section_header_iterator(self: Header, file: File) SectionHeaderIterator { | ||
| 353 | return .{ | ||
| 354 | .elf_header = self, | ||
| 355 | .file = file, | ||
| 356 | }; | ||
| 357 | } | ||
| 344 | }; | 358 | }; |
| 345 | 359 | ||
| 346 | pub fn readHeader(file: File) !Header { | 360 | pub fn readHeader(file: File) !Header { |
| ... | @@ -378,144 +392,137 @@ pub fn readHeader(file: File) !Header { | ... | @@ -378,144 +392,137 @@ pub fn readHeader(file: File) !Header { |
| 378 | }); | 392 | }); |
| 379 | } | 393 | } |
| 380 | 394 | ||
| 381 | /// All integers are native endian. | 395 | pub const ProgramHeaderIterator = struct { |
| 382 | pub const AllHeaders = struct { | 396 | elf_header: Header, |
| 383 | header: Header, | 397 | file: File, |
| 384 | section_headers: []Elf64_Shdr, | 398 | index: usize = 0, |
| 385 | program_headers: []Elf64_Phdr, | 399 | |
| 386 | allocator: *mem.Allocator, | 400 | pub fn next(self: *ProgramHeaderIterator) !?Elf64_Phdr { |
| 387 | }; | 401 | if (self.index >= self.elf_header.phnum) return null; |
| 388 | 402 | defer self.index += 1; | |
| 389 | pub fn readAllHeaders(allocator: *mem.Allocator, file: File) !AllHeaders { | 403 | |
| 390 | var hdrs: AllHeaders = .{ | 404 | if (self.elf_header.is_64) { |
| 391 | .allocator = allocator, | 405 | var phdr: Elf64_Phdr = undefined; |
| 392 | .header = try readHeader(file), | 406 | const offset = self.elf_header.phoff + @sizeOf(@TypeOf(phdr)) * self.index; |
| 393 | .section_headers = undefined, | 407 | try preadNoEof(self.file, mem.asBytes(&phdr), offset); |
| 394 | .program_headers = undefined, | 408 | |
| 395 | }; | 409 | // ELF endianness matches native endianness. |
| 396 | const is_64 = hdrs.header.is_64; | 410 | if (self.elf_header.endian == std.builtin.endian) return phdr; |
| 397 | const need_bswap = hdrs.header.endian != std.builtin.endian; | 411 | |
| 398 | 412 | // Convert fields to native endianness. | |
| 399 | hdrs.section_headers = try allocator.alloc(Elf64_Shdr, hdrs.header.shnum); | 413 | return Elf64_Phdr{ |
| 400 | errdefer allocator.free(hdrs.section_headers); | 414 | .p_type = @byteSwap(@TypeOf(phdr.p_type), phdr.p_type), |
| 401 | 415 | .p_offset = @byteSwap(@TypeOf(phdr.p_offset), phdr.p_offset), | |
| 402 | hdrs.program_headers = try allocator.alloc(Elf64_Phdr, hdrs.header.phnum); | 416 | .p_vaddr = @byteSwap(@TypeOf(phdr.p_vaddr), phdr.p_vaddr), |
| 403 | errdefer allocator.free(hdrs.program_headers); | 417 | .p_paddr = @byteSwap(@TypeOf(phdr.p_paddr), phdr.p_paddr), |
| 404 | 418 | .p_filesz = @byteSwap(@TypeOf(phdr.p_filesz), phdr.p_filesz), | |
| 405 | // If the ELF file is 64-bit and same-endianness, then all we have to do is | 419 | .p_memsz = @byteSwap(@TypeOf(phdr.p_memsz), phdr.p_memsz), |
| 406 | // yeet the bytes into memory. | 420 | .p_flags = @byteSwap(@TypeOf(phdr.p_flags), phdr.p_flags), |
| 407 | // If only the endianness is different, they can be simply byte swapped. | 421 | .p_align = @byteSwap(@TypeOf(phdr.p_align), phdr.p_align), |
| 408 | if (is_64) { | 422 | }; |
| 409 | const shdr_buf = std.mem.sliceAsBytes(hdrs.section_headers); | 423 | } |
| 410 | const phdr_buf = std.mem.sliceAsBytes(hdrs.program_headers); | ||
| 411 | try preadNoEof(file, shdr_buf, hdrs.header.shoff); | ||
| 412 | try preadNoEof(file, phdr_buf, hdrs.header.phoff); | ||
| 413 | 424 | ||
| 414 | if (need_bswap) { | 425 | var phdr: Elf32_Phdr = undefined; |
| 415 | for (hdrs.section_headers) |*shdr| { | 426 | const offset = self.elf_header.phoff + @sizeOf(@TypeOf(phdr)) * self.index; |
| 416 | shdr.* = .{ | 427 | try preadNoEof(self.file, mem.asBytes(&phdr), offset); |
| 417 | .sh_name = @byteSwap(@TypeOf(shdr.sh_name), shdr.sh_name), | 428 | |
| 418 | .sh_type = @byteSwap(@TypeOf(shdr.sh_type), shdr.sh_type), | 429 | // ELF endianness does NOT match native endianness. |
| 419 | .sh_flags = @byteSwap(@TypeOf(shdr.sh_flags), shdr.sh_flags), | 430 | if (self.elf_header.endian != std.builtin.endian) { |
| 420 | .sh_addr = @byteSwap(@TypeOf(shdr.sh_addr), shdr.sh_addr), | 431 | // Convert fields to native endianness. |
| 421 | .sh_offset = @byteSwap(@TypeOf(shdr.sh_offset), shdr.sh_offset), | 432 | phdr = .{ |
| 422 | .sh_size = @byteSwap(@TypeOf(shdr.sh_size), shdr.sh_size), | 433 | .p_type = @byteSwap(@TypeOf(phdr.p_type), phdr.p_type), |
| 423 | .sh_link = @byteSwap(@TypeOf(shdr.sh_link), shdr.sh_link), | 434 | .p_offset = @byteSwap(@TypeOf(phdr.p_offset), phdr.p_offset), |
| 424 | .sh_info = @byteSwap(@TypeOf(shdr.sh_info), shdr.sh_info), | 435 | .p_vaddr = @byteSwap(@TypeOf(phdr.p_vaddr), phdr.p_vaddr), |
| 425 | .sh_addralign = @byteSwap(@TypeOf(shdr.sh_addralign), shdr.sh_addralign), | 436 | .p_paddr = @byteSwap(@TypeOf(phdr.p_paddr), phdr.p_paddr), |
| 426 | .sh_entsize = @byteSwap(@TypeOf(shdr.sh_entsize), shdr.sh_entsize), | 437 | .p_filesz = @byteSwap(@TypeOf(phdr.p_filesz), phdr.p_filesz), |
| 427 | }; | 438 | .p_memsz = @byteSwap(@TypeOf(phdr.p_memsz), phdr.p_memsz), |
| 428 | } | 439 | .p_flags = @byteSwap(@TypeOf(phdr.p_flags), phdr.p_flags), |
| 429 | for (hdrs.program_headers) |*phdr| { | 440 | .p_align = @byteSwap(@TypeOf(phdr.p_align), phdr.p_align), |
| 430 | phdr.* = .{ | 441 | }; |
| 431 | .p_type = @byteSwap(@TypeOf(phdr.p_type), phdr.p_type), | ||
| 432 | .p_offset = @byteSwap(@TypeOf(phdr.p_offset), phdr.p_offset), | ||
| 433 | .p_vaddr = @byteSwap(@TypeOf(phdr.p_vaddr), phdr.p_vaddr), | ||
| 434 | .p_paddr = @byteSwap(@TypeOf(phdr.p_paddr), phdr.p_paddr), | ||
| 435 | .p_filesz = @byteSwap(@TypeOf(phdr.p_filesz), phdr.p_filesz), | ||
| 436 | .p_memsz = @byteSwap(@TypeOf(phdr.p_memsz), phdr.p_memsz), | ||
| 437 | .p_flags = @byteSwap(@TypeOf(phdr.p_flags), phdr.p_flags), | ||
| 438 | .p_align = @byteSwap(@TypeOf(phdr.p_align), phdr.p_align), | ||
| 439 | }; | ||
| 440 | } | ||
| 441 | } | 442 | } |
| 442 | 443 | ||
| 443 | return hdrs; | 444 | // Convert 32-bit header to 64-bit. |
| 445 | return Elf64_Phdr{ | ||
| 446 | .p_type = phdr.p_type, | ||
| 447 | .p_offset = phdr.p_offset, | ||
| 448 | .p_vaddr = phdr.p_vaddr, | ||
| 449 | .p_paddr = phdr.p_paddr, | ||
| 450 | .p_filesz = phdr.p_filesz, | ||
| 451 | .p_memsz = phdr.p_memsz, | ||
| 452 | .p_flags = phdr.p_flags, | ||
| 453 | .p_align = phdr.p_align, | ||
| 454 | }; | ||
| 444 | } | 455 | } |
| 456 | }; | ||
| 445 | 457 | ||
| 446 | const shdrs_32 = try allocator.alloc(Elf32_Shdr, hdrs.header.shnum); | 458 | pub const SectionHeaderIterator = struct { |
| 447 | defer allocator.free(shdrs_32); | 459 | elf_header: Header, |
| 448 | 460 | file: File, | |
| 449 | const phdrs_32 = try allocator.alloc(Elf32_Phdr, hdrs.header.phnum); | 461 | index: usize = 0, |
| 450 | defer allocator.free(phdrs_32); | 462 | |
| 451 | 463 | pub fn next(self: *SectionHeaderIterator) !?Elf64_Shdr { | |
| 452 | const shdr_buf = std.mem.sliceAsBytes(shdrs_32); | 464 | if (self.index >= self.elf_header.shnum) return null; |
| 453 | const phdr_buf = std.mem.sliceAsBytes(phdrs_32); | 465 | defer self.index += 1; |
| 454 | try preadNoEof(file, shdr_buf, hdrs.header.shoff); | 466 | |
| 455 | try preadNoEof(file, phdr_buf, hdrs.header.phoff); | 467 | if (self.elf_header.is_64) { |
| 456 | 468 | var shdr: Elf64_Shdr = undefined; | |
| 457 | if (need_bswap) { | 469 | const offset = self.elf_header.phoff + @sizeOf(@TypeOf(shdr)) * self.index; |
| 458 | for (hdrs.section_headers) |*shdr, i| { | 470 | try preadNoEof(self.file, mem.asBytes(&shdr), offset); |
| 459 | const o = shdrs_32[i]; | 471 | |
| 460 | shdr.* = .{ | 472 | // ELF endianness matches native endianness. |
| 461 | .sh_name = @byteSwap(@TypeOf(o.sh_name), o.sh_name), | 473 | if (self.elf_header.endian == std.builtin.endian) return shdr; |
| 462 | .sh_type = @byteSwap(@TypeOf(o.sh_type), o.sh_type), | 474 | |
| 463 | .sh_flags = @byteSwap(@TypeOf(o.sh_flags), o.sh_flags), | 475 | // Convert fields to native endianness. |
| 464 | .sh_addr = @byteSwap(@TypeOf(o.sh_addr), o.sh_addr), | 476 | return Elf64_Shdr{ |
| 465 | .sh_offset = @byteSwap(@TypeOf(o.sh_offset), o.sh_offset), | 477 | .sh_name = @byteSwap(@TypeOf(shdr.sh_name), shdr.sh_name), |
| 466 | .sh_size = @byteSwap(@TypeOf(o.sh_size), o.sh_size), | 478 | .sh_type = @byteSwap(@TypeOf(shdr.sh_type), shdr.sh_type), |
| 467 | .sh_link = @byteSwap(@TypeOf(o.sh_link), o.sh_link), | 479 | .sh_flags = @byteSwap(@TypeOf(shdr.sh_flags), shdr.sh_flags), |
| 468 | .sh_info = @byteSwap(@TypeOf(o.sh_info), o.sh_info), | 480 | .sh_addr = @byteSwap(@TypeOf(shdr.sh_addr), shdr.sh_addr), |
| 469 | .sh_addralign = @byteSwap(@TypeOf(o.sh_addralign), o.sh_addralign), | 481 | .sh_offset = @byteSwap(@TypeOf(shdr.sh_offset), shdr.sh_offset), |
| 470 | .sh_entsize = @byteSwap(@TypeOf(o.sh_entsize), o.sh_entsize), | 482 | .sh_size = @byteSwap(@TypeOf(shdr.sh_size), shdr.sh_size), |
| 471 | }; | 483 | .sh_link = @byteSwap(@TypeOf(shdr.sh_link), shdr.sh_link), |
| 472 | } | 484 | .sh_info = @byteSwap(@TypeOf(shdr.sh_info), shdr.sh_info), |
| 473 | for (hdrs.program_headers) |*phdr, i| { | 485 | .sh_addralign = @byteSwap(@TypeOf(shdr.sh_addralign), shdr.sh_addralign), |
| 474 | const o = phdrs_32[i]; | 486 | .sh_entsize = @byteSwap(@TypeOf(shdr.sh_entsize), shdr.sh_entsize), |
| 475 | phdr.* = .{ | ||
| 476 | .p_type = @byteSwap(@TypeOf(o.p_type), o.p_type), | ||
| 477 | .p_offset = @byteSwap(@TypeOf(o.p_offset), o.p_offset), | ||
| 478 | .p_vaddr = @byteSwap(@TypeOf(o.p_vaddr), o.p_vaddr), | ||
| 479 | .p_paddr = @byteSwap(@TypeOf(o.p_paddr), o.p_paddr), | ||
| 480 | .p_filesz = @byteSwap(@TypeOf(o.p_filesz), o.p_filesz), | ||
| 481 | .p_memsz = @byteSwap(@TypeOf(o.p_memsz), o.p_memsz), | ||
| 482 | .p_flags = @byteSwap(@TypeOf(o.p_flags), o.p_flags), | ||
| 483 | .p_align = @byteSwap(@TypeOf(o.p_align), o.p_align), | ||
| 484 | }; | 487 | }; |
| 485 | } | 488 | } |
| 486 | } else { | 489 | |
| 487 | for (hdrs.section_headers) |*shdr, i| { | 490 | var shdr: Elf32_Shdr = undefined; |
| 488 | const o = shdrs_32[i]; | 491 | const offset = self.elf_header.shoff + @sizeOf(@TypeOf(shdr)) * self.index; |
| 489 | shdr.* = .{ | 492 | try preadNoEof(self.file, mem.asBytes(&shdr), offset); |
| 490 | .sh_name = o.sh_name, | 493 | |
| 491 | .sh_type = o.sh_type, | 494 | // ELF endianness does NOT match native endianness. |
| 492 | .sh_flags = o.sh_flags, | 495 | if (self.elf_header.endian != std.builtin.endian) { |
| 493 | .sh_addr = o.sh_addr, | 496 | // Convert fields to native endianness. |
| 494 | .sh_offset = o.sh_offset, | 497 | shdr = .{ |
| 495 | .sh_size = o.sh_size, | 498 | .sh_name = @byteSwap(@TypeOf(shdr.sh_name), shdr.sh_name), |
| 496 | .sh_link = o.sh_link, | 499 | .sh_type = @byteSwap(@TypeOf(shdr.sh_type), shdr.sh_type), |
| 497 | .sh_info = o.sh_info, | 500 | .sh_flags = @byteSwap(@TypeOf(shdr.sh_flags), shdr.sh_flags), |
| 498 | .sh_addralign = o.sh_addralign, | 501 | .sh_addr = @byteSwap(@TypeOf(shdr.sh_addr), shdr.sh_addr), |
| 499 | .sh_entsize = o.sh_entsize, | 502 | .sh_offset = @byteSwap(@TypeOf(shdr.sh_offset), shdr.sh_offset), |
| 500 | }; | 503 | .sh_size = @byteSwap(@TypeOf(shdr.sh_size), shdr.sh_size), |
| 501 | } | 504 | .sh_link = @byteSwap(@TypeOf(shdr.sh_link), shdr.sh_link), |
| 502 | for (hdrs.program_headers) |*phdr, i| { | 505 | .sh_info = @byteSwap(@TypeOf(shdr.sh_info), shdr.sh_info), |
| 503 | const o = phdrs_32[i]; | 506 | .sh_addralign = @byteSwap(@TypeOf(shdr.sh_addralign), shdr.sh_addralign), |
| 504 | phdr.* = .{ | 507 | .sh_entsize = @byteSwap(@TypeOf(shdr.sh_entsize), shdr.sh_entsize), |
| 505 | .p_type = o.p_type, | ||
| 506 | .p_offset = o.p_offset, | ||
| 507 | .p_vaddr = o.p_vaddr, | ||
| 508 | .p_paddr = o.p_paddr, | ||
| 509 | .p_filesz = o.p_filesz, | ||
| 510 | .p_memsz = o.p_memsz, | ||
| 511 | .p_flags = o.p_flags, | ||
| 512 | .p_align = o.p_align, | ||
| 513 | }; | 508 | }; |
| 514 | } | 509 | } |
| 515 | } | ||
| 516 | 510 | ||
| 517 | return hdrs; | 511 | // Convert 32-bit header to 64-bit. |
| 518 | } | 512 | return Elf64_Shdr{ |
| 513 | .sh_name = shdr.sh_name, | ||
| 514 | .sh_type = shdr.sh_type, | ||
| 515 | .sh_flags = shdr.sh_flags, | ||
| 516 | .sh_addr = shdr.sh_addr, | ||
| 517 | .sh_offset = shdr.sh_offset, | ||
| 518 | .sh_size = shdr.sh_size, | ||
| 519 | .sh_link = shdr.sh_link, | ||
| 520 | .sh_info = shdr.sh_info, | ||
| 521 | .sh_addralign = shdr.sh_addralign, | ||
| 522 | .sh_entsize = shdr.sh_entsize, | ||
| 523 | }; | ||
| 524 | } | ||
| 525 | }; | ||
| 519 | 526 | ||
| 520 | pub fn int(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) { | 527 | pub fn int(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) { |
| 521 | if (is_64) { | 528 | if (is_64) { |
| ... | @@ -538,7 +545,7 @@ pub fn int32(need_bswap: bool, int_32: anytype, comptime Int64: anytype) Int64 { | ... | @@ -538,7 +545,7 @@ pub fn int32(need_bswap: bool, int_32: anytype, comptime Int64: anytype) Int64 { |
| 538 | } | 545 | } |
| 539 | 546 | ||
| 540 | fn preadNoEof(file: std.fs.File, buf: []u8, offset: u64) !void { | 547 | fn preadNoEof(file: std.fs.File, buf: []u8, offset: u64) !void { |
| 541 | var i: u64 = 0; | 548 | var i: usize = 0; |
| 542 | while (i < buf.len) { | 549 | while (i < buf.len) { |
| 543 | const len = file.pread(buf[i .. buf.len - i], offset + i) catch |err| switch (err) { | 550 | const len = file.pread(buf[i .. buf.len - i], offset + i) catch |err| switch (err) { |
| 544 | error.SystemResources => return error.SystemResources, | 551 | error.SystemResources => return error.SystemResources, |
lib/std/fifo.zig+8-2| ... | @@ -224,6 +224,7 @@ pub fn LinearFifo( | ... | @@ -224,6 +224,7 @@ pub fn LinearFifo( |
| 224 | pub fn reader(self: *Self) std.io.Reader(*Self, error{}, readFn) { | 224 | pub fn reader(self: *Self) std.io.Reader(*Self, error{}, readFn) { |
| 225 | return .{ .context = self }; | 225 | return .{ .context = self }; |
| 226 | } | 226 | } |
| 227 | |||
| 227 | /// Deprecated: `use reader` | 228 | /// Deprecated: `use reader` |
| 228 | pub fn inStream(self: *Self) std.io.InStream(*Self, error{}, readFn) { | 229 | pub fn inStream(self: *Self) std.io.InStream(*Self, error{}, readFn) { |
| 229 | return .{ .context = self }; | 230 | return .{ .context = self }; |
| ... | @@ -315,6 +316,11 @@ pub fn LinearFifo( | ... | @@ -315,6 +316,11 @@ pub fn LinearFifo( |
| 315 | return bytes.len; | 316 | return bytes.len; |
| 316 | } | 317 | } |
| 317 | 318 | ||
| 319 | pub fn writer(self: *Self) std.io.Writer(*Self, error{OutOfMemory}, appendWrite) { | ||
| 320 | return .{ .context = self }; | ||
| 321 | } | ||
| 322 | |||
| 323 | /// Deprecated: `use writer` | ||
| 318 | pub fn outStream(self: *Self) std.io.OutStream(*Self, error{OutOfMemory}, appendWrite) { | 324 | pub fn outStream(self: *Self) std.io.OutStream(*Self, error{OutOfMemory}, appendWrite) { |
| 319 | return .{ .context = self }; | 325 | return .{ .context = self }; |
| 320 | } | 326 | } |
| ... | @@ -426,14 +432,14 @@ test "LinearFifo(u8, .Dynamic)" { | ... | @@ -426,14 +432,14 @@ test "LinearFifo(u8, .Dynamic)" { |
| 426 | fifo.shrink(0); | 432 | fifo.shrink(0); |
| 427 | 433 | ||
| 428 | { | 434 | { |
| 429 | try fifo.outStream().print("{}, {}!", .{ "Hello", "World" }); | 435 | try fifo.writer().print("{}, {}!", .{ "Hello", "World" }); |
| 430 | var result: [30]u8 = undefined; | 436 | var result: [30]u8 = undefined; |
| 431 | testing.expectEqualSlices(u8, "Hello, World!", result[0..fifo.read(&result)]); | 437 | testing.expectEqualSlices(u8, "Hello, World!", result[0..fifo.read(&result)]); |
| 432 | testing.expectEqual(@as(usize, 0), fifo.readableLength()); | 438 | testing.expectEqual(@as(usize, 0), fifo.readableLength()); |
| 433 | } | 439 | } |
| 434 | 440 | ||
| 435 | { | 441 | { |
| 436 | try fifo.outStream().writeAll("This is a test"); | 442 | try fifo.writer().writeAll("This is a test"); |
| 437 | var result: [30]u8 = undefined; | 443 | var result: [30]u8 = undefined; |
| 438 | testing.expectEqualSlices(u8, "This", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?); | 444 | testing.expectEqualSlices(u8, "This", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?); |
| 439 | testing.expectEqualSlices(u8, "is", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?); | 445 | testing.expectEqualSlices(u8, "is", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?); |
lib/std/fmt.zig+2| ... | @@ -88,6 +88,8 @@ pub fn format( | ... | @@ -88,6 +88,8 @@ pub fn format( |
| 88 | if (args.len > ArgSetType.bit_count) { | 88 | if (args.len > ArgSetType.bit_count) { |
| 89 | @compileError("32 arguments max are supported per format call"); | 89 | @compileError("32 arguments max are supported per format call"); |
| 90 | } | 90 | } |
| 91 | if (args.len == 0) | ||
| 92 | return writer.writeAll(fmt); | ||
| 91 | 93 | ||
| 92 | const State = enum { | 94 | const State = enum { |
| 93 | Start, | 95 | Start, |
lib/std/fs.zig+37-23| ... | @@ -84,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: | ... | @@ -84,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: |
| 84 | try crypto.randomBytes(rand_buf[0..]); | 84 | try crypto.randomBytes(rand_buf[0..]); |
| 85 | base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); | 85 | base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); |
| 86 | 86 | ||
| 87 | if (cwd().symLink(existing_path, new_path, .{})) { | 87 | if (cwd().symLink(existing_path, tmp_path, .{})) { |
| 88 | return rename(tmp_path, new_path); | 88 | return rename(tmp_path, new_path); |
| 89 | } else |err| switch (err) { | 89 | } else |err| switch (err) { |
| 90 | error.PathAlreadyExists => continue, | 90 | error.PathAlreadyExists => continue, |
| ... | @@ -225,8 +225,7 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { | ... | @@ -225,8 +225,7 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { |
| 225 | /// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string. | 225 | /// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string. |
| 226 | pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { | 226 | pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { |
| 227 | assert(path.isAbsoluteWindowsW(absolute_path_w)); | 227 | assert(path.isAbsoluteWindowsW(absolute_path_w)); |
| 228 | const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null); | 228 | return os.mkdirW(absolute_path_w, default_new_dir_mode); |
| 229 | os.windows.CloseHandle(handle); | ||
| 230 | } | 229 | } |
| 231 | 230 | ||
| 232 | pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute"); | 231 | pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute"); |
| ... | @@ -881,8 +880,7 @@ pub const Dir = struct { | ... | @@ -881,8 +880,7 @@ pub const Dir = struct { |
| 881 | } | 880 | } |
| 882 | 881 | ||
| 883 | pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void { | 882 | pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void { |
| 884 | const handle = try os.windows.CreateDirectoryW(self.fd, sub_path, null); | 883 | try os.mkdiratW(self.fd, sub_path, default_new_dir_mode); |
| 885 | os.windows.CloseHandle(handle); | ||
| 886 | } | 884 | } |
| 887 | 885 | ||
| 888 | /// Calls makeDir recursively to make an entire path. Returns success if the path | 886 | /// Calls makeDir recursively to make an entire path. Returns success if the path |
| ... | @@ -1119,7 +1117,7 @@ pub const Dir = struct { | ... | @@ -1119,7 +1117,7 @@ pub const Dir = struct { |
| 1119 | pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { | 1117 | pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { |
| 1120 | if (builtin.os.tag == .windows) { | 1118 | if (builtin.os.tag == .windows) { |
| 1121 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); | 1119 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 1122 | return self.deleteFileW(sub_path_w.span().ptr); | 1120 | return self.deleteFileW(sub_path_w.span()); |
| 1123 | } else if (builtin.os.tag == .wasi) { | 1121 | } else if (builtin.os.tag == .wasi) { |
| 1124 | os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) { | 1122 | os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) { |
| 1125 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR | 1123 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR |
| ... | @@ -1153,7 +1151,7 @@ pub const Dir = struct { | ... | @@ -1153,7 +1151,7 @@ pub const Dir = struct { |
| 1153 | } | 1151 | } |
| 1154 | 1152 | ||
| 1155 | /// Same as `deleteFile` except the parameter is WTF-16 encoded. | 1153 | /// Same as `deleteFile` except the parameter is WTF-16 encoded. |
| 1156 | pub fn deleteFileW(self: Dir, sub_path_w: [*:0]const u16) DeleteFileError!void { | 1154 | pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void { |
| 1157 | os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) { | 1155 | os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) { |
| 1158 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR | 1156 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR |
| 1159 | else => |e| return e, | 1157 | else => |e| return e, |
| ... | @@ -1182,7 +1180,7 @@ pub const Dir = struct { | ... | @@ -1182,7 +1180,7 @@ pub const Dir = struct { |
| 1182 | pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { | 1180 | pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { |
| 1183 | if (builtin.os.tag == .windows) { | 1181 | if (builtin.os.tag == .windows) { |
| 1184 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); | 1182 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 1185 | return self.deleteDirW(sub_path_w.span().ptr); | 1183 | return self.deleteDirW(sub_path_w.span()); |
| 1186 | } else if (builtin.os.tag == .wasi) { | 1184 | } else if (builtin.os.tag == .wasi) { |
| 1187 | os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) { | 1185 | os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) { |
| 1188 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR | 1186 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR |
| ... | @@ -1204,7 +1202,7 @@ pub const Dir = struct { | ... | @@ -1204,7 +1202,7 @@ pub const Dir = struct { |
| 1204 | 1202 | ||
| 1205 | /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed. | 1203 | /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed. |
| 1206 | /// This function is Windows-only. | 1204 | /// This function is Windows-only. |
| 1207 | pub fn deleteDirW(self: Dir, sub_path_w: [*:0]const u16) DeleteDirError!void { | 1205 | pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void { |
| 1208 | os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) { | 1206 | os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) { |
| 1209 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR | 1207 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR |
| 1210 | else => |e| return e, | 1208 | else => |e| return e, |
| ... | @@ -1263,11 +1261,11 @@ pub const Dir = struct { | ... | @@ -1263,11 +1261,11 @@ pub const Dir = struct { |
| 1263 | /// are null-terminated, WTF16 encoded. | 1261 | /// are null-terminated, WTF16 encoded. |
| 1264 | pub fn symLinkW( | 1262 | pub fn symLinkW( |
| 1265 | self: Dir, | 1263 | self: Dir, |
| 1266 | target_path_w: [:0]const u16, | 1264 | target_path_w: []const u16, |
| 1267 | sym_link_path_w: [:0]const u16, | 1265 | sym_link_path_w: []const u16, |
| 1268 | flags: SymLinkFlags, | 1266 | flags: SymLinkFlags, |
| 1269 | ) !void { | 1267 | ) !void { |
| 1270 | return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory); | 1268 | return os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory); |
| 1271 | } | 1269 | } |
| 1272 | 1270 | ||
| 1273 | /// Read value of a symbolic link. | 1271 | /// Read value of a symbolic link. |
| ... | @@ -1278,7 +1276,8 @@ pub const Dir = struct { | ... | @@ -1278,7 +1276,8 @@ pub const Dir = struct { |
| 1278 | return self.readLinkWasi(sub_path, buffer); | 1276 | return self.readLinkWasi(sub_path, buffer); |
| 1279 | } | 1277 | } |
| 1280 | if (builtin.os.tag == .windows) { | 1278 | if (builtin.os.tag == .windows) { |
| 1281 | return os.windows.ReadLink(self.fd, sub_path, buffer); | 1279 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 1280 | return self.readLinkW(sub_path_w.span(), buffer); | ||
| 1282 | } | 1281 | } |
| 1283 | const sub_path_c = try os.toPosixPath(sub_path); | 1282 | const sub_path_c = try os.toPosixPath(sub_path); |
| 1284 | return self.readLinkZ(&sub_path_c, buffer); | 1283 | return self.readLinkZ(&sub_path_c, buffer); |
| ... | @@ -1295,15 +1294,15 @@ pub const Dir = struct { | ... | @@ -1295,15 +1294,15 @@ pub const Dir = struct { |
| 1295 | pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { | 1294 | pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { |
| 1296 | if (builtin.os.tag == .windows) { | 1295 | if (builtin.os.tag == .windows) { |
| 1297 | const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); | 1296 | const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); |
| 1298 | return self.readLinkW(sub_path_w, buffer); | 1297 | return self.readLinkW(sub_path_w.span(), buffer); |
| 1299 | } | 1298 | } |
| 1300 | return os.readlinkatZ(self.fd, sub_path_c, buffer); | 1299 | return os.readlinkatZ(self.fd, sub_path_c, buffer); |
| 1301 | } | 1300 | } |
| 1302 | 1301 | ||
| 1303 | /// Windows-only. Same as `readLink` except the pathname parameter | 1302 | /// Windows-only. Same as `readLink` except the pathname parameter |
| 1304 | /// is null-terminated, WTF16 encoded. | 1303 | /// is null-terminated, WTF16 encoded. |
| 1305 | pub fn readLinkW(self: Dir, sub_path_w: [*:0]const u16, buffer: []u8) ![]u8 { | 1304 | pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 { |
| 1306 | return os.windows.ReadLinkW(self.fd, sub_path_w, buffer); | 1305 | return os.windows.ReadLink(self.fd, sub_path_w, buffer); |
| 1307 | } | 1306 | } |
| 1308 | 1307 | ||
| 1309 | /// On success, caller owns returned buffer. | 1308 | /// On success, caller owns returned buffer. |
| ... | @@ -1813,7 +1812,9 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags | ... | @@ -1813,7 +1812,9 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags |
| 1813 | assert(path.isAbsolute(target_path)); | 1812 | assert(path.isAbsolute(target_path)); |
| 1814 | assert(path.isAbsolute(sym_link_path)); | 1813 | assert(path.isAbsolute(sym_link_path)); |
| 1815 | if (builtin.os.tag == .windows) { | 1814 | if (builtin.os.tag == .windows) { |
| 1816 | return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory); | 1815 | const target_path_w = try os.windows.sliceToPrefixedFileW(target_path); |
| 1816 | const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path); | ||
| 1817 | return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory); | ||
| 1817 | } | 1818 | } |
| 1818 | return os.symlink(target_path, sym_link_path); | 1819 | return os.symlink(target_path, sym_link_path); |
| 1819 | } | 1820 | } |
| ... | @@ -1822,10 +1823,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags | ... | @@ -1822,10 +1823,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags |
| 1822 | /// Note that this function will by default try creating a symbolic link to a file. If you would | 1823 | /// Note that this function will by default try creating a symbolic link to a file. If you would |
| 1823 | /// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`. | 1824 | /// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`. |
| 1824 | /// See also `symLinkAbsolute`, `symLinkAbsoluteZ`. | 1825 | /// See also `symLinkAbsolute`, `symLinkAbsoluteZ`. |
| 1825 | pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void { | 1826 | pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void { |
| 1826 | assert(path.isAbsoluteWindowsW(target_path_w)); | 1827 | assert(path.isAbsoluteWindowsWTF16(target_path_w)); |
| 1827 | assert(path.isAbsoluteWindowsW(sym_link_path_w)); | 1828 | assert(path.isAbsoluteWindowsWTF16(sym_link_path_w)); |
| 1828 | return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory); | 1829 | return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory); |
| 1829 | } | 1830 | } |
| 1830 | 1831 | ||
| 1831 | /// Same as `symLinkAbsolute` except the parameters are null-terminated pointers. | 1832 | /// 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 | ... | @@ -1836,7 +1837,7 @@ pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]cons |
| 1836 | if (builtin.os.tag == .windows) { | 1837 | if (builtin.os.tag == .windows) { |
| 1837 | const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c); | 1838 | const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c); |
| 1838 | const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c); | 1839 | const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c); |
| 1839 | return os.windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags.is_directory); | 1840 | return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory); |
| 1840 | } | 1841 | } |
| 1841 | return os.symlinkZ(target_path_c, sym_link_path_c); | 1842 | return os.symlinkZ(target_path_c, sym_link_path_c); |
| 1842 | } | 1843 | } |
| ... | @@ -1938,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { | ... | @@ -1938,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { |
| 1938 | return walker; | 1939 | return walker; |
| 1939 | } | 1940 | } |
| 1940 | 1941 | ||
| 1941 | pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError || os.FlockError; | 1942 | pub const OpenSelfExeError = error{ |
| 1943 | SharingViolation, | ||
| 1944 | PathAlreadyExists, | ||
| 1945 | FileNotFound, | ||
| 1946 | AccessDenied, | ||
| 1947 | PipeBusy, | ||
| 1948 | NameTooLong, | ||
| 1949 | /// On Windows, file paths must be valid Unicode. | ||
| 1950 | InvalidUtf8, | ||
| 1951 | /// On Windows, file paths cannot contain these characters: | ||
| 1952 | /// '/', '*', '?', '"', '<', '>', '|' | ||
| 1953 | BadPathName, | ||
| 1954 | Unexpected, | ||
| 1955 | } || os.OpenError || SelfExePathError || os.FlockError; | ||
| 1942 | 1956 | ||
| 1943 | pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { | 1957 | pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { |
| 1944 | if (builtin.os.tag == .linux) { | 1958 | if (builtin.os.tag == .linux) { |
lib/std/fs/file.zig+14-1| ... | @@ -47,7 +47,20 @@ pub const File = struct { | ... | @@ -47,7 +47,20 @@ pub const File = struct { |
| 47 | else => 0o666, | 47 | else => 0o666, |
| 48 | }; | 48 | }; |
| 49 | 49 | ||
| 50 | pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError; | 50 | pub const OpenError = error{ |
| 51 | SharingViolation, | ||
| 52 | PathAlreadyExists, | ||
| 53 | FileNotFound, | ||
| 54 | AccessDenied, | ||
| 55 | PipeBusy, | ||
| 56 | NameTooLong, | ||
| 57 | /// On Windows, file paths must be valid Unicode. | ||
| 58 | InvalidUtf8, | ||
| 59 | /// On Windows, file paths cannot contain these characters: | ||
| 60 | /// '/', '*', '?', '"', '<', '>', '|' | ||
| 61 | BadPathName, | ||
| 62 | Unexpected, | ||
| 63 | } || os.OpenError || os.FlockError; | ||
| 51 | 64 | ||
| 52 | pub const Lock = enum { None, Shared, Exclusive }; | 65 | pub const Lock = enum { None, Shared, Exclusive }; |
| 53 | 66 |
lib/std/fs/watch.zig+7-9| ... | @@ -374,15 +374,13 @@ pub fn Watch(comptime V: type) type { | ... | @@ -374,15 +374,13 @@ pub fn Watch(comptime V: type) type { |
| 374 | defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null); | 374 | defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null); |
| 375 | const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; | 375 | const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; |
| 376 | 376 | ||
| 377 | const dir_handle = try windows.CreateFileW( | 377 | const dir_handle = try windows.OpenFile(dirname_utf16le, .{ |
| 378 | dirname_utf16le.ptr, | 378 | .dir = std.fs.cwd().fd, |
| 379 | windows.FILE_LIST_DIRECTORY, | 379 | .access_mask = windows.FILE_LIST_DIRECTORY, |
| 380 | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE, | 380 | .creation = windows.FILE_OPEN, |
| 381 | null, | 381 | .io_mode = .blocking, |
| 382 | windows.OPEN_EXISTING, | 382 | .open_dir = true, |
| 383 | windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED, | 383 | }); |
| 384 | null, | ||
| 385 | ); | ||
| 386 | var dir_handle_consumed = false; | 384 | var dir_handle_consumed = false; |
| 387 | defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle); | 385 | defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle); |
| 388 | 386 |
lib/std/hash/auto_hash.zig+15-17| ... | @@ -56,9 +56,6 @@ pub fn hashPointer(hasher: anytype, key: anytype, comptime strat: HashStrategy) | ... | @@ -56,9 +56,6 @@ pub fn hashPointer(hasher: anytype, key: anytype, comptime strat: HashStrategy) |
| 56 | pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { | 56 | pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { |
| 57 | switch (strat) { | 57 | switch (strat) { |
| 58 | .Shallow => { | 58 | .Shallow => { |
| 59 | // TODO detect via a trait when Key has no padding bits to | ||
| 60 | // hash it as an array of bytes. | ||
| 61 | // Otherwise, hash every element. | ||
| 62 | for (key) |element| { | 59 | for (key) |element| { |
| 63 | hash(hasher, element, .Shallow); | 60 | hash(hasher, element, .Shallow); |
| 64 | } | 61 | } |
| ... | @@ -75,30 +72,34 @@ pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) vo | ... | @@ -75,30 +72,34 @@ pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) vo |
| 75 | /// Strategy is provided to determine if pointers should be followed or not. | 72 | /// Strategy is provided to determine if pointers should be followed or not. |
| 76 | pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { | 73 | pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { |
| 77 | const Key = @TypeOf(key); | 74 | const Key = @TypeOf(key); |
| 75 | |||
| 76 | if (strat == .Shallow and comptime meta.trait.hasUniqueRepresentation(Key)) { | ||
| 77 | @call(.{ .modifier = .always_inline }, hasher.update, .{mem.asBytes(&key)}); | ||
| 78 | return; | ||
| 79 | } | ||
| 80 | |||
| 78 | switch (@typeInfo(Key)) { | 81 | switch (@typeInfo(Key)) { |
| 79 | .NoReturn, | 82 | .NoReturn, |
| 80 | .Opaque, | 83 | .Opaque, |
| 81 | .Undefined, | 84 | .Undefined, |
| 82 | .Void, | 85 | .Void, |
| 83 | .Null, | 86 | .Null, |
| 84 | .BoundFn, | ||
| 85 | .ComptimeFloat, | 87 | .ComptimeFloat, |
| 86 | .ComptimeInt, | 88 | .ComptimeInt, |
| 87 | .Type, | 89 | .Type, |
| 88 | .EnumLiteral, | 90 | .EnumLiteral, |
| 89 | .Frame, | 91 | .Frame, |
| 92 | .Float, | ||
| 90 | => @compileError("cannot hash this type"), | 93 | => @compileError("cannot hash this type"), |
| 91 | 94 | ||
| 92 | // Help the optimizer see that hashing an int is easy by inlining! | 95 | // Help the optimizer see that hashing an int is easy by inlining! |
| 93 | // TODO Check if the situation is better after #561 is resolved. | 96 | // TODO Check if the situation is better after #561 is resolved. |
| 94 | .Int => @call(.{ .modifier = .always_inline }, hasher.update, .{std.mem.asBytes(&key)}), | 97 | .Int => @call(.{ .modifier = .always_inline }, hasher.update, .{std.mem.asBytes(&key)}), |
| 95 | 98 | ||
| 96 | .Float => |info| hash(hasher, @bitCast(std.meta.Int(false, info.bits), key), strat), | ||
| 97 | |||
| 98 | .Bool => hash(hasher, @boolToInt(key), strat), | 99 | .Bool => hash(hasher, @boolToInt(key), strat), |
| 99 | .Enum => hash(hasher, @enumToInt(key), strat), | 100 | .Enum => hash(hasher, @enumToInt(key), strat), |
| 100 | .ErrorSet => hash(hasher, @errorToInt(key), strat), | 101 | .ErrorSet => hash(hasher, @errorToInt(key), strat), |
| 101 | .AnyFrame, .Fn => hash(hasher, @ptrToInt(key), strat), | 102 | .AnyFrame, .BoundFn, .Fn => hash(hasher, @ptrToInt(key), strat), |
| 102 | 103 | ||
| 103 | .Pointer => @call(.{ .modifier = .always_inline }, hashPointer, .{ hasher, key, strat }), | 104 | .Pointer => @call(.{ .modifier = .always_inline }, hashPointer, .{ hasher, key, strat }), |
| 104 | 105 | ||
| ... | @@ -121,9 +122,6 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { | ... | @@ -121,9 +122,6 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { |
| 121 | }, | 122 | }, |
| 122 | 123 | ||
| 123 | .Struct => |info| { | 124 | .Struct => |info| { |
| 124 | // TODO detect via a trait when Key has no padding bits to | ||
| 125 | // hash it as an array of bytes. | ||
| 126 | // Otherwise, hash every field. | ||
| 127 | inline for (info.fields) |field| { | 125 | inline for (info.fields) |field| { |
| 128 | // We reuse the hash of the previous field as the seed for the | 126 | // We reuse the hash of the previous field as the seed for the |
| 129 | // next one so that they're dependant. | 127 | // next one so that they're dependant. |
| ... | @@ -266,12 +264,12 @@ test "hash slice deep" { | ... | @@ -266,12 +264,12 @@ test "hash slice deep" { |
| 266 | test "hash struct deep" { | 264 | test "hash struct deep" { |
| 267 | const Foo = struct { | 265 | const Foo = struct { |
| 268 | a: u32, | 266 | a: u32, |
| 269 | b: f64, | 267 | b: u16, |
| 270 | c: *bool, | 268 | c: *bool, |
| 271 | 269 | ||
| 272 | const Self = @This(); | 270 | const Self = @This(); |
| 273 | 271 | ||
| 274 | pub fn init(allocator: *mem.Allocator, a_: u32, b_: f64, c_: bool) !Self { | 272 | pub fn init(allocator: *mem.Allocator, a_: u32, b_: u16, c_: bool) !Self { |
| 275 | const ptr = try allocator.create(bool); | 273 | const ptr = try allocator.create(bool); |
| 276 | ptr.* = c_; | 274 | ptr.* = c_; |
| 277 | return Self{ .a = a_, .b = b_, .c = ptr }; | 275 | return Self{ .a = a_, .b = b_, .c = ptr }; |
| ... | @@ -279,9 +277,9 @@ test "hash struct deep" { | ... | @@ -279,9 +277,9 @@ test "hash struct deep" { |
| 279 | }; | 277 | }; |
| 280 | 278 | ||
| 281 | const allocator = std.testing.allocator; | 279 | const allocator = std.testing.allocator; |
| 282 | const foo = try Foo.init(allocator, 123, 1.0, true); | 280 | const foo = try Foo.init(allocator, 123, 10, true); |
| 283 | const bar = try Foo.init(allocator, 123, 1.0, true); | 281 | const bar = try Foo.init(allocator, 123, 10, true); |
| 284 | const baz = try Foo.init(allocator, 123, 1.0, false); | 282 | const baz = try Foo.init(allocator, 123, 10, false); |
| 285 | defer allocator.destroy(foo.c); | 283 | defer allocator.destroy(foo.c); |
| 286 | defer allocator.destroy(bar.c); | 284 | defer allocator.destroy(bar.c); |
| 287 | defer allocator.destroy(baz.c); | 285 | defer allocator.destroy(baz.c); |
| ... | @@ -338,12 +336,12 @@ test "testHash struct" { | ... | @@ -338,12 +336,12 @@ test "testHash struct" { |
| 338 | test "testHash union" { | 336 | test "testHash union" { |
| 339 | const Foo = union(enum) { | 337 | const Foo = union(enum) { |
| 340 | A: u32, | 338 | A: u32, |
| 341 | B: f32, | 339 | B: bool, |
| 342 | C: u32, | 340 | C: u32, |
| 343 | }; | 341 | }; |
| 344 | 342 | ||
| 345 | const a = Foo{ .A = 18 }; | 343 | const a = Foo{ .A = 18 }; |
| 346 | var b = Foo{ .B = 12.34 }; | 344 | var b = Foo{ .B = true }; |
| 347 | const c = Foo{ .C = 18 }; | 345 | const c = Foo{ .C = 18 }; |
| 348 | testing.expect(testHash(a) == testHash(a)); | 346 | testing.expect(testHash(a) == testHash(a)); |
| 349 | testing.expect(testHash(a) != testHash(b)); | 347 | testing.expect(testHash(a) != testHash(b)); |
lib/std/hash_map.zig+21-8| ... | @@ -5,6 +5,7 @@ const testing = std.testing; | ... | @@ -5,6 +5,7 @@ const testing = std.testing; |
| 5 | const math = std.math; | 5 | const math = std.math; |
| 6 | const mem = std.mem; | 6 | const mem = std.mem; |
| 7 | const meta = std.meta; | 7 | const meta = std.meta; |
| 8 | const trait = meta.trait; | ||
| 8 | const autoHash = std.hash.autoHash; | 9 | const autoHash = std.hash.autoHash; |
| 9 | const Wyhash = std.hash.Wyhash; | 10 | const Wyhash = std.hash.Wyhash; |
| 10 | const Allocator = mem.Allocator; | 11 | const Allocator = mem.Allocator; |
| ... | @@ -195,6 +196,10 @@ pub fn HashMap( | ... | @@ -195,6 +196,10 @@ pub fn HashMap( |
| 195 | return self.unmanaged.getEntry(key); | 196 | return self.unmanaged.getEntry(key); |
| 196 | } | 197 | } |
| 197 | 198 | ||
| 199 | pub fn getIndex(self: Self, key: K) ?usize { | ||
| 200 | return self.unmanaged.getIndex(key); | ||
| 201 | } | ||
| 202 | |||
| 198 | pub fn get(self: Self, key: K) ?V { | 203 | pub fn get(self: Self, key: K) ?V { |
| 199 | return self.unmanaged.get(key); | 204 | return self.unmanaged.get(key); |
| 200 | } | 205 | } |
| ... | @@ -478,17 +483,21 @@ pub fn HashMapUnmanaged( | ... | @@ -478,17 +483,21 @@ pub fn HashMapUnmanaged( |
| 478 | } | 483 | } |
| 479 | 484 | ||
| 480 | pub fn getEntry(self: Self, key: K) ?*Entry { | 485 | pub fn getEntry(self: Self, key: K) ?*Entry { |
| 486 | const index = self.getIndex(key) orelse return null; | ||
| 487 | return &self.entries.items[index]; | ||
| 488 | } | ||
| 489 | |||
| 490 | pub fn getIndex(self: Self, key: K) ?usize { | ||
| 481 | const header = self.index_header orelse { | 491 | const header = self.index_header orelse { |
| 482 | // Linear scan. | 492 | // Linear scan. |
| 483 | const h = if (store_hash) hash(key) else {}; | 493 | const h = if (store_hash) hash(key) else {}; |
| 484 | for (self.entries.items) |*item| { | 494 | for (self.entries.items) |*item, i| { |
| 485 | if (item.hash == h and eql(key, item.key)) { | 495 | if (item.hash == h and eql(key, item.key)) { |
| 486 | return item; | 496 | return i; |
| 487 | } | 497 | } |
| 488 | } | 498 | } |
| 489 | return null; | 499 | return null; |
| 490 | }; | 500 | }; |
| 491 | |||
| 492 | switch (header.capacityIndexType()) { | 501 | switch (header.capacityIndexType()) { |
| 493 | .u8 => return self.getInternal(key, header, u8), | 502 | .u8 => return self.getInternal(key, header, u8), |
| 494 | .u16 => return self.getInternal(key, header, u16), | 503 | .u16 => return self.getInternal(key, header, u16), |
| ... | @@ -710,7 +719,7 @@ pub fn HashMapUnmanaged( | ... | @@ -710,7 +719,7 @@ pub fn HashMapUnmanaged( |
| 710 | unreachable; | 719 | unreachable; |
| 711 | } | 720 | } |
| 712 | 721 | ||
| 713 | fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry { | 722 | fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?usize { |
| 714 | const indexes = header.indexes(I); | 723 | const indexes = header.indexes(I); |
| 715 | const h = hash(key); | 724 | const h = hash(key); |
| 716 | const start_index = header.constrainIndex(h); | 725 | const start_index = header.constrainIndex(h); |
| ... | @@ -724,7 +733,7 @@ pub fn HashMapUnmanaged( | ... | @@ -724,7 +733,7 @@ pub fn HashMapUnmanaged( |
| 724 | const entry = &self.entries.items[index.entry_index]; | 733 | const entry = &self.entries.items[index.entry_index]; |
| 725 | const hash_match = if (store_hash) h == entry.hash else true; | 734 | const hash_match = if (store_hash) h == entry.hash else true; |
| 726 | if (hash_match and eql(key, entry.key)) | 735 | if (hash_match and eql(key, entry.key)) |
| 727 | return entry; | 736 | return index.entry_index; |
| 728 | } | 737 | } |
| 729 | return null; | 738 | return null; |
| 730 | } | 739 | } |
| ... | @@ -1023,9 +1032,13 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) { | ... | @@ -1023,9 +1032,13 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) { |
| 1023 | pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { | 1032 | pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { |
| 1024 | return struct { | 1033 | return struct { |
| 1025 | fn hash(key: K) u32 { | 1034 | fn hash(key: K) u32 { |
| 1026 | var hasher = Wyhash.init(0); | 1035 | if (comptime trait.hasUniqueRepresentation(K)) { |
| 1027 | autoHash(&hasher, key); | 1036 | return @truncate(u32, Wyhash.hash(0, std.mem.asBytes(&key))); |
| 1028 | return @truncate(u32, hasher.final()); | 1037 | } else { |
| 1038 | var hasher = Wyhash.init(0); | ||
| 1039 | autoHash(&hasher, key); | ||
| 1040 | return @truncate(u32, hasher.final()); | ||
| 1041 | } | ||
| 1029 | } | 1042 | } |
| 1030 | }.hash; | 1043 | }.hash; |
| 1031 | } | 1044 | } |
lib/std/math/big/int.zig+1-1| ... | @@ -99,7 +99,7 @@ pub const Mutable = struct { | ... | @@ -99,7 +99,7 @@ pub const Mutable = struct { |
| 99 | pub fn toManaged(self: Mutable, allocator: *Allocator) Managed { | 99 | pub fn toManaged(self: Mutable, allocator: *Allocator) Managed { |
| 100 | return .{ | 100 | return .{ |
| 101 | .allocator = allocator, | 101 | .allocator = allocator, |
| 102 | .limbs = limbs, | 102 | .limbs = self.limbs, |
| 103 | .metadata = if (self.positive) | 103 | .metadata = if (self.positive) |
| 104 | self.len & ~Managed.sign_bit | 104 | self.len & ~Managed.sign_bit |
| 105 | else | 105 | else |
lib/std/math/big/int_test.zig+22| ... | @@ -2,6 +2,7 @@ const std = @import("../../std.zig"); | ... | @@ -2,6 +2,7 @@ const std = @import("../../std.zig"); |
| 2 | const mem = std.mem; | 2 | const mem = std.mem; |
| 3 | const testing = std.testing; | 3 | const testing = std.testing; |
| 4 | const Managed = std.math.big.int.Managed; | 4 | const Managed = std.math.big.int.Managed; |
| 5 | const Mutable = std.math.big.int.Mutable; | ||
| 5 | const Limb = std.math.big.Limb; | 6 | const Limb = std.math.big.Limb; |
| 6 | const DoubleLimb = std.math.big.DoubleLimb; | 7 | const DoubleLimb = std.math.big.DoubleLimb; |
| 7 | const maxInt = std.math.maxInt; | 8 | const maxInt = std.math.maxInt; |
| ... | @@ -1453,3 +1454,24 @@ test "big.int gcd one large" { | ... | @@ -1453,3 +1454,24 @@ test "big.int gcd one large" { |
| 1453 | 1454 | ||
| 1454 | testing.expect((try r.to(u64)) == 1); | 1455 | testing.expect((try r.to(u64)) == 1); |
| 1455 | } | 1456 | } |
| 1457 | |||
| 1458 | test "big.int mutable to managed" { | ||
| 1459 | const allocator = testing.allocator; | ||
| 1460 | var limbs_buf = try allocator.alloc(Limb, 8); | ||
| 1461 | defer allocator.free(limbs_buf); | ||
| 1462 | |||
| 1463 | var a = Mutable.init(limbs_buf, 0xdeadbeef); | ||
| 1464 | var a_managed = a.toManaged(allocator); | ||
| 1465 | |||
| 1466 | testing.expect(a.toConst().eq(a_managed.toConst())); | ||
| 1467 | } | ||
| 1468 | |||
| 1469 | test "big.int const to managed" { | ||
| 1470 | var a = try Managed.initSet(testing.allocator, 123423453456); | ||
| 1471 | defer a.deinit(); | ||
| 1472 | |||
| 1473 | var b = try a.toConst().toManaged(testing.allocator); | ||
| 1474 | defer b.deinit(); | ||
| 1475 | |||
| 1476 | testing.expect(a.toConst().eq(b.toConst())); | ||
| 1477 | } |
lib/std/mem.zig+73| ... | @@ -2030,6 +2030,79 @@ test "rotate" { | ... | @@ -2030,6 +2030,79 @@ test "rotate" { |
| 2030 | testing.expect(eql(i32, &arr, &[_]i32{ 1, 2, 4, 5, 3 })); | 2030 | testing.expect(eql(i32, &arr, &[_]i32{ 1, 2, 4, 5, 3 })); |
| 2031 | } | 2031 | } |
| 2032 | 2032 | ||
| 2033 | /// Replace needle with replacement as many times as possible, writing to an output buffer which is assumed to be of | ||
| 2034 | /// appropriate size. Use replacementSize to calculate an appropriate buffer size. | ||
| 2035 | pub fn replace(comptime T: type, input: []const T, needle: []const T, replacement: []const T, output: []T) usize { | ||
| 2036 | var i: usize = 0; | ||
| 2037 | var slide: usize = 0; | ||
| 2038 | var replacements: usize = 0; | ||
| 2039 | while (slide < input.len) { | ||
| 2040 | if (mem.indexOf(T, input[slide..], needle) == @as(usize, 0)) { | ||
| 2041 | mem.copy(T, output[i..i + replacement.len], replacement); | ||
| 2042 | i += replacement.len; | ||
| 2043 | slide += needle.len; | ||
| 2044 | replacements += 1; | ||
| 2045 | } else { | ||
| 2046 | output[i] = input[slide]; | ||
| 2047 | i += 1; | ||
| 2048 | slide += 1; | ||
| 2049 | } | ||
| 2050 | } | ||
| 2051 | |||
| 2052 | return replacements; | ||
| 2053 | } | ||
| 2054 | |||
| 2055 | test "replace" { | ||
| 2056 | var output: [29]u8 = undefined; | ||
| 2057 | var replacements = replace(u8, "All your base are belong to us", "base", "Zig", output[0..]); | ||
| 2058 | testing.expect(replacements == 1); | ||
| 2059 | testing.expect(eql(u8, output[0..], "All your Zig are belong to us")); | ||
| 2060 | |||
| 2061 | replacements = replace(u8, "Favor reading code over writing code.", "code", "", output[0..]); | ||
| 2062 | testing.expect(replacements == 2); | ||
| 2063 | testing.expect(eql(u8, output[0..], "Favor reading over writing .")); | ||
| 2064 | } | ||
| 2065 | |||
| 2066 | /// Calculate the size needed in an output buffer to perform a replacement. | ||
| 2067 | pub fn replacementSize(comptime T: type, input: []const T, needle: []const T, replacement: []const T) usize { | ||
| 2068 | var i: usize = 0; | ||
| 2069 | var size: usize = input.len; | ||
| 2070 | while (i < input.len) : (i += 1) { | ||
| 2071 | if (mem.indexOf(T, input[i..], needle) == @as(usize, 0)) { | ||
| 2072 | size = size - needle.len + replacement.len; | ||
| 2073 | i += needle.len; | ||
| 2074 | } | ||
| 2075 | } | ||
| 2076 | |||
| 2077 | return size; | ||
| 2078 | } | ||
| 2079 | |||
| 2080 | test "replacementSize" { | ||
| 2081 | testing.expect(replacementSize(u8, "All your base are belong to us", "base", "Zig") == 29); | ||
| 2082 | testing.expect(replacementSize(u8, "", "", "") == 0); | ||
| 2083 | testing.expect(replacementSize(u8, "Favor reading code over writing code.", "code", "") == 29); | ||
| 2084 | testing.expect(replacementSize(u8, "Only one obvious way to do things.", "things.", "things in Zig.") == 41); | ||
| 2085 | } | ||
| 2086 | |||
| 2087 | /// Perform a replacement on an allocated buffer of pre-determined size. Caller must free returned memory. | ||
| 2088 | pub fn replaceOwned(comptime T: type, allocator: *Allocator, input: []const T, needle: []const T, replacement: []const T) Allocator.Error![]T { | ||
| 2089 | var output = try allocator.alloc(T, replacementSize(T, input, needle, replacement)); | ||
| 2090 | _ = replace(T, input, needle, replacement, output); | ||
| 2091 | return output; | ||
| 2092 | } | ||
| 2093 | |||
| 2094 | test "replaceOwned" { | ||
| 2095 | const allocator = std.heap.page_allocator; | ||
| 2096 | |||
| 2097 | const base_replace = replaceOwned(u8, allocator, "All your base are belong to us", "base", "Zig") catch unreachable; | ||
| 2098 | defer allocator.free(base_replace); | ||
| 2099 | testing.expect(eql(u8, base_replace, "All your Zig are belong to us")); | ||
| 2100 | |||
| 2101 | const zen_replace = replaceOwned(u8, allocator, "Favor reading code over writing code.", " code", "") catch unreachable; | ||
| 2102 | defer allocator.free(zen_replace); | ||
| 2103 | testing.expect(eql(u8, zen_replace, "Favor reading over writing.")); | ||
| 2104 | } | ||
| 2105 | |||
| 2033 | /// Converts a little-endian integer to host endianness. | 2106 | /// Converts a little-endian integer to host endianness. |
| 2034 | pub fn littleToNative(comptime T: type, x: T) T { | 2107 | pub fn littleToNative(comptime T: type, x: T) T { |
| 2035 | return switch (builtin.endian) { | 2108 | return switch (builtin.endian) { |
lib/std/meta/trait.zig+68| ... | @@ -429,3 +429,71 @@ test "std.meta.trait.hasFunctions" { | ... | @@ -429,3 +429,71 @@ test "std.meta.trait.hasFunctions" { |
| 429 | testing.expect(!hasFunctions(TestStruct2, .{ "a", "b", "c" })); | 429 | testing.expect(!hasFunctions(TestStruct2, .{ "a", "b", "c" })); |
| 430 | testing.expect(!hasFunctions(TestStruct2, tuple)); | 430 | testing.expect(!hasFunctions(TestStruct2, tuple)); |
| 431 | } | 431 | } |
| 432 | |||
| 433 | /// True if every value of the type `T` has a unique bit pattern representing it. | ||
| 434 | /// In other words, `T` has no unused bits and no padding. | ||
| 435 | pub fn hasUniqueRepresentation(comptime T: type) bool { | ||
| 436 | switch (@typeInfo(T)) { | ||
| 437 | else => return false, // TODO can we know if it's true for some of these types ? | ||
| 438 | |||
| 439 | .AnyFrame, | ||
| 440 | .Bool, | ||
| 441 | .BoundFn, | ||
| 442 | .Enum, | ||
| 443 | .ErrorSet, | ||
| 444 | .Fn, | ||
| 445 | .Int, // TODO check that it is still true | ||
| 446 | .Pointer, | ||
| 447 | => return true, | ||
| 448 | |||
| 449 | .Array => |info| return comptime hasUniqueRepresentation(info.child), | ||
| 450 | |||
| 451 | .Struct => |info| { | ||
| 452 | var sum_size = @as(usize, 0); | ||
| 453 | |||
| 454 | inline for (info.fields) |field| { | ||
| 455 | const FieldType = field.field_type; | ||
| 456 | if (comptime !hasUniqueRepresentation(FieldType)) return false; | ||
| 457 | sum_size += @sizeOf(FieldType); | ||
| 458 | } | ||
| 459 | |||
| 460 | return @sizeOf(T) == sum_size; | ||
| 461 | }, | ||
| 462 | |||
| 463 | .Vector => |info| return comptime hasUniqueRepresentation(info.child), | ||
| 464 | } | ||
| 465 | } | ||
| 466 | |||
| 467 | test "std.meta.trait.hasUniqueRepresentation" { | ||
| 468 | const TestStruct1 = struct { | ||
| 469 | a: u32, | ||
| 470 | b: u32, | ||
| 471 | }; | ||
| 472 | |||
| 473 | testing.expect(hasUniqueRepresentation(TestStruct1)); | ||
| 474 | |||
| 475 | const TestStruct2 = struct { | ||
| 476 | a: u32, | ||
| 477 | b: u16, | ||
| 478 | }; | ||
| 479 | |||
| 480 | testing.expect(!hasUniqueRepresentation(TestStruct2)); | ||
| 481 | |||
| 482 | const TestStruct3 = struct { | ||
| 483 | a: u32, | ||
| 484 | b: u32, | ||
| 485 | }; | ||
| 486 | |||
| 487 | testing.expect(hasUniqueRepresentation(TestStruct3)); | ||
| 488 | |||
| 489 | testing.expect(hasUniqueRepresentation(i1)); | ||
| 490 | testing.expect(hasUniqueRepresentation(u2)); | ||
| 491 | testing.expect(hasUniqueRepresentation(i3)); | ||
| 492 | testing.expect(hasUniqueRepresentation(u4)); | ||
| 493 | testing.expect(hasUniqueRepresentation(i5)); | ||
| 494 | testing.expect(hasUniqueRepresentation(u6)); | ||
| 495 | testing.expect(hasUniqueRepresentation(i7)); | ||
| 496 | testing.expect(hasUniqueRepresentation(u8)); | ||
| 497 | testing.expect(hasUniqueRepresentation(i9)); | ||
| 498 | testing.expect(hasUniqueRepresentation(u10)); | ||
| 499 | } |
lib/std/net.zig+279-196| ... | @@ -14,8 +14,8 @@ const has_unix_sockets = @hasDecl(os, "sockaddr_un"); | ... | @@ -14,8 +14,8 @@ const has_unix_sockets = @hasDecl(os, "sockaddr_un"); |
| 14 | 14 | ||
| 15 | pub const Address = extern union { | 15 | pub const Address = extern union { |
| 16 | any: os.sockaddr, | 16 | any: os.sockaddr, |
| 17 | in: os.sockaddr_in, | 17 | in: Ip4Address, |
| 18 | in6: os.sockaddr_in6, | 18 | in6: Ip6Address, |
| 19 | un: if (has_unix_sockets) os.sockaddr_un else void, | 19 | un: if (has_unix_sockets) os.sockaddr_un else void, |
| 20 | 20 | ||
| 21 | // TODO this crashed the compiler. https://github.com/ziglang/zig/issues/3512 | 21 | // TODO this crashed the compiler. https://github.com/ziglang/zig/issues/3512 |
| ... | @@ -76,19 +76,227 @@ pub const Address = extern union { | ... | @@ -76,19 +76,227 @@ pub const Address = extern union { |
| 76 | } | 76 | } |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | pub fn parseIp6(buf: []const u8, port: u16) !Address { | ||
| 80 | return Address{.in6 = try Ip6Address.parse(buf, port) }; | ||
| 81 | } | ||
| 82 | |||
| 83 | pub fn resolveIp6(buf: []const u8, port: u16) !Address { | ||
| 84 | return Address{.in6 = try Ip6Address.resolve(buf, port) }; | ||
| 85 | } | ||
| 86 | |||
| 87 | pub fn parseIp4(buf: []const u8, port: u16) !Address { | ||
| 88 | return Address {.in = try Ip4Address.parse(buf, port) }; | ||
| 89 | } | ||
| 90 | |||
| 91 | pub fn initIp4(addr: [4]u8, port: u16) Address { | ||
| 92 | return Address{.in = Ip4Address.init(addr, port) }; | ||
| 93 | } | ||
| 94 | |||
| 95 | pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address { | ||
| 96 | return Address{.in6 = Ip6Address.init(addr, port, flowinfo, scope_id) }; | ||
| 97 | } | ||
| 98 | |||
| 99 | pub fn initUnix(path: []const u8) !Address { | ||
| 100 | var sock_addr = os.sockaddr_un{ | ||
| 101 | .family = os.AF_UNIX, | ||
| 102 | .path = undefined, | ||
| 103 | }; | ||
| 104 | |||
| 105 | // this enables us to have the proper length of the socket in getOsSockLen | ||
| 106 | mem.set(u8, &sock_addr.path, 0); | ||
| 107 | |||
| 108 | if (path.len > sock_addr.path.len) return error.NameTooLong; | ||
| 109 | mem.copy(u8, &sock_addr.path, path); | ||
| 110 | |||
| 111 | return Address{ .un = sock_addr }; | ||
| 112 | } | ||
| 113 | |||
| 114 | /// Returns the port in native endian. | ||
| 115 | /// Asserts that the address is ip4 or ip6. | ||
| 116 | pub fn getPort(self: Address) u16 { | ||
| 117 | return switch (self.any.family) { | ||
| 118 | os.AF_INET => self.in.getPort(), | ||
| 119 | os.AF_INET6 => self.in6.getPort(), | ||
| 120 | else => unreachable, | ||
| 121 | }; | ||
| 122 | } | ||
| 123 | |||
| 124 | /// `port` is native-endian. | ||
| 125 | /// Asserts that the address is ip4 or ip6. | ||
| 126 | pub fn setPort(self: *Address, port: u16) void { | ||
| 127 | switch (self.any.family) { | ||
| 128 | os.AF_INET => self.in.setPort(port), | ||
| 129 | os.AF_INET6 => self.in6.setPort(port), | ||
| 130 | else => unreachable, | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | /// Asserts that `addr` is an IP address. | ||
| 135 | /// This function will read past the end of the pointer, with a size depending | ||
| 136 | /// on the address family. | ||
| 137 | pub fn initPosix(addr: *align(4) const os.sockaddr) Address { | ||
| 138 | switch (addr.family) { | ||
| 139 | os.AF_INET => return Address{ .in = Ip4Address{ .sa = @ptrCast(*const os.sockaddr_in, addr).*} }, | ||
| 140 | os.AF_INET6 => return Address{ .in6 = Ip6Address{ .sa = @ptrCast(*const os.sockaddr_in6, addr).*} }, | ||
| 141 | else => unreachable, | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | pub fn format( | ||
| 146 | self: Address, | ||
| 147 | comptime fmt: []const u8, | ||
| 148 | options: std.fmt.FormatOptions, | ||
| 149 | out_stream: anytype, | ||
| 150 | ) !void { | ||
| 151 | switch (self.any.family) { | ||
| 152 | os.AF_INET => try self.in.format(fmt, options, out_stream), | ||
| 153 | os.AF_INET6 => try self.in6.format(fmt, options, out_stream), | ||
| 154 | os.AF_UNIX => { | ||
| 155 | if (!has_unix_sockets) { | ||
| 156 | unreachable; | ||
| 157 | } | ||
| 158 | |||
| 159 | try std.fmt.format(out_stream, "{}", .{&self.un.path}); | ||
| 160 | }, | ||
| 161 | else => unreachable, | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | pub fn eql(a: Address, b: Address) bool { | ||
| 166 | const a_bytes = @ptrCast([*]const u8, &a.any)[0..a.getOsSockLen()]; | ||
| 167 | const b_bytes = @ptrCast([*]const u8, &b.any)[0..b.getOsSockLen()]; | ||
| 168 | return mem.eql(u8, a_bytes, b_bytes); | ||
| 169 | } | ||
| 170 | |||
| 171 | pub fn getOsSockLen(self: Address) os.socklen_t { | ||
| 172 | switch (self.any.family) { | ||
| 173 | os.AF_INET => return self.in.getOsSockLen(), | ||
| 174 | os.AF_INET6 => return self.in6.getOsSockLen(), | ||
| 175 | os.AF_UNIX => { | ||
| 176 | if (!has_unix_sockets) { | ||
| 177 | unreachable; | ||
| 178 | } | ||
| 179 | |||
| 180 | const path_len = std.mem.len(@ptrCast([*:0]const u8, &self.un.path)); | ||
| 181 | return @intCast(os.socklen_t, @sizeOf(os.sockaddr_un) - self.un.path.len + path_len); | ||
| 182 | }, | ||
| 183 | else => unreachable, | ||
| 184 | } | ||
| 185 | } | ||
| 186 | }; | ||
| 187 | |||
| 188 | pub const Ip4Address = extern struct { | ||
| 189 | sa: os.sockaddr_in, | ||
| 190 | |||
| 191 | pub fn parse(buf: []const u8, port: u16) !Ip4Address { | ||
| 192 | var result = Ip4Address{ | ||
| 193 | .sa = .{ | ||
| 194 | .port = mem.nativeToBig(u16, port), | ||
| 195 | .addr = undefined, | ||
| 196 | } | ||
| 197 | }; | ||
| 198 | const out_ptr = mem.sliceAsBytes(@as(*[1]u32, &result.sa.addr)[0..]); | ||
| 199 | |||
| 200 | var x: u8 = 0; | ||
| 201 | var index: u8 = 0; | ||
| 202 | var saw_any_digits = false; | ||
| 203 | for (buf) |c| { | ||
| 204 | if (c == '.') { | ||
| 205 | if (!saw_any_digits) { | ||
| 206 | return error.InvalidCharacter; | ||
| 207 | } | ||
| 208 | if (index == 3) { | ||
| 209 | return error.InvalidEnd; | ||
| 210 | } | ||
| 211 | out_ptr[index] = x; | ||
| 212 | index += 1; | ||
| 213 | x = 0; | ||
| 214 | saw_any_digits = false; | ||
| 215 | } else if (c >= '0' and c <= '9') { | ||
| 216 | saw_any_digits = true; | ||
| 217 | x = try std.math.mul(u8, x, 10); | ||
| 218 | x = try std.math.add(u8, x, c - '0'); | ||
| 219 | } else { | ||
| 220 | return error.InvalidCharacter; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | if (index == 3 and saw_any_digits) { | ||
| 224 | out_ptr[index] = x; | ||
| 225 | return result; | ||
| 226 | } | ||
| 227 | |||
| 228 | return error.Incomplete; | ||
| 229 | } | ||
| 230 | |||
| 231 | pub fn resolveIp(name: []const u8, port: u16) !Ip4Address { | ||
| 232 | if (parse(name, port)) |ip4| return ip4 else |err| switch (err) { | ||
| 233 | error.Overflow, | ||
| 234 | error.InvalidEnd, | ||
| 235 | error.InvalidCharacter, | ||
| 236 | error.Incomplete, | ||
| 237 | => {}, | ||
| 238 | } | ||
| 239 | return error.InvalidIPAddressFormat; | ||
| 240 | } | ||
| 241 | |||
| 242 | pub fn init(addr: [4]u8, port: u16) Ip4Address { | ||
| 243 | return Ip4Address { | ||
| 244 | .sa = os.sockaddr_in{ | ||
| 245 | .port = mem.nativeToBig(u16, port), | ||
| 246 | .addr = @ptrCast(*align(1) const u32, &addr).*, | ||
| 247 | }, | ||
| 248 | }; | ||
| 249 | } | ||
| 250 | |||
| 251 | /// Returns the port in native endian. | ||
| 252 | /// Asserts that the address is ip4 or ip6. | ||
| 253 | pub fn getPort(self: Ip4Address) u16 { | ||
| 254 | return mem.bigToNative(u16, self.sa.port); | ||
| 255 | } | ||
| 256 | |||
| 257 | /// `port` is native-endian. | ||
| 258 | /// Asserts that the address is ip4 or ip6. | ||
| 259 | pub fn setPort(self: *Ip4Address, port: u16) void { | ||
| 260 | self.sa.port = mem.nativeToBig(u16, port); | ||
| 261 | } | ||
| 262 | |||
| 263 | pub fn format( | ||
| 264 | self: Ip4Address, | ||
| 265 | comptime fmt: []const u8, | ||
| 266 | options: std.fmt.FormatOptions, | ||
| 267 | out_stream: anytype, | ||
| 268 | ) !void { | ||
| 269 | const bytes = @ptrCast(*const [4]u8, &self.sa.addr); | ||
| 270 | try std.fmt.format(out_stream, "{}.{}.{}.{}:{}", .{ | ||
| 271 | bytes[0], | ||
| 272 | bytes[1], | ||
| 273 | bytes[2], | ||
| 274 | bytes[3], | ||
| 275 | self.getPort(), | ||
| 276 | }); | ||
| 277 | } | ||
| 278 | |||
| 279 | pub fn getOsSockLen(self: Ip4Address) os.socklen_t { | ||
| 280 | return @sizeOf(os.sockaddr_in); | ||
| 281 | } | ||
| 282 | }; | ||
| 283 | |||
| 284 | pub const Ip6Address = extern struct { | ||
| 285 | sa: os.sockaddr_in6, | ||
| 286 | |||
| 79 | /// Parse a given IPv6 address string into an Address. | 287 | /// Parse a given IPv6 address string into an Address. |
| 80 | /// Assumes the Scope ID of the address is fully numeric. | 288 | /// Assumes the Scope ID of the address is fully numeric. |
| 81 | /// For non-numeric addresses, see `resolveIp6`. | 289 | /// For non-numeric addresses, see `resolveIp6`. |
| 82 | pub fn parseIp6(buf: []const u8, port: u16) !Address { | 290 | pub fn parse(buf: []const u8, port: u16) !Ip6Address { |
| 83 | var result = Address{ | 291 | var result = Ip6Address{ |
| 84 | .in6 = os.sockaddr_in6{ | 292 | .sa = os.sockaddr_in6{ |
| 85 | .scope_id = 0, | 293 | .scope_id = 0, |
| 86 | .port = mem.nativeToBig(u16, port), | 294 | .port = mem.nativeToBig(u16, port), |
| 87 | .flowinfo = 0, | 295 | .flowinfo = 0, |
| 88 | .addr = undefined, | 296 | .addr = undefined, |
| 89 | }, | 297 | }, |
| 90 | }; | 298 | }; |
| 91 | var ip_slice = result.in6.addr[0..]; | 299 | var ip_slice = result.sa.addr[0..]; |
| 92 | 300 | ||
| 93 | var tail: [16]u8 = undefined; | 301 | var tail: [16]u8 = undefined; |
| 94 | 302 | ||
| ... | @@ -101,10 +309,10 @@ pub const Address = extern union { | ... | @@ -101,10 +309,10 @@ pub const Address = extern union { |
| 101 | if (scope_id) { | 309 | if (scope_id) { |
| 102 | if (c >= '0' and c <= '9') { | 310 | if (c >= '0' and c <= '9') { |
| 103 | const digit = c - '0'; | 311 | const digit = c - '0'; |
| 104 | if (@mulWithOverflow(u32, result.in6.scope_id, 10, &result.in6.scope_id)) { | 312 | if (@mulWithOverflow(u32, result.sa.scope_id, 10, &result.sa.scope_id)) { |
| 105 | return error.Overflow; | 313 | return error.Overflow; |
| 106 | } | 314 | } |
| 107 | if (@addWithOverflow(u32, result.in6.scope_id, digit, &result.in6.scope_id)) { | 315 | if (@addWithOverflow(u32, result.sa.scope_id, digit, &result.sa.scope_id)) { |
| 108 | return error.Overflow; | 316 | return error.Overflow; |
| 109 | } | 317 | } |
| 110 | } else { | 318 | } else { |
| ... | @@ -141,10 +349,10 @@ pub const Address = extern union { | ... | @@ -141,10 +349,10 @@ pub const Address = extern union { |
| 141 | return error.InvalidIpv4Mapping; | 349 | return error.InvalidIpv4Mapping; |
| 142 | } | 350 | } |
| 143 | const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1; | 351 | const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1; |
| 144 | const addr = (parseIp4(buf[start_index..], 0) catch { | 352 | const addr = (Ip4Address.parse(buf[start_index..], 0) catch { |
| 145 | return error.InvalidIpv4Mapping; | 353 | return error.InvalidIpv4Mapping; |
| 146 | }).in.addr; | 354 | }).sa.addr; |
| 147 | ip_slice = result.in6.addr[0..]; | 355 | ip_slice = result.sa.addr[0..]; |
| 148 | ip_slice[10] = 0xff; | 356 | ip_slice[10] = 0xff; |
| 149 | ip_slice[11] = 0xff; | 357 | ip_slice[11] = 0xff; |
| 150 | 358 | ||
| ... | @@ -180,22 +388,22 @@ pub const Address = extern union { | ... | @@ -180,22 +388,22 @@ pub const Address = extern union { |
| 180 | index += 1; | 388 | index += 1; |
| 181 | ip_slice[index] = @truncate(u8, x); | 389 | ip_slice[index] = @truncate(u8, x); |
| 182 | index += 1; | 390 | index += 1; |
| 183 | mem.copy(u8, result.in6.addr[16 - index ..], ip_slice[0..index]); | 391 | mem.copy(u8, result.sa.addr[16 - index ..], ip_slice[0..index]); |
| 184 | return result; | 392 | return result; |
| 185 | } | 393 | } |
| 186 | } | 394 | } |
| 187 | 395 | ||
| 188 | pub fn resolveIp6(buf: []const u8, port: u16) !Address { | 396 | pub fn resolve(buf: []const u8, port: u16) !Ip6Address { |
| 189 | // TODO: Unify the implementations of resolveIp6 and parseIp6. | 397 | // TODO: Unify the implementations of resolveIp6 and parseIp6. |
| 190 | var result = Address{ | 398 | var result = Ip6Address{ |
| 191 | .in6 = os.sockaddr_in6{ | 399 | .sa = os.sockaddr_in6{ |
| 192 | .scope_id = 0, | 400 | .scope_id = 0, |
| 193 | .port = mem.nativeToBig(u16, port), | 401 | .port = mem.nativeToBig(u16, port), |
| 194 | .flowinfo = 0, | 402 | .flowinfo = 0, |
| 195 | .addr = undefined, | 403 | .addr = undefined, |
| 196 | }, | 404 | }, |
| 197 | }; | 405 | }; |
| 198 | var ip_slice = result.in6.addr[0..]; | 406 | var ip_slice = result.sa.addr[0..]; |
| 199 | 407 | ||
| 200 | var tail: [16]u8 = undefined; | 408 | var tail: [16]u8 = undefined; |
| 201 | 409 | ||
| ... | @@ -256,10 +464,10 @@ pub const Address = extern union { | ... | @@ -256,10 +464,10 @@ pub const Address = extern union { |
| 256 | return error.InvalidIpv4Mapping; | 464 | return error.InvalidIpv4Mapping; |
| 257 | } | 465 | } |
| 258 | const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1; | 466 | const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1; |
| 259 | const addr = (parseIp4(buf[start_index..], 0) catch { | 467 | const addr = (Ip4Address.parse(buf[start_index..], 0) catch { |
| 260 | return error.InvalidIpv4Mapping; | 468 | return error.InvalidIpv4Mapping; |
| 261 | }).in.addr; | 469 | }).sa.addr; |
| 262 | ip_slice = result.in6.addr[0..]; | 470 | ip_slice = result.sa.addr[0..]; |
| 263 | ip_slice[10] = 0xff; | 471 | ip_slice[10] = 0xff; |
| 264 | ip_slice[11] = 0xff; | 472 | ip_slice[11] = 0xff; |
| 265 | 473 | ||
| ... | @@ -299,7 +507,7 @@ pub const Address = extern union { | ... | @@ -299,7 +507,7 @@ pub const Address = extern union { |
| 299 | }; | 507 | }; |
| 300 | } | 508 | } |
| 301 | 509 | ||
| 302 | result.in6.scope_id = resolved_scope_id; | 510 | result.sa.scope_id = resolved_scope_id; |
| 303 | 511 | ||
| 304 | if (index == 14) { | 512 | if (index == 14) { |
| 305 | ip_slice[14] = @truncate(u8, x >> 8); | 513 | ip_slice[14] = @truncate(u8, x >> 8); |
| ... | @@ -310,63 +518,14 @@ pub const Address = extern union { | ... | @@ -310,63 +518,14 @@ pub const Address = extern union { |
| 310 | index += 1; | 518 | index += 1; |
| 311 | ip_slice[index] = @truncate(u8, x); | 519 | ip_slice[index] = @truncate(u8, x); |
| 312 | index += 1; | 520 | index += 1; |
| 313 | mem.copy(u8, result.in6.addr[16 - index ..], ip_slice[0..index]); | 521 | mem.copy(u8, result.sa.addr[16 - index ..], ip_slice[0..index]); |
| 314 | return result; | ||
| 315 | } | ||
| 316 | } | ||
| 317 | |||
| 318 | pub fn parseIp4(buf: []const u8, port: u16) !Address { | ||
| 319 | var result = Address{ | ||
| 320 | .in = os.sockaddr_in{ | ||
| 321 | .port = mem.nativeToBig(u16, port), | ||
| 322 | .addr = undefined, | ||
| 323 | }, | ||
| 324 | }; | ||
| 325 | const out_ptr = mem.sliceAsBytes(@as(*[1]u32, &result.in.addr)[0..]); | ||
| 326 | |||
| 327 | var x: u8 = 0; | ||
| 328 | var index: u8 = 0; | ||
| 329 | var saw_any_digits = false; | ||
| 330 | for (buf) |c| { | ||
| 331 | if (c == '.') { | ||
| 332 | if (!saw_any_digits) { | ||
| 333 | return error.InvalidCharacter; | ||
| 334 | } | ||
| 335 | if (index == 3) { | ||
| 336 | return error.InvalidEnd; | ||
| 337 | } | ||
| 338 | out_ptr[index] = x; | ||
| 339 | index += 1; | ||
| 340 | x = 0; | ||
| 341 | saw_any_digits = false; | ||
| 342 | } else if (c >= '0' and c <= '9') { | ||
| 343 | saw_any_digits = true; | ||
| 344 | x = try std.math.mul(u8, x, 10); | ||
| 345 | x = try std.math.add(u8, x, c - '0'); | ||
| 346 | } else { | ||
| 347 | return error.InvalidCharacter; | ||
| 348 | } | ||
| 349 | } | ||
| 350 | if (index == 3 and saw_any_digits) { | ||
| 351 | out_ptr[index] = x; | ||
| 352 | return result; | 522 | return result; |
| 353 | } | 523 | } |
| 354 | |||
| 355 | return error.Incomplete; | ||
| 356 | } | 524 | } |
| 357 | 525 | ||
| 358 | pub fn initIp4(addr: [4]u8, port: u16) Address { | 526 | pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address { |
| 359 | return Address{ | 527 | return Ip6Address{ |
| 360 | .in = os.sockaddr_in{ | 528 | .sa = os.sockaddr_in6{ |
| 361 | .port = mem.nativeToBig(u16, port), | ||
| 362 | .addr = @ptrCast(*align(1) const u32, &addr).*, | ||
| 363 | }, | ||
| 364 | }; | ||
| 365 | } | ||
| 366 | |||
| 367 | pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address { | ||
| 368 | return Address{ | ||
| 369 | .in6 = os.sockaddr_in6{ | ||
| 370 | .addr = addr, | 529 | .addr = addr, |
| 371 | .port = mem.nativeToBig(u16, port), | 530 | .port = mem.nativeToBig(u16, port), |
| 372 | .flowinfo = flowinfo, | 531 | .flowinfo = flowinfo, |
| ... | @@ -375,147 +534,71 @@ pub const Address = extern union { | ... | @@ -375,147 +534,71 @@ pub const Address = extern union { |
| 375 | }; | 534 | }; |
| 376 | } | 535 | } |
| 377 | 536 | ||
| 378 | pub fn initUnix(path: []const u8) !Address { | ||
| 379 | var sock_addr = os.sockaddr_un{ | ||
| 380 | .family = os.AF_UNIX, | ||
| 381 | .path = undefined, | ||
| 382 | }; | ||
| 383 | |||
| 384 | // this enables us to have the proper length of the socket in getOsSockLen | ||
| 385 | mem.set(u8, &sock_addr.path, 0); | ||
| 386 | |||
| 387 | if (path.len > sock_addr.path.len) return error.NameTooLong; | ||
| 388 | mem.copy(u8, &sock_addr.path, path); | ||
| 389 | |||
| 390 | return Address{ .un = sock_addr }; | ||
| 391 | } | ||
| 392 | |||
| 393 | /// Returns the port in native endian. | 537 | /// Returns the port in native endian. |
| 394 | /// Asserts that the address is ip4 or ip6. | 538 | /// Asserts that the address is ip4 or ip6. |
| 395 | pub fn getPort(self: Address) u16 { | 539 | pub fn getPort(self: Ip6Address) u16 { |
| 396 | const big_endian_port = switch (self.any.family) { | 540 | return mem.bigToNative(u16, self.sa.port); |
| 397 | os.AF_INET => self.in.port, | ||
| 398 | os.AF_INET6 => self.in6.port, | ||
| 399 | else => unreachable, | ||
| 400 | }; | ||
| 401 | return mem.bigToNative(u16, big_endian_port); | ||
| 402 | } | 541 | } |
| 403 | 542 | ||
| 404 | /// `port` is native-endian. | 543 | /// `port` is native-endian. |
| 405 | /// Asserts that the address is ip4 or ip6. | 544 | /// Asserts that the address is ip4 or ip6. |
| 406 | pub fn setPort(self: *Address, port: u16) void { | 545 | pub fn setPort(self: *Ip6Address, port: u16) void { |
| 407 | const ptr = switch (self.any.family) { | 546 | self.sa.port = mem.nativeToBig(u16, port); |
| 408 | os.AF_INET => &self.in.port, | ||
| 409 | os.AF_INET6 => &self.in6.port, | ||
| 410 | else => unreachable, | ||
| 411 | }; | ||
| 412 | ptr.* = mem.nativeToBig(u16, port); | ||
| 413 | } | ||
| 414 | |||
| 415 | /// Asserts that `addr` is an IP address. | ||
| 416 | /// This function will read past the end of the pointer, with a size depending | ||
| 417 | /// on the address family. | ||
| 418 | pub fn initPosix(addr: *align(4) const os.sockaddr) Address { | ||
| 419 | switch (addr.family) { | ||
| 420 | os.AF_INET => return Address{ .in = @ptrCast(*const os.sockaddr_in, addr).* }, | ||
| 421 | os.AF_INET6 => return Address{ .in6 = @ptrCast(*const os.sockaddr_in6, addr).* }, | ||
| 422 | else => unreachable, | ||
| 423 | } | ||
| 424 | } | 547 | } |
| 425 | 548 | ||
| 426 | pub fn format( | 549 | pub fn format( |
| 427 | self: Address, | 550 | self: Ip6Address, |
| 428 | comptime fmt: []const u8, | 551 | comptime fmt: []const u8, |
| 429 | options: std.fmt.FormatOptions, | 552 | options: std.fmt.FormatOptions, |
| 430 | out_stream: anytype, | 553 | out_stream: anytype, |
| 431 | ) !void { | 554 | ) !void { |
| 432 | switch (self.any.family) { | 555 | const port = mem.bigToNative(u16, self.sa.port); |
| 433 | os.AF_INET => { | 556 | if (mem.eql(u8, self.sa.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { |
| 434 | const port = mem.bigToNative(u16, self.in.port); | 557 | try std.fmt.format(out_stream, "[::ffff:{}.{}.{}.{}]:{}", .{ |
| 435 | const bytes = @ptrCast(*const [4]u8, &self.in.addr); | 558 | self.sa.addr[12], |
| 436 | try std.fmt.format(out_stream, "{}.{}.{}.{}:{}", .{ | 559 | self.sa.addr[13], |
| 437 | bytes[0], | 560 | self.sa.addr[14], |
| 438 | bytes[1], | 561 | self.sa.addr[15], |
| 439 | bytes[2], | 562 | port, |
| 440 | bytes[3], | 563 | }); |
| 441 | port, | 564 | return; |
| 442 | }); | 565 | } |
| 443 | }, | 566 | const big_endian_parts = @ptrCast(*align(1) const [8]u16, &self.sa.addr); |
| 444 | os.AF_INET6 => { | 567 | const native_endian_parts = switch (builtin.endian) { |
| 445 | const port = mem.bigToNative(u16, self.in6.port); | 568 | .Big => big_endian_parts.*, |
| 446 | if (mem.eql(u8, self.in6.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { | 569 | .Little => blk: { |
| 447 | try std.fmt.format(out_stream, "[::ffff:{}.{}.{}.{}]:{}", .{ | 570 | var buf: [8]u16 = undefined; |
| 448 | self.in6.addr[12], | 571 | for (big_endian_parts) |part, i| { |
| 449 | self.in6.addr[13], | 572 | buf[i] = mem.bigToNative(u16, part); |
| 450 | self.in6.addr[14], | ||
| 451 | self.in6.addr[15], | ||
| 452 | port, | ||
| 453 | }); | ||
| 454 | return; | ||
| 455 | } | ||
| 456 | const big_endian_parts = @ptrCast(*align(1) const [8]u16, &self.in6.addr); | ||
| 457 | const native_endian_parts = switch (builtin.endian) { | ||
| 458 | .Big => big_endian_parts.*, | ||
| 459 | .Little => blk: { | ||
| 460 | var buf: [8]u16 = undefined; | ||
| 461 | for (big_endian_parts) |part, i| { | ||
| 462 | buf[i] = mem.bigToNative(u16, part); | ||
| 463 | } | ||
| 464 | break :blk buf; | ||
| 465 | }, | ||
| 466 | }; | ||
| 467 | try out_stream.writeAll("["); | ||
| 468 | var i: usize = 0; | ||
| 469 | var abbrv = false; | ||
| 470 | while (i < native_endian_parts.len) : (i += 1) { | ||
| 471 | if (native_endian_parts[i] == 0) { | ||
| 472 | if (!abbrv) { | ||
| 473 | try out_stream.writeAll(if (i == 0) "::" else ":"); | ||
| 474 | abbrv = true; | ||
| 475 | } | ||
| 476 | continue; | ||
| 477 | } | ||
| 478 | try std.fmt.format(out_stream, "{x}", .{native_endian_parts[i]}); | ||
| 479 | if (i != native_endian_parts.len - 1) { | ||
| 480 | try out_stream.writeAll(":"); | ||
| 481 | } | ||
| 482 | } | 573 | } |
| 483 | try std.fmt.format(out_stream, "]:{}", .{port}); | 574 | break :blk buf; |
| 484 | }, | 575 | }, |
| 485 | os.AF_UNIX => { | 576 | }; |
| 486 | if (!has_unix_sockets) { | 577 | try out_stream.writeAll("["); |
| 487 | unreachable; | 578 | var i: usize = 0; |
| 579 | var abbrv = false; | ||
| 580 | while (i < native_endian_parts.len) : (i += 1) { | ||
| 581 | if (native_endian_parts[i] == 0) { | ||
| 582 | if (!abbrv) { | ||
| 583 | try out_stream.writeAll(if (i == 0) "::" else ":"); | ||
| 584 | abbrv = true; | ||
| 488 | } | 585 | } |
| 489 | 586 | continue; | |
| 490 | try std.fmt.format(out_stream, "{}", .{&self.un.path}); | 587 | } |
| 491 | }, | 588 | try std.fmt.format(out_stream, "{x}", .{native_endian_parts[i]}); |
| 492 | else => unreachable, | 589 | if (i != native_endian_parts.len - 1) { |
| 590 | try out_stream.writeAll(":"); | ||
| 591 | } | ||
| 493 | } | 592 | } |
| 593 | try std.fmt.format(out_stream, "]:{}", .{port}); | ||
| 494 | } | 594 | } |
| 495 | 595 | ||
| 496 | pub fn eql(a: Address, b: Address) bool { | 596 | pub fn getOsSockLen(self: Ip6Address) os.socklen_t { |
| 497 | const a_bytes = @ptrCast([*]const u8, &a.any)[0..a.getOsSockLen()]; | 597 | return @sizeOf(os.sockaddr_in6); |
| 498 | const b_bytes = @ptrCast([*]const u8, &b.any)[0..b.getOsSockLen()]; | ||
| 499 | return mem.eql(u8, a_bytes, b_bytes); | ||
| 500 | } | ||
| 501 | |||
| 502 | pub fn getOsSockLen(self: Address) os.socklen_t { | ||
| 503 | switch (self.any.family) { | ||
| 504 | os.AF_INET => return @sizeOf(os.sockaddr_in), | ||
| 505 | os.AF_INET6 => return @sizeOf(os.sockaddr_in6), | ||
| 506 | os.AF_UNIX => { | ||
| 507 | if (!has_unix_sockets) { | ||
| 508 | unreachable; | ||
| 509 | } | ||
| 510 | |||
| 511 | const path_len = std.mem.len(@ptrCast([*:0]const u8, &self.un.path)); | ||
| 512 | return @intCast(os.socklen_t, @sizeOf(os.sockaddr_un) - self.un.path.len + path_len); | ||
| 513 | }, | ||
| 514 | else => unreachable, | ||
| 515 | } | ||
| 516 | } | 598 | } |
| 517 | }; | 599 | }; |
| 518 | 600 | ||
| 601 | |||
| 519 | pub fn connectUnixSocket(path: []const u8) !fs.File { | 602 | pub fn connectUnixSocket(path: []const u8) !fs.File { |
| 520 | const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0; | 603 | const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0; |
| 521 | const sockfd = try os.socket( | 604 | const sockfd = try os.socket( |
| ... | @@ -777,7 +860,7 @@ fn linuxLookupName( | ... | @@ -777,7 +860,7 @@ fn linuxLookupName( |
| 777 | @memset(@ptrCast([*]u8, &sa6), 0, @sizeOf(os.sockaddr_in6)); | 860 | @memset(@ptrCast([*]u8, &sa6), 0, @sizeOf(os.sockaddr_in6)); |
| 778 | var da6 = os.sockaddr_in6{ | 861 | var da6 = os.sockaddr_in6{ |
| 779 | .family = os.AF_INET6, | 862 | .family = os.AF_INET6, |
| 780 | .scope_id = addr.addr.in6.scope_id, | 863 | .scope_id = addr.addr.in6.sa.scope_id, |
| 781 | .port = 65535, | 864 | .port = 65535, |
| 782 | .flowinfo = 0, | 865 | .flowinfo = 0, |
| 783 | .addr = [1]u8{0} ** 16, | 866 | .addr = [1]u8{0} ** 16, |
| ... | @@ -795,7 +878,7 @@ fn linuxLookupName( | ... | @@ -795,7 +878,7 @@ fn linuxLookupName( |
| 795 | var salen: os.socklen_t = undefined; | 878 | var salen: os.socklen_t = undefined; |
| 796 | var dalen: os.socklen_t = undefined; | 879 | var dalen: os.socklen_t = undefined; |
| 797 | if (addr.addr.any.family == os.AF_INET6) { | 880 | if (addr.addr.any.family == os.AF_INET6) { |
| 798 | mem.copy(u8, &da6.addr, &addr.addr.in6.addr); | 881 | mem.copy(u8, &da6.addr, &addr.addr.in6.sa.addr); |
| 799 | da = @ptrCast(*os.sockaddr, &da6); | 882 | da = @ptrCast(*os.sockaddr, &da6); |
| 800 | dalen = @sizeOf(os.sockaddr_in6); | 883 | dalen = @sizeOf(os.sockaddr_in6); |
| 801 | sa = @ptrCast(*os.sockaddr, &sa6); | 884 | sa = @ptrCast(*os.sockaddr, &sa6); |
| ... | @@ -803,8 +886,8 @@ fn linuxLookupName( | ... | @@ -803,8 +886,8 @@ fn linuxLookupName( |
| 803 | } else { | 886 | } else { |
| 804 | mem.copy(u8, &sa6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff"); | 887 | mem.copy(u8, &sa6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff"); |
| 805 | mem.copy(u8, &da6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff"); | 888 | mem.copy(u8, &da6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff"); |
| 806 | mem.writeIntNative(u32, da6.addr[12..], addr.addr.in.addr); | 889 | mem.writeIntNative(u32, da6.addr[12..], addr.addr.in.sa.addr); |
| 807 | da4.addr = addr.addr.in.addr; | 890 | da4.addr = addr.addr.in.sa.addr; |
| 808 | da = @ptrCast(*os.sockaddr, &da4); | 891 | da = @ptrCast(*os.sockaddr, &da4); |
| 809 | dalen = @sizeOf(os.sockaddr_in); | 892 | dalen = @sizeOf(os.sockaddr_in); |
| 810 | sa = @ptrCast(*os.sockaddr, &sa4); | 893 | sa = @ptrCast(*os.sockaddr, &sa4); |
lib/std/os.zig+176-106| ... | @@ -1041,6 +1041,9 @@ pub const OpenError = error{ | ... | @@ -1041,6 +1041,9 @@ pub const OpenError = error{ |
| 1041 | 1041 | ||
| 1042 | /// The underlying filesystem does not support file locks | 1042 | /// The underlying filesystem does not support file locks |
| 1043 | FileLocksNotSupported, | 1043 | FileLocksNotSupported, |
| 1044 | |||
| 1045 | BadPathName, | ||
| 1046 | InvalidUtf8, | ||
| 1044 | } || UnexpectedError; | 1047 | } || UnexpectedError; |
| 1045 | 1048 | ||
| 1046 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | 1049 | /// 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 | ... | @@ -1092,18 +1095,65 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t |
| 1092 | } | 1095 | } |
| 1093 | } | 1096 | } |
| 1094 | 1097 | ||
| 1098 | fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions { | ||
| 1099 | const w = windows; | ||
| 1100 | |||
| 1101 | var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE; | ||
| 1102 | if (flags & O_RDWR != 0) { | ||
| 1103 | access_mask |= w.GENERIC_READ | w.GENERIC_WRITE; | ||
| 1104 | } else if (flags & O_WRONLY != 0) { | ||
| 1105 | access_mask |= w.GENERIC_WRITE; | ||
| 1106 | } else { | ||
| 1107 | access_mask |= w.GENERIC_READ | w.GENERIC_WRITE; | ||
| 1108 | } | ||
| 1109 | |||
| 1110 | const open_dir: bool = flags & O_DIRECTORY != 0; | ||
| 1111 | const follow_symlinks: bool = flags & O_NOFOLLOW == 0; | ||
| 1112 | |||
| 1113 | const creation: w.ULONG = blk: { | ||
| 1114 | if (flags & O_CREAT != 0) { | ||
| 1115 | if (flags & O_EXCL != 0) { | ||
| 1116 | break :blk w.FILE_CREATE; | ||
| 1117 | } | ||
| 1118 | } | ||
| 1119 | break :blk w.FILE_OPEN; | ||
| 1120 | }; | ||
| 1121 | |||
| 1122 | return .{ | ||
| 1123 | .access_mask = access_mask, | ||
| 1124 | .io_mode = .blocking, | ||
| 1125 | .creation = creation, | ||
| 1126 | .open_dir = open_dir, | ||
| 1127 | .follow_symlinks = follow_symlinks, | ||
| 1128 | }; | ||
| 1129 | } | ||
| 1130 | |||
| 1095 | /// Windows-only. The path parameter is | 1131 | /// Windows-only. The path parameter is |
| 1096 | /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. | 1132 | /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. |
| 1097 | /// Translates the POSIX open API call to a Windows API call. | 1133 | /// Translates the POSIX open API call to a Windows API call. |
| 1098 | pub fn openW(file_path_w: []const u16, flags: u32, perm: usize) OpenError!fd_t { | 1134 | /// TODO currently, this function does not handle all flag combinations |
| 1099 | @compileError("TODO implement openW for windows"); | 1135 | /// or makes use of perm argument. |
| 1136 | pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t { | ||
| 1137 | var options = openOptionsFromFlags(flags); | ||
| 1138 | options.dir = std.fs.cwd().fd; | ||
| 1139 | return windows.OpenFile(file_path_w, options) catch |err| switch (err) { | ||
| 1140 | error.WouldBlock => unreachable, | ||
| 1141 | error.PipeBusy => unreachable, | ||
| 1142 | else => |e| return e, | ||
| 1143 | }; | ||
| 1100 | } | 1144 | } |
| 1101 | 1145 | ||
| 1102 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | 1146 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1103 | /// `file_path` is relative to the open directory handle `dir_fd`. | 1147 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1104 | /// See also `openatC`. | 1148 | /// See also `openatC`. |
| 1105 | /// TODO support windows | ||
| 1106 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t { | 1149 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t { |
| 1150 | if (builtin.os.tag == .wasi) { | ||
| 1151 | @compileError("use openatWasi instead"); | ||
| 1152 | } | ||
| 1153 | if (builtin.os.tag == .windows) { | ||
| 1154 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); | ||
| 1155 | return openatW(dir_fd, file_path_w.span(), flags, mode); | ||
| 1156 | } | ||
| 1107 | const file_path_c = try toPosixPath(file_path); | 1157 | const file_path_c = try toPosixPath(file_path); |
| 1108 | return openatZ(dir_fd, &file_path_c, flags, mode); | 1158 | return openatZ(dir_fd, &file_path_c, flags, mode); |
| 1109 | } | 1159 | } |
| ... | @@ -1145,8 +1195,11 @@ pub const openatC = @compileError("deprecated: renamed to openatZ"); | ... | @@ -1145,8 +1195,11 @@ pub const openatC = @compileError("deprecated: renamed to openatZ"); |
| 1145 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | 1195 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1146 | /// `file_path` is relative to the open directory handle `dir_fd`. | 1196 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1147 | /// See also `openat`. | 1197 | /// See also `openat`. |
| 1148 | /// TODO support windows | ||
| 1149 | pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t { | 1198 | pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t { |
| 1199 | if (builtin.os.tag == .windows) { | ||
| 1200 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); | ||
| 1201 | return openatW(dir_fd, file_path_w.span(), flags, mode); | ||
| 1202 | } | ||
| 1150 | while (true) { | 1203 | while (true) { |
| 1151 | const rc = system.openat(dir_fd, file_path, flags, mode); | 1204 | const rc = system.openat(dir_fd, file_path, flags, mode); |
| 1152 | switch (errno(rc)) { | 1205 | switch (errno(rc)) { |
| ... | @@ -1177,6 +1230,20 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) | ... | @@ -1177,6 +1230,20 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) |
| 1177 | } | 1230 | } |
| 1178 | } | 1231 | } |
| 1179 | 1232 | ||
| 1233 | /// Windows-only. Similar to `openat` but with pathname argument null-terminated | ||
| 1234 | /// WTF16 encoded. | ||
| 1235 | /// TODO currently, this function does not handle all flag combinations | ||
| 1236 | /// or makes use of perm argument. | ||
| 1237 | pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t { | ||
| 1238 | var options = openOptionsFromFlags(flags); | ||
| 1239 | options.dir = dir_fd; | ||
| 1240 | return windows.OpenFile(file_path_w, options) catch |err| switch (err) { | ||
| 1241 | error.WouldBlock => unreachable, | ||
| 1242 | error.PipeBusy => unreachable, | ||
| 1243 | else => |e| return e, | ||
| 1244 | }; | ||
| 1245 | } | ||
| 1246 | |||
| 1180 | pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { | 1247 | pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { |
| 1181 | while (true) { | 1248 | while (true) { |
| 1182 | switch (errno(system.dup2(old_fd, new_fd))) { | 1249 | switch (errno(system.dup2(old_fd, new_fd))) { |
| ... | @@ -1683,7 +1750,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void { | ... | @@ -1683,7 +1750,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void { |
| 1683 | @compileError("unlink is not supported in WASI; use unlinkat instead"); | 1750 | @compileError("unlink is not supported in WASI; use unlinkat instead"); |
| 1684 | } else if (builtin.os.tag == .windows) { | 1751 | } else if (builtin.os.tag == .windows) { |
| 1685 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); | 1752 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 1686 | return windows.DeleteFileW(file_path_w.span().ptr); | 1753 | return unlinkW(file_path_w.span()); |
| 1687 | } else { | 1754 | } else { |
| 1688 | const file_path_c = try toPosixPath(file_path); | 1755 | const file_path_c = try toPosixPath(file_path); |
| 1689 | return unlinkZ(&file_path_c); | 1756 | return unlinkZ(&file_path_c); |
| ... | @@ -1696,7 +1763,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); | ... | @@ -1696,7 +1763,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); |
| 1696 | pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { | 1763 | pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { |
| 1697 | if (builtin.os.tag == .windows) { | 1764 | if (builtin.os.tag == .windows) { |
| 1698 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); | 1765 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 1699 | return windows.DeleteFileW(file_path_w.span().ptr); | 1766 | return unlinkW(file_path_w.span()); |
| 1700 | } | 1767 | } |
| 1701 | switch (errno(system.unlink(file_path))) { | 1768 | switch (errno(system.unlink(file_path))) { |
| 1702 | 0 => return, | 1769 | 0 => return, |
| ... | @@ -1717,6 +1784,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { | ... | @@ -1717,6 +1784,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { |
| 1717 | } | 1784 | } |
| 1718 | } | 1785 | } |
| 1719 | 1786 | ||
| 1787 | /// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded. | ||
| 1788 | pub fn unlinkW(file_path_w: []const u16) UnlinkError!void { | ||
| 1789 | return windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd }); | ||
| 1790 | } | ||
| 1791 | |||
| 1720 | pub const UnlinkatError = UnlinkError || error{ | 1792 | pub const UnlinkatError = UnlinkError || error{ |
| 1721 | /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty. | 1793 | /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty. |
| 1722 | DirNotEmpty, | 1794 | DirNotEmpty, |
| ... | @@ -1727,7 +1799,7 @@ pub const UnlinkatError = UnlinkError || error{ | ... | @@ -1727,7 +1799,7 @@ pub const UnlinkatError = UnlinkError || error{ |
| 1727 | pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { | 1799 | pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { |
| 1728 | if (builtin.os.tag == .windows) { | 1800 | if (builtin.os.tag == .windows) { |
| 1729 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); | 1801 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 1730 | return unlinkatW(dirfd, file_path_w.span().ptr, flags); | 1802 | return unlinkatW(dirfd, file_path_w.span(), flags); |
| 1731 | } else if (builtin.os.tag == .wasi) { | 1803 | } else if (builtin.os.tag == .wasi) { |
| 1732 | return unlinkatWasi(dirfd, file_path, flags); | 1804 | return unlinkatWasi(dirfd, file_path, flags); |
| 1733 | } else { | 1805 | } else { |
| ... | @@ -1774,7 +1846,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro | ... | @@ -1774,7 +1846,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro |
| 1774 | pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { | 1846 | pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { |
| 1775 | if (builtin.os.tag == .windows) { | 1847 | if (builtin.os.tag == .windows) { |
| 1776 | const file_path_w = try windows.cStrToPrefixedFileW(file_path_c); | 1848 | const file_path_w = try windows.cStrToPrefixedFileW(file_path_c); |
| 1777 | return unlinkatW(dirfd, file_path_w.span().ptr, flags); | 1849 | return unlinkatW(dirfd, file_path_w.span(), flags); |
| 1778 | } | 1850 | } |
| 1779 | switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { | 1851 | switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { |
| 1780 | 0 => return, | 1852 | 0 => return, |
| ... | @@ -1800,67 +1872,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr | ... | @@ -1800,67 +1872,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr |
| 1800 | } | 1872 | } |
| 1801 | 1873 | ||
| 1802 | /// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only. | 1874 | /// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only. |
| 1803 | pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatError!void { | 1875 | pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void { |
| 1804 | const w = windows; | 1876 | const remove_dir = (flags & AT_REMOVEDIR) != 0; |
| 1805 | 1877 | return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); | |
| 1806 | const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0; | ||
| 1807 | const create_options_flags = if (want_rmdir_behavior) | ||
| 1808 | @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT) | ||
| 1809 | else | ||
| 1810 | @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? | ||
| 1811 | |||
| 1812 | const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2); | ||
| 1813 | var nt_name = w.UNICODE_STRING{ | ||
| 1814 | .Length = path_len_bytes, | ||
| 1815 | .MaximumLength = path_len_bytes, | ||
| 1816 | // The Windows API makes this mutable, but it will not mutate here. | ||
| 1817 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), | ||
| 1818 | }; | ||
| 1819 | |||
| 1820 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { | ||
| 1821 | // Windows does not recognize this, but it does work with empty string. | ||
| 1822 | nt_name.Length = 0; | ||
| 1823 | } | ||
| 1824 | if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { | ||
| 1825 | // Can't remove the parent directory with an open handle. | ||
| 1826 | return error.FileBusy; | ||
| 1827 | } | ||
| 1828 | |||
| 1829 | var attr = w.OBJECT_ATTRIBUTES{ | ||
| 1830 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), | ||
| 1831 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd, | ||
| 1832 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | ||
| 1833 | .ObjectName = &nt_name, | ||
| 1834 | .SecurityDescriptor = null, | ||
| 1835 | .SecurityQualityOfService = null, | ||
| 1836 | }; | ||
| 1837 | var io: w.IO_STATUS_BLOCK = undefined; | ||
| 1838 | var tmp_handle: w.HANDLE = undefined; | ||
| 1839 | var rc = w.ntdll.NtCreateFile( | ||
| 1840 | &tmp_handle, | ||
| 1841 | w.SYNCHRONIZE | w.DELETE, | ||
| 1842 | &attr, | ||
| 1843 | &io, | ||
| 1844 | null, | ||
| 1845 | 0, | ||
| 1846 | w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE, | ||
| 1847 | w.FILE_OPEN, | ||
| 1848 | create_options_flags, | ||
| 1849 | null, | ||
| 1850 | 0, | ||
| 1851 | ); | ||
| 1852 | if (rc == .SUCCESS) { | ||
| 1853 | rc = w.ntdll.NtClose(tmp_handle); | ||
| 1854 | } | ||
| 1855 | switch (rc) { | ||
| 1856 | .SUCCESS => return, | ||
| 1857 | .OBJECT_NAME_INVALID => unreachable, | ||
| 1858 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | ||
| 1859 | .INVALID_PARAMETER => unreachable, | ||
| 1860 | .FILE_IS_A_DIRECTORY => return error.IsDir, | ||
| 1861 | .NOT_A_DIRECTORY => return error.NotDir, | ||
| 1862 | else => return w.unexpectedStatus(rc), | ||
| 1863 | } | ||
| 1864 | } | 1878 | } |
| 1865 | 1879 | ||
| 1866 | const RenameError = error{ | 1880 | const RenameError = error{ |
| ... | @@ -2087,7 +2101,7 @@ pub fn renameatW( | ... | @@ -2087,7 +2101,7 @@ pub fn renameatW( |
| 2087 | pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { | 2101 | pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2088 | if (builtin.os.tag == .windows) { | 2102 | if (builtin.os.tag == .windows) { |
| 2089 | const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); | 2103 | const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); |
| 2090 | return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); | 2104 | return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); |
| 2091 | } else if (builtin.os.tag == .wasi) { | 2105 | } else if (builtin.os.tag == .wasi) { |
| 2092 | return mkdiratWasi(dir_fd, sub_dir_path, mode); | 2106 | return mkdiratWasi(dir_fd, sub_dir_path, mode); |
| 2093 | } else { | 2107 | } else { |
| ... | @@ -2145,8 +2159,19 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr | ... | @@ -2145,8 +2159,19 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr |
| 2145 | } | 2159 | } |
| 2146 | } | 2160 | } |
| 2147 | 2161 | ||
| 2148 | pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void { | 2162 | pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void { |
| 2149 | const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null); | 2163 | const sub_dir_handle = windows.OpenFile(sub_path_w, .{ |
| 2164 | .dir = dir_fd, | ||
| 2165 | .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, | ||
| 2166 | .creation = windows.FILE_CREATE, | ||
| 2167 | .io_mode = .blocking, | ||
| 2168 | .open_dir = true, | ||
| 2169 | }) catch |err| switch (err) { | ||
| 2170 | error.IsDir => unreachable, | ||
| 2171 | error.PipeBusy => unreachable, | ||
| 2172 | error.WouldBlock => unreachable, | ||
| 2173 | else => |e| return e, | ||
| 2174 | }; | ||
| 2150 | windows.CloseHandle(sub_dir_handle); | 2175 | windows.CloseHandle(sub_dir_handle); |
| 2151 | } | 2176 | } |
| 2152 | 2177 | ||
| ... | @@ -2175,9 +2200,8 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { | ... | @@ -2175,9 +2200,8 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2175 | if (builtin.os.tag == .wasi) { | 2200 | if (builtin.os.tag == .wasi) { |
| 2176 | @compileError("mkdir is not supported in WASI; use mkdirat instead"); | 2201 | @compileError("mkdir is not supported in WASI; use mkdirat instead"); |
| 2177 | } else if (builtin.os.tag == .windows) { | 2202 | } else if (builtin.os.tag == .windows) { |
| 2178 | const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null); | 2203 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); |
| 2179 | windows.CloseHandle(sub_dir_handle); | 2204 | return mkdirW(dir_path_w.span(), mode); |
| 2180 | return; | ||
| 2181 | } else { | 2205 | } else { |
| 2182 | const dir_path_c = try toPosixPath(dir_path); | 2206 | const dir_path_c = try toPosixPath(dir_path); |
| 2183 | return mkdirZ(&dir_path_c, mode); | 2207 | return mkdirZ(&dir_path_c, mode); |
| ... | @@ -2188,9 +2212,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { | ... | @@ -2188,9 +2212,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2188 | pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { | 2212 | pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { |
| 2189 | if (builtin.os.tag == .windows) { | 2213 | if (builtin.os.tag == .windows) { |
| 2190 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); | 2214 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); |
| 2191 | const sub_dir_handle = try windows.CreateDirectoryW(null, dir_path_w.span().ptr, null); | 2215 | return mkdirW(dir_path_w.span(), mode); |
| 2192 | windows.CloseHandle(sub_dir_handle); | ||
| 2193 | return; | ||
| 2194 | } | 2216 | } |
| 2195 | switch (errno(system.mkdir(dir_path, mode))) { | 2217 | switch (errno(system.mkdir(dir_path, mode))) { |
| 2196 | 0 => return, | 2218 | 0 => return, |
| ... | @@ -2211,6 +2233,23 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { | ... | @@ -2211,6 +2233,23 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { |
| 2211 | } | 2233 | } |
| 2212 | } | 2234 | } |
| 2213 | 2235 | ||
| 2236 | /// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded. | ||
| 2237 | pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void { | ||
| 2238 | const sub_dir_handle = windows.OpenFile(dir_path_w, .{ | ||
| 2239 | .dir = std.fs.cwd().fd, | ||
| 2240 | .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, | ||
| 2241 | .creation = windows.FILE_CREATE, | ||
| 2242 | .io_mode = .blocking, | ||
| 2243 | .open_dir = true, | ||
| 2244 | }) catch |err| switch (err) { | ||
| 2245 | error.IsDir => unreachable, | ||
| 2246 | error.PipeBusy => unreachable, | ||
| 2247 | error.WouldBlock => unreachable, | ||
| 2248 | else => |e| return e, | ||
| 2249 | }; | ||
| 2250 | windows.CloseHandle(sub_dir_handle); | ||
| 2251 | } | ||
| 2252 | |||
| 2214 | pub const DeleteDirError = error{ | 2253 | pub const DeleteDirError = error{ |
| 2215 | AccessDenied, | 2254 | AccessDenied, |
| 2216 | FileBusy, | 2255 | FileBusy, |
| ... | @@ -2231,7 +2270,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { | ... | @@ -2231,7 +2270,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { |
| 2231 | @compileError("rmdir is not supported in WASI; use unlinkat instead"); | 2270 | @compileError("rmdir is not supported in WASI; use unlinkat instead"); |
| 2232 | } else if (builtin.os.tag == .windows) { | 2271 | } else if (builtin.os.tag == .windows) { |
| 2233 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); | 2272 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); |
| 2234 | return windows.RemoveDirectoryW(dir_path_w.span().ptr); | 2273 | return rmdirW(dir_path_w.span()); |
| 2235 | } else { | 2274 | } else { |
| 2236 | const dir_path_c = try toPosixPath(dir_path); | 2275 | const dir_path_c = try toPosixPath(dir_path); |
| 2237 | return rmdirZ(&dir_path_c); | 2276 | return rmdirZ(&dir_path_c); |
| ... | @@ -2244,7 +2283,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); | ... | @@ -2244,7 +2283,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); |
| 2244 | pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { | 2283 | pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { |
| 2245 | if (builtin.os.tag == .windows) { | 2284 | if (builtin.os.tag == .windows) { |
| 2246 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); | 2285 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); |
| 2247 | return windows.RemoveDirectoryW(dir_path_w.span().ptr); | 2286 | return rmdirW(dir_path_w.span()); |
| 2248 | } | 2287 | } |
| 2249 | switch (errno(system.rmdir(dir_path))) { | 2288 | switch (errno(system.rmdir(dir_path))) { |
| 2250 | 0 => return, | 2289 | 0 => return, |
| ... | @@ -2265,6 +2304,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { | ... | @@ -2265,6 +2304,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { |
| 2265 | } | 2304 | } |
| 2266 | } | 2305 | } |
| 2267 | 2306 | ||
| 2307 | /// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded. | ||
| 2308 | pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void { | ||
| 2309 | return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) { | ||
| 2310 | error.IsDir => unreachable, | ||
| 2311 | else => |e| return e, | ||
| 2312 | }; | ||
| 2313 | } | ||
| 2314 | |||
| 2268 | pub const ChangeCurDirError = error{ | 2315 | pub const ChangeCurDirError = error{ |
| 2269 | AccessDenied, | 2316 | AccessDenied, |
| 2270 | FileSystem, | 2317 | FileSystem, |
| ... | @@ -2354,7 +2401,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { | ... | @@ -2354,7 +2401,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2354 | if (builtin.os.tag == .wasi) { | 2401 | if (builtin.os.tag == .wasi) { |
| 2355 | @compileError("readlink is not supported in WASI; use readlinkat instead"); | 2402 | @compileError("readlink is not supported in WASI; use readlinkat instead"); |
| 2356 | } else if (builtin.os.tag == .windows) { | 2403 | } else if (builtin.os.tag == .windows) { |
| 2357 | return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer); | 2404 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 2405 | return readlinkW(file_path_w.span(), out_buffer); | ||
| 2358 | } else { | 2406 | } else { |
| 2359 | const file_path_c = try toPosixPath(file_path); | 2407 | const file_path_c = try toPosixPath(file_path); |
| 2360 | return readlinkZ(&file_path_c, out_buffer); | 2408 | return readlinkZ(&file_path_c, out_buffer); |
| ... | @@ -2363,17 +2411,17 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { | ... | @@ -2363,17 +2411,17 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2363 | 2411 | ||
| 2364 | pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); | 2412 | pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); |
| 2365 | 2413 | ||
| 2366 | /// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded. | 2414 | /// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded. |
| 2367 | /// See also `readlinkZ`. | 2415 | /// See also `readlinkZ`. |
| 2368 | pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { | 2416 | pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 2369 | return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer); | 2417 | return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer); |
| 2370 | } | 2418 | } |
| 2371 | 2419 | ||
| 2372 | /// Same as `readlink` except `file_path` is null-terminated. | 2420 | /// Same as `readlink` except `file_path` is null-terminated. |
| 2373 | pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { | 2421 | pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2374 | if (builtin.os.tag == .windows) { | 2422 | if (builtin.os.tag == .windows) { |
| 2375 | const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path); | 2423 | const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path); |
| 2376 | return readlinkW(file_path_w.span().ptr, out_buffer); | 2424 | return readlinkW(file_path_w.span(), out_buffer); |
| 2377 | } | 2425 | } |
| 2378 | const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); | 2426 | const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); |
| 2379 | switch (errno(rc)) { | 2427 | switch (errno(rc)) { |
| ... | @@ -2399,7 +2447,8 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink | ... | @@ -2399,7 +2447,8 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink |
| 2399 | return readlinkatWasi(dirfd, file_path, out_buffer); | 2447 | return readlinkatWasi(dirfd, file_path, out_buffer); |
| 2400 | } | 2448 | } |
| 2401 | if (builtin.os.tag == .windows) { | 2449 | if (builtin.os.tag == .windows) { |
| 2402 | return windows.ReadLink(dirfd, file_path, out_buffer); | 2450 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 2451 | return readlinkatW(dirfd, file_path_w.span(), out_buffer); | ||
| 2403 | } | 2452 | } |
| 2404 | const file_path_c = try toPosixPath(file_path); | 2453 | const file_path_c = try toPosixPath(file_path); |
| 2405 | return readlinkatZ(dirfd, &file_path_c, out_buffer); | 2454 | 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 | ... | @@ -2429,8 +2478,8 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read |
| 2429 | 2478 | ||
| 2430 | /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. | 2479 | /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. |
| 2431 | /// See also `readlinkat`. | 2480 | /// See also `readlinkat`. |
| 2432 | pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { | 2481 | pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 2433 | return windows.ReadLinkW(dirfd, file_path, out_buffer); | 2482 | return windows.ReadLink(dirfd, file_path, out_buffer); |
| 2434 | } | 2483 | } |
| 2435 | 2484 | ||
| 2436 | /// Same as `readlinkat` except `file_path` is null-terminated. | 2485 | /// 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 | ... | @@ -2438,7 +2487,7 @@ pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) Rea |
| 2438 | pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { | 2487 | pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2439 | if (builtin.os.tag == .windows) { | 2488 | if (builtin.os.tag == .windows) { |
| 2440 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); | 2489 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 2441 | return readlinkatW(dirfd, file_path_w.span().ptr, out_buffer); | 2490 | return readlinkatW(dirfd, file_path_w.span(), out_buffer); |
| 2442 | } | 2491 | } |
| 2443 | const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); | 2492 | const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); |
| 2444 | switch (errno(rc)) { | 2493 | switch (errno(rc)) { |
| ... | @@ -3959,7 +4008,7 @@ pub const RealPathError = error{ | ... | @@ -3959,7 +4008,7 @@ pub const RealPathError = error{ |
| 3959 | pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { | 4008 | pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 3960 | if (builtin.os.tag == .windows) { | 4009 | if (builtin.os.tag == .windows) { |
| 3961 | const pathname_w = try windows.sliceToPrefixedFileW(pathname); | 4010 | const pathname_w = try windows.sliceToPrefixedFileW(pathname); |
| 3962 | return realpathW(pathname_w.span().ptr, out_buffer); | 4011 | return realpathW(pathname_w.span(), out_buffer); |
| 3963 | } | 4012 | } |
| 3964 | if (builtin.os.tag == .wasi) { | 4013 | if (builtin.os.tag == .wasi) { |
| 3965 | @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths"); | 4014 | @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"); | ... | @@ -3974,7 +4023,7 @@ pub const realpathC = @compileError("deprecated: renamed realpathZ"); |
| 3974 | pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { | 4023 | pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 3975 | if (builtin.os.tag == .windows) { | 4024 | if (builtin.os.tag == .windows) { |
| 3976 | const pathname_w = try windows.cStrToPrefixedFileW(pathname); | 4025 | const pathname_w = try windows.cStrToPrefixedFileW(pathname); |
| 3977 | return realpathW(pathname_w.span().ptr, out_buffer); | 4026 | return realpathW(pathname_w.span(), out_buffer); |
| 3978 | } | 4027 | } |
| 3979 | if (builtin.os.tag == .linux and !builtin.link_libc) { | 4028 | if (builtin.os.tag == .linux and !builtin.link_libc) { |
| 3980 | const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) { | 4029 | 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 | ... | @@ -4010,22 +4059,43 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP |
| 4010 | return mem.spanZ(result_path); | 4059 | return mem.spanZ(result_path); |
| 4011 | } | 4060 | } |
| 4012 | 4061 | ||
| 4013 | /// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded. | 4062 | /// Same as `realpath` except `pathname` is UTF16LE-encoded. |
| 4014 | /// TODO use ntdll for better semantics | 4063 | /// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine |
| 4015 | pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { | 4064 | pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 4016 | const h_file = try windows.CreateFileW( | 4065 | const w = windows; |
| 4017 | pathname, | 4066 | |
| 4018 | windows.GENERIC_READ, | 4067 | const dir = std.fs.cwd().fd; |
| 4019 | windows.FILE_SHARE_READ, | 4068 | const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; |
| 4020 | null, | 4069 | const share_access = w.FILE_SHARE_READ; |
| 4021 | windows.OPEN_EXISTING, | 4070 | const creation = w.FILE_OPEN; |
| 4022 | windows.FILE_FLAG_BACKUP_SEMANTICS, | 4071 | const h_file = blk: { |
| 4023 | null, | 4072 | const res = w.OpenFile(pathname, .{ |
| 4024 | ); | 4073 | .dir = dir, |
| 4025 | defer windows.CloseHandle(h_file); | 4074 | .access_mask = access_mask, |
| 4075 | .share_access = share_access, | ||
| 4076 | .creation = creation, | ||
| 4077 | .io_mode = .blocking, | ||
| 4078 | }) catch |err| switch (err) { | ||
| 4079 | error.IsDir => break :blk w.OpenFile(pathname, .{ | ||
| 4080 | .dir = dir, | ||
| 4081 | .access_mask = access_mask, | ||
| 4082 | .share_access = share_access, | ||
| 4083 | .creation = creation, | ||
| 4084 | .io_mode = .blocking, | ||
| 4085 | .open_dir = true, | ||
| 4086 | }) catch |er| switch (er) { | ||
| 4087 | error.WouldBlock => unreachable, | ||
| 4088 | else => |e2| return e2, | ||
| 4089 | }, | ||
| 4090 | error.WouldBlock => unreachable, | ||
| 4091 | else => |e| return e, | ||
| 4092 | }; | ||
| 4093 | break :blk res; | ||
| 4094 | }; | ||
| 4095 | defer w.CloseHandle(h_file); | ||
| 4026 | 4096 | ||
| 4027 | var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; | 4097 | var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined; |
| 4028 | const wide_slice = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS); | 4098 | const wide_slice = try w.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, w.VOLUME_NAME_DOS); |
| 4029 | 4099 | ||
| 4030 | // Windows returns \\?\ prepended to the path. | 4100 | // Windows returns \\?\ prepended to the path. |
| 4031 | // We strip it to make this function consistent across platforms. | 4101 | // We strip it to make this function consistent across platforms. |
lib/std/os/bits/windows.zig+25| ... | @@ -237,3 +237,28 @@ pub const IPPROTO_TCP = ws2_32.IPPROTO_TCP; | ... | @@ -237,3 +237,28 @@ pub const IPPROTO_TCP = ws2_32.IPPROTO_TCP; |
| 237 | pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP; | 237 | pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP; |
| 238 | pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6; | 238 | pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6; |
| 239 | pub const IPPROTO_RM = ws2_32.IPPROTO_RM; | 239 | pub const IPPROTO_RM = ws2_32.IPPROTO_RM; |
| 240 | |||
| 241 | pub const O_RDONLY = 0o0; | ||
| 242 | pub const O_WRONLY = 0o1; | ||
| 243 | pub const O_RDWR = 0o2; | ||
| 244 | |||
| 245 | pub const O_CREAT = 0o100; | ||
| 246 | pub const O_EXCL = 0o200; | ||
| 247 | pub const O_NOCTTY = 0o400; | ||
| 248 | pub const O_TRUNC = 0o1000; | ||
| 249 | pub const O_APPEND = 0o2000; | ||
| 250 | pub const O_NONBLOCK = 0o4000; | ||
| 251 | pub const O_DSYNC = 0o10000; | ||
| 252 | pub const O_SYNC = 0o4010000; | ||
| 253 | pub const O_RSYNC = 0o4010000; | ||
| 254 | pub const O_DIRECTORY = 0o200000; | ||
| 255 | pub const O_NOFOLLOW = 0o400000; | ||
| 256 | pub const O_CLOEXEC = 0o2000000; | ||
| 257 | |||
| 258 | pub const O_ASYNC = 0o20000; | ||
| 259 | pub const O_DIRECT = 0o40000; | ||
| 260 | pub const O_LARGEFILE = 0; | ||
| 261 | pub const O_NOATIME = 0o1000000; | ||
| 262 | pub const O_PATH = 0o10000000; | ||
| 263 | pub const O_TMPFILE = 0o20200000; | ||
| 264 | pub const O_NDELAY = O_NONBLOCK; | ||
| \ No newline at end of file | |||
lib/std/os/test.zig+92-2| ... | @@ -3,6 +3,7 @@ const os = std.os; | ... | @@ -3,6 +3,7 @@ const os = std.os; |
| 3 | const testing = std.testing; | 3 | const testing = std.testing; |
| 4 | const expect = testing.expect; | 4 | const expect = testing.expect; |
| 5 | const expectEqual = testing.expectEqual; | 5 | const expectEqual = testing.expectEqual; |
| 6 | const expectError = testing.expectError; | ||
| 6 | const io = std.io; | 7 | const io = std.io; |
| 7 | const fs = std.fs; | 8 | const fs = std.fs; |
| 8 | const mem = std.mem; | 9 | const mem = std.mem; |
| ... | @@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir; | ... | @@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir; |
| 19 | const Dir = std.fs.Dir; | 20 | const Dir = std.fs.Dir; |
| 20 | const ArenaAllocator = std.heap.ArenaAllocator; | 21 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 21 | 22 | ||
| 23 | test "open smoke test" { | ||
| 24 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | ||
| 25 | |||
| 26 | // TODO verify file attributes using `fstat` | ||
| 27 | |||
| 28 | var tmp = tmpDir(.{}); | ||
| 29 | defer tmp.cleanup(); | ||
| 30 | |||
| 31 | // Get base abs path | ||
| 32 | var arena = ArenaAllocator.init(testing.allocator); | ||
| 33 | defer arena.deinit(); | ||
| 34 | |||
| 35 | const base_path = blk: { | ||
| 36 | const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); | ||
| 37 | break :blk try fs.realpathAlloc(&arena.allocator, relative_path); | ||
| 38 | }; | ||
| 39 | |||
| 40 | var file_path: []u8 = undefined; | ||
| 41 | var fd: os.fd_t = undefined; | ||
| 42 | const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666; | ||
| 43 | |||
| 44 | // Create some file using `open`. | ||
| 45 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" }); | ||
| 46 | fd = try os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode); | ||
| 47 | os.close(fd); | ||
| 48 | |||
| 49 | // Try this again with the same flags. This op should fail with error.PathAlreadyExists. | ||
| 50 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" }); | ||
| 51 | expectError(error.PathAlreadyExists, os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode)); | ||
| 52 | |||
| 53 | // Try opening without `O_EXCL` flag. | ||
| 54 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" }); | ||
| 55 | fd = try os.open(file_path, os.O_RDWR | os.O_CREAT, mode); | ||
| 56 | os.close(fd); | ||
| 57 | |||
| 58 | // Try opening as a directory which should fail. | ||
| 59 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" }); | ||
| 60 | expectError(error.NotDir, os.open(file_path, os.O_RDWR | os.O_DIRECTORY, mode)); | ||
| 61 | |||
| 62 | // Create some directory | ||
| 63 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" }); | ||
| 64 | try os.mkdir(file_path, mode); | ||
| 65 | |||
| 66 | // Open dir using `open` | ||
| 67 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" }); | ||
| 68 | fd = try os.open(file_path, os.O_RDONLY | os.O_DIRECTORY, mode); | ||
| 69 | os.close(fd); | ||
| 70 | |||
| 71 | // Try opening as file which should fail. | ||
| 72 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" }); | ||
| 73 | expectError(error.IsDir, os.open(file_path, os.O_RDWR, mode)); | ||
| 74 | } | ||
| 75 | |||
| 76 | test "openat smoke test" { | ||
| 77 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | ||
| 78 | |||
| 79 | // TODO verify file attributes using `fstatat` | ||
| 80 | |||
| 81 | var tmp = tmpDir(.{}); | ||
| 82 | defer tmp.cleanup(); | ||
| 83 | |||
| 84 | var fd: os.fd_t = undefined; | ||
| 85 | const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666; | ||
| 86 | |||
| 87 | // Create some file using `openat`. | ||
| 88 | fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode); | ||
| 89 | os.close(fd); | ||
| 90 | |||
| 91 | // Try this again with the same flags. This op should fail with error.PathAlreadyExists. | ||
| 92 | expectError(error.PathAlreadyExists, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode)); | ||
| 93 | |||
| 94 | // Try opening without `O_EXCL` flag. | ||
| 95 | fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT, mode); | ||
| 96 | os.close(fd); | ||
| 97 | |||
| 98 | // Try opening as a directory which should fail. | ||
| 99 | expectError(error.NotDir, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_DIRECTORY, mode)); | ||
| 100 | |||
| 101 | // Create some directory | ||
| 102 | try os.mkdirat(tmp.dir.fd, "some_dir", mode); | ||
| 103 | |||
| 104 | // Open dir using `open` | ||
| 105 | fd = try os.openat(tmp.dir.fd, "some_dir", os.O_RDONLY | os.O_DIRECTORY, mode); | ||
| 106 | os.close(fd); | ||
| 107 | |||
| 108 | // Try opening as file which should fail. | ||
| 109 | expectError(error.IsDir, os.openat(tmp.dir.fd, "some_dir", os.O_RDWR, mode)); | ||
| 110 | } | ||
| 111 | |||
| 22 | test "symlink with relative paths" { | 112 | test "symlink with relative paths" { |
| 23 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 113 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 24 | 114 | ||
| ... | @@ -27,7 +117,7 @@ test "symlink with relative paths" { | ... | @@ -27,7 +117,7 @@ test "symlink with relative paths" { |
| 27 | try cwd.writeFile("file.txt", "nonsense"); | 117 | try cwd.writeFile("file.txt", "nonsense"); |
| 28 | 118 | ||
| 29 | if (builtin.os.tag == .windows) { | 119 | if (builtin.os.tag == .windows) { |
| 30 | try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false); | 120 | 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); |
| 31 | } else { | 121 | } else { |
| 32 | try os.symlink("file.txt", "symlinked"); | 122 | try os.symlink("file.txt", "symlinked"); |
| 33 | } | 123 | } |
| ... | @@ -85,7 +175,7 @@ test "readlinkat" { | ... | @@ -85,7 +175,7 @@ test "readlinkat" { |
| 85 | 175 | ||
| 86 | // create a symbolic link | 176 | // create a symbolic link |
| 87 | if (builtin.os.tag == .windows) { | 177 | if (builtin.os.tag == .windows) { |
| 88 | try os.windows.CreateSymbolicLink(tmp.dir.fd, "link", "file.txt", false); | 178 | try os.windows.CreateSymbolicLink(tmp.dir.fd, &[_]u16{ 'l', 'i', 'n', 'k' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false); |
| 89 | } else { | 179 | } else { |
| 90 | try os.symlinkat("file.txt", tmp.dir.fd, "link"); | 180 | try os.symlinkat("file.txt", tmp.dir.fd, "link"); |
| 91 | } | 181 | } |
lib/std/os/windows.zig+129-285| ... | @@ -25,76 +25,11 @@ pub usingnamespace @import("windows/bits.zig"); | ... | @@ -25,76 +25,11 @@ pub usingnamespace @import("windows/bits.zig"); |
| 25 | 25 | ||
| 26 | pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize)); | 26 | pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize)); |
| 27 | 27 | ||
| 28 | pub const CreateFileError = error{ | ||
| 29 | SharingViolation, | ||
| 30 | PathAlreadyExists, | ||
| 31 | |||
| 32 | /// When any of the path components can not be found or the file component can not | ||
| 33 | /// be found. Some operating systems distinguish between path components not found and | ||
| 34 | /// file components not found, but they are collapsed into FileNotFound to gain | ||
| 35 | /// consistency across operating systems. | ||
| 36 | FileNotFound, | ||
| 37 | |||
| 38 | AccessDenied, | ||
| 39 | PipeBusy, | ||
| 40 | NameTooLong, | ||
| 41 | |||
| 42 | /// On Windows, file paths must be valid Unicode. | ||
| 43 | InvalidUtf8, | ||
| 44 | |||
| 45 | /// On Windows, file paths cannot contain these characters: | ||
| 46 | /// '/', '*', '?', '"', '<', '>', '|' | ||
| 47 | BadPathName, | ||
| 48 | |||
| 49 | Unexpected, | ||
| 50 | }; | ||
| 51 | |||
| 52 | pub fn CreateFile( | ||
| 53 | file_path: []const u8, | ||
| 54 | desired_access: DWORD, | ||
| 55 | share_mode: DWORD, | ||
| 56 | lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, | ||
| 57 | creation_disposition: DWORD, | ||
| 58 | flags_and_attrs: DWORD, | ||
| 59 | hTemplateFile: ?HANDLE, | ||
| 60 | ) CreateFileError!HANDLE { | ||
| 61 | const file_path_w = try sliceToPrefixedFileW(file_path); | ||
| 62 | return CreateFileW(file_path_w.span().ptr, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile); | ||
| 63 | } | ||
| 64 | |||
| 65 | pub fn CreateFileW( | ||
| 66 | file_path_w: [*:0]const u16, | ||
| 67 | desired_access: DWORD, | ||
| 68 | share_mode: DWORD, | ||
| 69 | lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, | ||
| 70 | creation_disposition: DWORD, | ||
| 71 | flags_and_attrs: DWORD, | ||
| 72 | hTemplateFile: ?HANDLE, | ||
| 73 | ) CreateFileError!HANDLE { | ||
| 74 | const result = kernel32.CreateFileW(file_path_w, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile); | ||
| 75 | |||
| 76 | if (result == INVALID_HANDLE_VALUE) { | ||
| 77 | switch (kernel32.GetLastError()) { | ||
| 78 | .SHARING_VIOLATION => return error.SharingViolation, | ||
| 79 | .ALREADY_EXISTS => return error.PathAlreadyExists, | ||
| 80 | .FILE_EXISTS => return error.PathAlreadyExists, | ||
| 81 | .FILE_NOT_FOUND => return error.FileNotFound, | ||
| 82 | .PATH_NOT_FOUND => return error.FileNotFound, | ||
| 83 | .ACCESS_DENIED => return error.AccessDenied, | ||
| 84 | .PIPE_BUSY => return error.PipeBusy, | ||
| 85 | .FILENAME_EXCED_RANGE => return error.NameTooLong, | ||
| 86 | else => |err| return unexpectedError(err), | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | return result; | ||
| 91 | } | ||
| 92 | |||
| 93 | pub const OpenError = error{ | 28 | pub const OpenError = error{ |
| 94 | IsDir, | 29 | IsDir, |
| 30 | NotDir, | ||
| 95 | FileNotFound, | 31 | FileNotFound, |
| 96 | NoDevice, | 32 | NoDevice, |
| 97 | SharingViolation, | ||
| 98 | AccessDenied, | 33 | AccessDenied, |
| 99 | PipeBusy, | 34 | PipeBusy, |
| 100 | PathAlreadyExists, | 35 | PathAlreadyExists, |
| ... | @@ -111,15 +46,21 @@ pub const OpenFileOptions = struct { | ... | @@ -111,15 +46,21 @@ pub const OpenFileOptions = struct { |
| 111 | share_access_nonblocking: bool = false, | 46 | share_access_nonblocking: bool = false, |
| 112 | creation: ULONG, | 47 | creation: ULONG, |
| 113 | io_mode: std.io.ModeOverride, | 48 | io_mode: std.io.ModeOverride, |
| 49 | /// If true, tries to open path as a directory. | ||
| 50 | /// Defaults to false. | ||
| 51 | open_dir: bool = false, | ||
| 52 | /// If false, tries to open path as a reparse point without dereferencing it. | ||
| 53 | /// Defaults to true. | ||
| 54 | follow_symlinks: bool = true, | ||
| 114 | }; | 55 | }; |
| 115 | 56 | ||
| 116 | /// TODO when share_access_nonblocking is false, this implementation uses | 57 | /// TODO when share_access_nonblocking is false, this implementation uses |
| 117 | /// untinterruptible sleep() to block. This is not the final iteration of the API. | 58 | /// untinterruptible sleep() to block. This is not the final iteration of the API. |
| 118 | pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE { | 59 | pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE { |
| 119 | if (mem.eql(u16, sub_path_w, &[_]u16{'.'})) { | 60 | if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and !options.open_dir) { |
| 120 | return error.IsDir; | 61 | return error.IsDir; |
| 121 | } | 62 | } |
| 122 | if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' })) { | 63 | if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.open_dir) { |
| 123 | return error.IsDir; | 64 | return error.IsDir; |
| 124 | } | 65 | } |
| 125 | 66 | ||
| ... | @@ -142,11 +83,13 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN | ... | @@ -142,11 +83,13 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 142 | .SecurityQualityOfService = null, | 83 | .SecurityQualityOfService = null, |
| 143 | }; | 84 | }; |
| 144 | var io: IO_STATUS_BLOCK = undefined; | 85 | var io: IO_STATUS_BLOCK = undefined; |
| 86 | const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0; | ||
| 87 | const file_or_dir_flag: ULONG = if (options.open_dir) FILE_DIRECTORY_FILE else FILE_NON_DIRECTORY_FILE; | ||
| 88 | // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT. | ||
| 89 | const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT; | ||
| 145 | 90 | ||
| 146 | var delay: usize = 1; | 91 | var delay: usize = 1; |
| 147 | while (true) { | 92 | while (true) { |
| 148 | var flags: ULONG = undefined; | ||
| 149 | const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0; | ||
| 150 | const rc = ntdll.NtCreateFile( | 93 | const rc = ntdll.NtCreateFile( |
| 151 | &result, | 94 | &result, |
| 152 | options.access_mask, | 95 | options.access_mask, |
| ... | @@ -156,7 +99,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN | ... | @@ -156,7 +99,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 156 | FILE_ATTRIBUTE_NORMAL, | 99 | FILE_ATTRIBUTE_NORMAL, |
| 157 | options.share_access, | 100 | options.share_access, |
| 158 | options.creation, | 101 | options.creation, |
| 159 | FILE_NON_DIRECTORY_FILE | blocking_flag, | 102 | flags, |
| 160 | null, | 103 | null, |
| 161 | 0, | 104 | 0, |
| 162 | ); | 105 | ); |
| ... | @@ -184,6 +127,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN | ... | @@ -184,6 +127,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 184 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | 127 | .OBJECT_PATH_SYNTAX_BAD => unreachable, |
| 185 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | 128 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, |
| 186 | .FILE_IS_A_DIRECTORY => return error.IsDir, | 129 | .FILE_IS_A_DIRECTORY => return error.IsDir, |
| 130 | .NOT_A_DIRECTORY => return error.NotDir, | ||
| 187 | else => return unexpectedStatus(rc), | 131 | else => return unexpectedStatus(rc), |
| 188 | } | 132 | } |
| 189 | } | 133 | } |
| ... | @@ -215,30 +159,61 @@ pub fn CreateEventExW(attributes: ?*SECURITY_ATTRIBUTES, nameW: [*:0]const u16, | ... | @@ -215,30 +159,61 @@ pub fn CreateEventExW(attributes: ?*SECURITY_ATTRIBUTES, nameW: [*:0]const u16, |
| 215 | } | 159 | } |
| 216 | } | 160 | } |
| 217 | 161 | ||
| 162 | pub const DeviceIoControlError = error{Unexpected}; | ||
| 163 | |||
| 164 | /// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls. | ||
| 165 | /// It implements similar behavior to `DeviceIoControl` and is meant to serve | ||
| 166 | /// as a direct substitute for that call. | ||
| 167 | /// TODO work out if we need to expose other arguments to the underlying syscalls. | ||
| 218 | pub fn DeviceIoControl( | 168 | pub fn DeviceIoControl( |
| 219 | h: HANDLE, | 169 | h: HANDLE, |
| 220 | ioControlCode: DWORD, | 170 | ioControlCode: ULONG, |
| 221 | in: ?[]const u8, | 171 | in: ?[]const u8, |
| 222 | out: ?[]u8, | 172 | out: ?[]u8, |
| 223 | overlapped: ?*OVERLAPPED, | 173 | ) DeviceIoControlError!void { |
| 224 | ) !DWORD { | 174 | // Logic from: https://doxygen.reactos.org/d3/d74/deviceio_8c.html |
| 225 | var bytes: DWORD = undefined; | 175 | const is_fsctl = (ioControlCode >> 16) == FILE_DEVICE_FILE_SYSTEM; |
| 226 | if (kernel32.DeviceIoControl( | 176 | |
| 227 | h, | 177 | var io: IO_STATUS_BLOCK = undefined; |
| 228 | ioControlCode, | 178 | const in_ptr = if (in) |i| i.ptr else null; |
| 229 | if (in) |i| i.ptr else null, | 179 | const in_len = if (in) |i| @intCast(ULONG, i.len) else 0; |
| 230 | if (in) |i| @intCast(u32, i.len) else 0, | 180 | const out_ptr = if (out) |o| o.ptr else null; |
| 231 | if (out) |o| o.ptr else null, | 181 | const out_len = if (out) |o| @intCast(ULONG, o.len) else 0; |
| 232 | if (out) |o| @intCast(u32, o.len) else 0, | 182 | |
| 233 | &bytes, | 183 | const rc = blk: { |
| 234 | overlapped, | 184 | if (is_fsctl) { |
| 235 | ) == 0) { | 185 | break :blk ntdll.NtFsControlFile( |
| 236 | switch (kernel32.GetLastError()) { | 186 | h, |
| 237 | .IO_PENDING => if (overlapped == null) unreachable, | 187 | null, |
| 238 | else => |err| return unexpectedError(err), | 188 | null, |
| 189 | null, | ||
| 190 | &io, | ||
| 191 | ioControlCode, | ||
| 192 | in_ptr, | ||
| 193 | in_len, | ||
| 194 | out_ptr, | ||
| 195 | out_len, | ||
| 196 | ); | ||
| 197 | } else { | ||
| 198 | break :blk ntdll.NtDeviceIoControlFile( | ||
| 199 | h, | ||
| 200 | null, | ||
| 201 | null, | ||
| 202 | null, | ||
| 203 | &io, | ||
| 204 | ioControlCode, | ||
| 205 | in_ptr, | ||
| 206 | in_len, | ||
| 207 | out_ptr, | ||
| 208 | out_len, | ||
| 209 | ); | ||
| 239 | } | 210 | } |
| 211 | }; | ||
| 212 | switch (rc) { | ||
| 213 | .SUCCESS => {}, | ||
| 214 | .INVALID_PARAMETER => unreachable, | ||
| 215 | else => return unexpectedStatus(rc), | ||
| 240 | } | 216 | } |
| 241 | return bytes; | ||
| 242 | } | 217 | } |
| 243 | 218 | ||
| 244 | pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWORD { | 219 | pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWORD { |
| ... | @@ -607,27 +582,14 @@ pub const CreateSymbolicLinkError = error{ | ... | @@ -607,27 +582,14 @@ pub const CreateSymbolicLinkError = error{ |
| 607 | PathAlreadyExists, | 582 | PathAlreadyExists, |
| 608 | FileNotFound, | 583 | FileNotFound, |
| 609 | NameTooLong, | 584 | NameTooLong, |
| 610 | InvalidUtf8, | ||
| 611 | BadPathName, | ||
| 612 | NoDevice, | 585 | NoDevice, |
| 613 | Unexpected, | 586 | Unexpected, |
| 614 | }; | 587 | }; |
| 615 | 588 | ||
| 616 | pub fn CreateSymbolicLink( | 589 | pub fn CreateSymbolicLink( |
| 617 | dir: ?HANDLE, | 590 | dir: ?HANDLE, |
| 618 | sym_link_path: []const u8, | 591 | sym_link_path: []const u16, |
| 619 | target_path: []const u8, | 592 | target_path: []const u16, |
| 620 | is_directory: bool, | ||
| 621 | ) CreateSymbolicLinkError!void { | ||
| 622 | const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path); | ||
| 623 | const target_path_w = try sliceToPrefixedFileW(target_path); | ||
| 624 | return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory); | ||
| 625 | } | ||
| 626 | |||
| 627 | pub fn CreateSymbolicLinkW( | ||
| 628 | dir: ?HANDLE, | ||
| 629 | sym_link_path: [:0]const u16, | ||
| 630 | target_path: [:0]const u16, | ||
| 631 | is_directory: bool, | 593 | is_directory: bool, |
| 632 | ) CreateSymbolicLinkError!void { | 594 | ) CreateSymbolicLinkError!void { |
| 633 | const SYMLINK_DATA = extern struct { | 595 | const SYMLINK_DATA = extern struct { |
| ... | @@ -641,71 +603,19 @@ pub fn CreateSymbolicLinkW( | ... | @@ -641,71 +603,19 @@ pub fn CreateSymbolicLinkW( |
| 641 | Flags: ULONG, | 603 | Flags: ULONG, |
| 642 | }; | 604 | }; |
| 643 | 605 | ||
| 644 | var symlink_handle: HANDLE = undefined; | 606 | const symlink_handle = OpenFile(sym_link_path, .{ |
| 645 | if (is_directory) { | 607 | .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, |
| 646 | const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) { | 608 | .dir = dir, |
| 647 | error.Overflow => return error.NameTooLong, | 609 | .creation = FILE_CREATE, |
| 648 | }; | 610 | .io_mode = .blocking, |
| 649 | var nt_name = UNICODE_STRING{ | 611 | .open_dir = is_directory, |
| 650 | .Length = sym_link_len_bytes, | 612 | }) catch |err| switch (err) { |
| 651 | .MaximumLength = sym_link_len_bytes, | 613 | error.IsDir => return error.PathAlreadyExists, |
| 652 | .Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)), | 614 | error.NotDir => unreachable, |
| 653 | }; | 615 | error.WouldBlock => unreachable, |
| 654 | 616 | error.PipeBusy => unreachable, | |
| 655 | if (sym_link_path[0] == '.' and sym_link_path[1] == 0) { | 617 | else => |e| return e, |
| 656 | // Windows does not recognize this, but it does work with empty string. | 618 | }; |
| 657 | nt_name.Length = 0; | ||
| 658 | } | ||
| 659 | |||
| 660 | var attr = OBJECT_ATTRIBUTES{ | ||
| 661 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | ||
| 662 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sym_link_path)) null else dir, | ||
| 663 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | ||
| 664 | .ObjectName = &nt_name, | ||
| 665 | .SecurityDescriptor = null, | ||
| 666 | .SecurityQualityOfService = null, | ||
| 667 | }; | ||
| 668 | |||
| 669 | var io: IO_STATUS_BLOCK = undefined; | ||
| 670 | const rc = ntdll.NtCreateFile( | ||
| 671 | &symlink_handle, | ||
| 672 | GENERIC_READ | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES, | ||
| 673 | &attr, | ||
| 674 | &io, | ||
| 675 | null, | ||
| 676 | FILE_ATTRIBUTE_NORMAL, | ||
| 677 | FILE_SHARE_READ, | ||
| 678 | FILE_CREATE, | ||
| 679 | FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT, | ||
| 680 | null, | ||
| 681 | 0, | ||
| 682 | ); | ||
| 683 | switch (rc) { | ||
| 684 | .SUCCESS => {}, | ||
| 685 | .OBJECT_NAME_INVALID => unreachable, | ||
| 686 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | ||
| 687 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | ||
| 688 | .NO_MEDIA_IN_DEVICE => return error.NoDevice, | ||
| 689 | .INVALID_PARAMETER => unreachable, | ||
| 690 | .ACCESS_DENIED => return error.AccessDenied, | ||
| 691 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | ||
| 692 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | ||
| 693 | else => return unexpectedStatus(rc), | ||
| 694 | } | ||
| 695 | } else { | ||
| 696 | symlink_handle = OpenFile(sym_link_path, .{ | ||
| 697 | .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, | ||
| 698 | .dir = dir, | ||
| 699 | .creation = FILE_CREATE, | ||
| 700 | .io_mode = .blocking, | ||
| 701 | }) catch |err| switch (err) { | ||
| 702 | error.WouldBlock => unreachable, | ||
| 703 | error.IsDir => return error.PathAlreadyExists, | ||
| 704 | error.PipeBusy => unreachable, | ||
| 705 | error.SharingViolation => return error.AccessDenied, | ||
| 706 | else => |e| return e, | ||
| 707 | }; | ||
| 708 | } | ||
| 709 | defer CloseHandle(symlink_handle); | 619 | defer CloseHandle(symlink_handle); |
| 710 | 620 | ||
| 711 | // prepare reparse data buffer | 621 | // prepare reparse data buffer |
| ... | @@ -727,8 +637,7 @@ pub fn CreateSymbolicLinkW( | ... | @@ -727,8 +637,7 @@ pub fn CreateSymbolicLinkW( |
| 727 | @memcpy(buffer[@sizeOf(SYMLINK_DATA)..], @ptrCast([*]const u8, target_path), target_path.len * 2); | 637 | @memcpy(buffer[@sizeOf(SYMLINK_DATA)..], @ptrCast([*]const u8, target_path), target_path.len * 2); |
| 728 | const paths_start = @sizeOf(SYMLINK_DATA) + target_path.len * 2; | 638 | const paths_start = @sizeOf(SYMLINK_DATA) + target_path.len * 2; |
| 729 | @memcpy(buffer[paths_start..].ptr, @ptrCast([*]const u8, target_path), target_path.len * 2); | 639 | @memcpy(buffer[paths_start..].ptr, @ptrCast([*]const u8, target_path), target_path.len * 2); |
| 730 | // TODO replace with NtDeviceIoControl | 640 | _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null); |
| 731 | _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null); | ||
| 732 | } | 641 | } |
| 733 | 642 | ||
| 734 | pub const ReadLinkError = error{ | 643 | pub const ReadLinkError = error{ |
| ... | @@ -737,44 +646,32 @@ pub const ReadLinkError = error{ | ... | @@ -737,44 +646,32 @@ pub const ReadLinkError = error{ |
| 737 | Unexpected, | 646 | Unexpected, |
| 738 | NameTooLong, | 647 | NameTooLong, |
| 739 | UnsupportedReparsePointType, | 648 | UnsupportedReparsePointType, |
| 740 | InvalidUtf8, | ||
| 741 | BadPathName, | ||
| 742 | }; | 649 | }; |
| 743 | 650 | ||
| 744 | pub fn ReadLink( | 651 | pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 745 | dir: ?HANDLE, | 652 | // Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper. |
| 746 | sub_path: []const u8, | 653 | // With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that |
| 747 | out_buffer: []u8, | 654 | // failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible |
| 748 | ) ReadLinkError![]u8 { | 655 | // to open the symlink there and then. |
| 749 | const sub_path_w = try sliceToPrefixedFileW(sub_path); | 656 | const path_len_bytes = math.cast(u16, sub_path_w.len * 2) catch |err| switch (err) { |
| 750 | return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer); | ||
| 751 | } | ||
| 752 | |||
| 753 | pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { | ||
| 754 | const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) { | ||
| 755 | error.Overflow => return error.NameTooLong, | 657 | error.Overflow => return error.NameTooLong, |
| 756 | }; | 658 | }; |
| 757 | var nt_name = UNICODE_STRING{ | 659 | var nt_name = UNICODE_STRING{ |
| 758 | .Length = path_len_bytes, | 660 | .Length = path_len_bytes, |
| 759 | .MaximumLength = path_len_bytes, | 661 | .MaximumLength = path_len_bytes, |
| 760 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), | 662 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)), |
| 761 | }; | 663 | }; |
| 762 | |||
| 763 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { | ||
| 764 | // Windows does not recognize this, but it does work with empty string. | ||
| 765 | nt_name.Length = 0; | ||
| 766 | } | ||
| 767 | |||
| 768 | var attr = OBJECT_ATTRIBUTES{ | 664 | var attr = OBJECT_ATTRIBUTES{ |
| 769 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | 665 | .Length = @sizeOf(OBJECT_ATTRIBUTES), |
| 770 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, | 666 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else dir, |
| 771 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | 667 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 772 | .ObjectName = &nt_name, | 668 | .ObjectName = &nt_name, |
| 773 | .SecurityDescriptor = null, | 669 | .SecurityDescriptor = null, |
| 774 | .SecurityQualityOfService = null, | 670 | .SecurityQualityOfService = null, |
| 775 | }; | 671 | }; |
| 776 | var io: IO_STATUS_BLOCK = undefined; | ||
| 777 | var result_handle: HANDLE = undefined; | 672 | var result_handle: HANDLE = undefined; |
| 673 | var io: IO_STATUS_BLOCK = undefined; | ||
| 674 | |||
| 778 | const rc = ntdll.NtCreateFile( | 675 | const rc = ntdll.NtCreateFile( |
| 779 | &result_handle, | 676 | &result_handle, |
| 780 | FILE_READ_ATTRIBUTES, | 677 | FILE_READ_ATTRIBUTES, |
| ... | @@ -806,7 +703,7 @@ pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) Rea | ... | @@ -806,7 +703,7 @@ pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) Rea |
| 806 | defer CloseHandle(result_handle); | 703 | defer CloseHandle(result_handle); |
| 807 | 704 | ||
| 808 | var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; | 705 | var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; |
| 809 | _ = try DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null); | 706 | _ = try DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]); |
| 810 | 707 | ||
| 811 | const reparse_struct = @ptrCast(*const REPARSE_DATA_BUFFER, @alignCast(@alignOf(REPARSE_DATA_BUFFER), &reparse_buf[0])); | 708 | const reparse_struct = @ptrCast(*const REPARSE_DATA_BUFFER, @alignCast(@alignOf(REPARSE_DATA_BUFFER), &reparse_buf[0])); |
| 812 | switch (reparse_struct.ReparseTag) { | 709 | switch (reparse_struct.ReparseTag) { |
| ... | @@ -848,135 +745,83 @@ pub const DeleteFileError = error{ | ... | @@ -848,135 +745,83 @@ pub const DeleteFileError = error{ |
| 848 | NameTooLong, | 745 | NameTooLong, |
| 849 | FileBusy, | 746 | FileBusy, |
| 850 | Unexpected, | 747 | Unexpected, |
| 748 | NotDir, | ||
| 749 | IsDir, | ||
| 851 | }; | 750 | }; |
| 852 | 751 | ||
| 853 | pub fn DeleteFile(filename: []const u8) DeleteFileError!void { | 752 | pub const DeleteFileOptions = struct { |
| 854 | const filename_w = try sliceToPrefixedFileW(filename); | 753 | dir: ?HANDLE, |
| 855 | return DeleteFileW(filename_w.span().ptr); | 754 | remove_dir: bool = false, |
| 856 | } | ||
| 857 | |||
| 858 | pub fn DeleteFileW(filename: [*:0]const u16) DeleteFileError!void { | ||
| 859 | if (kernel32.DeleteFileW(filename) == 0) { | ||
| 860 | switch (kernel32.GetLastError()) { | ||
| 861 | .FILE_NOT_FOUND => return error.FileNotFound, | ||
| 862 | .PATH_NOT_FOUND => return error.FileNotFound, | ||
| 863 | .ACCESS_DENIED => return error.AccessDenied, | ||
| 864 | .FILENAME_EXCED_RANGE => return error.NameTooLong, | ||
| 865 | .INVALID_PARAMETER => return error.NameTooLong, | ||
| 866 | .SHARING_VIOLATION => return error.FileBusy, | ||
| 867 | else => |err| return unexpectedError(err), | ||
| 868 | } | ||
| 869 | } | ||
| 870 | } | ||
| 871 | |||
| 872 | pub const MoveFileError = error{Unexpected}; | ||
| 873 | |||
| 874 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { | ||
| 875 | const old_path_w = try sliceToPrefixedFileW(old_path); | ||
| 876 | const new_path_w = try sliceToPrefixedFileW(new_path); | ||
| 877 | return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags); | ||
| 878 | } | ||
| 879 | |||
| 880 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { | ||
| 881 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { | ||
| 882 | switch (kernel32.GetLastError()) { | ||
| 883 | else => |err| return unexpectedError(err), | ||
| 884 | } | ||
| 885 | } | ||
| 886 | } | ||
| 887 | |||
| 888 | pub const CreateDirectoryError = error{ | ||
| 889 | NameTooLong, | ||
| 890 | PathAlreadyExists, | ||
| 891 | FileNotFound, | ||
| 892 | NoDevice, | ||
| 893 | AccessDenied, | ||
| 894 | InvalidUtf8, | ||
| 895 | BadPathName, | ||
| 896 | Unexpected, | ||
| 897 | }; | 755 | }; |
| 898 | 756 | ||
| 899 | /// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`. | 757 | pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void { |
| 900 | pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE { | 758 | const create_options_flags: ULONG = if (options.remove_dir) |
| 901 | const pathname_w = try sliceToPrefixedFileW(pathname); | 759 | FILE_DELETE_ON_CLOSE | FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT |
| 902 | return CreateDirectoryW(dir, pathname_w.span().ptr, sa); | 760 | else |
| 903 | } | 761 | FILE_DELETE_ON_CLOSE | FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead? |
| 904 | 762 | ||
| 905 | /// Same as `CreateDirectory` except takes a WTF-16 encoded path. | 763 | const path_len_bytes = @intCast(u16, sub_path_w.len * 2); |
| 906 | pub fn CreateDirectoryW( | ||
| 907 | dir: ?HANDLE, | ||
| 908 | sub_path_w: [*:0]const u16, | ||
| 909 | sa: ?*SECURITY_ATTRIBUTES, | ||
| 910 | ) CreateDirectoryError!HANDLE { | ||
| 911 | const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) { | ||
| 912 | error.Overflow => return error.NameTooLong, | ||
| 913 | }; | ||
| 914 | var nt_name = UNICODE_STRING{ | 764 | var nt_name = UNICODE_STRING{ |
| 915 | .Length = path_len_bytes, | 765 | .Length = path_len_bytes, |
| 916 | .MaximumLength = path_len_bytes, | 766 | .MaximumLength = path_len_bytes, |
| 917 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), | 767 | // The Windows API makes this mutable, but it will not mutate here. |
| 768 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)), | ||
| 918 | }; | 769 | }; |
| 919 | 770 | ||
| 920 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { | 771 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { |
| 921 | // Windows does not recognize this, but it does work with empty string. | 772 | // Windows does not recognize this, but it does work with empty string. |
| 922 | nt_name.Length = 0; | 773 | nt_name.Length = 0; |
| 923 | } | 774 | } |
| 775 | if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { | ||
| 776 | // Can't remove the parent directory with an open handle. | ||
| 777 | return error.FileBusy; | ||
| 778 | } | ||
| 924 | 779 | ||
| 925 | var attr = OBJECT_ATTRIBUTES{ | 780 | var attr = OBJECT_ATTRIBUTES{ |
| 926 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | 781 | .Length = @sizeOf(OBJECT_ATTRIBUTES), |
| 927 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, | 782 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir, |
| 928 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | 783 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 929 | .ObjectName = &nt_name, | 784 | .ObjectName = &nt_name, |
| 930 | .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null, | 785 | .SecurityDescriptor = null, |
| 931 | .SecurityQualityOfService = null, | 786 | .SecurityQualityOfService = null, |
| 932 | }; | 787 | }; |
| 933 | var io: IO_STATUS_BLOCK = undefined; | 788 | var io: IO_STATUS_BLOCK = undefined; |
| 934 | var result_handle: HANDLE = undefined; | 789 | var tmp_handle: HANDLE = undefined; |
| 935 | const rc = ntdll.NtCreateFile( | 790 | var rc = ntdll.NtCreateFile( |
| 936 | &result_handle, | 791 | &tmp_handle, |
| 937 | GENERIC_READ | SYNCHRONIZE, | 792 | SYNCHRONIZE | DELETE, |
| 938 | &attr, | 793 | &attr, |
| 939 | &io, | 794 | &io, |
| 940 | null, | 795 | null, |
| 941 | FILE_ATTRIBUTE_NORMAL, | 796 | 0, |
| 942 | FILE_SHARE_READ, | 797 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| 943 | FILE_CREATE, | 798 | FILE_OPEN, |
| 944 | FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, | 799 | create_options_flags, |
| 945 | null, | 800 | null, |
| 946 | 0, | 801 | 0, |
| 947 | ); | 802 | ); |
| 948 | switch (rc) { | 803 | switch (rc) { |
| 949 | .SUCCESS => return result_handle, | 804 | .SUCCESS => return CloseHandle(tmp_handle), |
| 950 | .OBJECT_NAME_INVALID => unreachable, | 805 | .OBJECT_NAME_INVALID => unreachable, |
| 951 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | 806 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 952 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | ||
| 953 | .NO_MEDIA_IN_DEVICE => return error.NoDevice, | ||
| 954 | .INVALID_PARAMETER => unreachable, | 807 | .INVALID_PARAMETER => unreachable, |
| 955 | .ACCESS_DENIED => return error.AccessDenied, | 808 | .FILE_IS_A_DIRECTORY => return error.IsDir, |
| 956 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | 809 | .NOT_A_DIRECTORY => return error.NotDir, |
| 957 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | ||
| 958 | else => return unexpectedStatus(rc), | 810 | else => return unexpectedStatus(rc), |
| 959 | } | 811 | } |
| 960 | } | 812 | } |
| 961 | 813 | ||
| 962 | pub const RemoveDirectoryError = error{ | 814 | pub const MoveFileError = error{Unexpected}; |
| 963 | FileNotFound, | ||
| 964 | DirNotEmpty, | ||
| 965 | Unexpected, | ||
| 966 | NotDir, | ||
| 967 | }; | ||
| 968 | 815 | ||
| 969 | pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void { | 816 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { |
| 970 | const dir_path_w = try sliceToPrefixedFileW(dir_path); | 817 | const old_path_w = try sliceToPrefixedFileW(old_path); |
| 971 | return RemoveDirectoryW(dir_path_w.span().ptr); | 818 | const new_path_w = try sliceToPrefixedFileW(new_path); |
| 819 | return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags); | ||
| 972 | } | 820 | } |
| 973 | 821 | ||
| 974 | pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void { | 822 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { |
| 975 | if (kernel32.RemoveDirectoryW(dir_path_w) == 0) { | 823 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { |
| 976 | switch (kernel32.GetLastError()) { | 824 | switch (kernel32.GetLastError()) { |
| 977 | .PATH_NOT_FOUND => return error.FileNotFound, | ||
| 978 | .DIR_NOT_EMPTY => return error.DirNotEmpty, | ||
| 979 | .DIRECTORY => return error.NotDir, | ||
| 980 | else => |err| return unexpectedError(err), | 825 | else => |err| return unexpectedError(err), |
| 981 | } | 826 | } |
| 982 | } | 827 | } |
| ... | @@ -1463,8 +1308,7 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { | ... | @@ -1463,8 +1308,7 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { |
| 1463 | } | 1308 | } |
| 1464 | 1309 | ||
| 1465 | /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, | 1310 | /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, |
| 1466 | /// it will get NT-style prefix `\??\` prepended automatically. For prepending | 1311 | /// it will get NT-style prefix `\??\` prepended automatically. |
| 1467 | /// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead. | ||
| 1468 | pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { | 1312 | pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { |
| 1469 | // TODO https://github.com/ziglang/zig/issues/2765 | 1313 | // TODO https://github.com/ziglang/zig/issues/2765 |
| 1470 | var path_space: PathSpace = undefined; | 1314 | var path_space: PathSpace = undefined; |
lib/std/os/windows/ntdll.zig+12| ... | @@ -54,6 +54,18 @@ pub extern "NtDll" fn NtDeviceIoControlFile( | ... | @@ -54,6 +54,18 @@ pub extern "NtDll" fn NtDeviceIoControlFile( |
| 54 | OutputBuffer: ?PVOID, | 54 | OutputBuffer: ?PVOID, |
| 55 | OutputBufferLength: ULONG, | 55 | OutputBufferLength: ULONG, |
| 56 | ) callconv(.Stdcall) NTSTATUS; | 56 | ) callconv(.Stdcall) NTSTATUS; |
| 57 | pub extern "NtDll" fn NtFsControlFile( | ||
| 58 | FileHandle: HANDLE, | ||
| 59 | Event: ?HANDLE, | ||
| 60 | ApcRoutine: ?IO_APC_ROUTINE, | ||
| 61 | ApcContext: ?*c_void, | ||
| 62 | IoStatusBlock: *IO_STATUS_BLOCK, | ||
| 63 | FsControlCode: ULONG, | ||
| 64 | InputBuffer: ?*const c_void, | ||
| 65 | InputBufferLength: ULONG, | ||
| 66 | OutputBuffer: ?PVOID, | ||
| 67 | OutputBufferLength: ULONG, | ||
| 68 | ) callconv(.Stdcall) NTSTATUS; | ||
| 57 | pub extern "NtDll" fn NtClose(Handle: HANDLE) callconv(.Stdcall) NTSTATUS; | 69 | pub extern "NtDll" fn NtClose(Handle: HANDLE) callconv(.Stdcall) NTSTATUS; |
| 58 | pub extern "NtDll" fn RtlDosPathNameToNtPathName_U( | 70 | pub extern "NtDll" fn RtlDosPathNameToNtPathName_U( |
| 59 | DosPathName: [*:0]const u16, | 71 | DosPathName: [*:0]const u16, |
lib/std/special/compiler_rt.zig+1| ... | @@ -92,6 +92,7 @@ comptime { | ... | @@ -92,6 +92,7 @@ comptime { |
| 92 | @export(@import("compiler_rt/floatunsidf.zig").__floatunsidf, .{ .name = "__floatunsidf", .linkage = linkage }); | 92 | @export(@import("compiler_rt/floatunsidf.zig").__floatunsidf, .{ .name = "__floatunsidf", .linkage = linkage }); |
| 93 | @export(@import("compiler_rt/floatundidf.zig").__floatundidf, .{ .name = "__floatundidf", .linkage = linkage }); | 93 | @export(@import("compiler_rt/floatundidf.zig").__floatundidf, .{ .name = "__floatundidf", .linkage = linkage }); |
| 94 | 94 | ||
| 95 | @export(@import("compiler_rt/floatditf.zig").__floatditf, .{ .name = "__floatditf", .linkage = linkage }); | ||
| 95 | @export(@import("compiler_rt/floattitf.zig").__floattitf, .{ .name = "__floattitf", .linkage = linkage }); | 96 | @export(@import("compiler_rt/floattitf.zig").__floattitf, .{ .name = "__floattitf", .linkage = linkage }); |
| 96 | @export(@import("compiler_rt/floattidf.zig").__floattidf, .{ .name = "__floattidf", .linkage = linkage }); | 97 | @export(@import("compiler_rt/floattidf.zig").__floattidf, .{ .name = "__floattidf", .linkage = linkage }); |
| 97 | @export(@import("compiler_rt/floattisf.zig").__floattisf, .{ .name = "__floattisf", .linkage = linkage }); | 98 | @export(@import("compiler_rt/floattisf.zig").__floattisf, .{ .name = "__floattisf", .linkage = linkage }); |
lib/std/special/compiler_rt/floatditf.zig created+38| ... | @@ -0,0 +1,38 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const is_test = builtin.is_test; | ||
| 3 | const std = @import("std"); | ||
| 4 | const maxInt = std.math.maxInt; | ||
| 5 | |||
| 6 | const significandBits = 112; | ||
| 7 | const exponentBias = 16383; | ||
| 8 | const implicitBit = (@as(u128, 1) << significandBits); | ||
| 9 | |||
| 10 | pub fn __floatditf(arg: i64) callconv(.C) f128 { | ||
| 11 | @setRuntimeSafety(is_test); | ||
| 12 | |||
| 13 | if (arg == 0) | ||
| 14 | return 0.0; | ||
| 15 | |||
| 16 | // All other cases begin by extracting the sign and absolute value of a | ||
| 17 | var sign: u128 = 0; | ||
| 18 | var aAbs = @bitCast(u64, arg); | ||
| 19 | if (arg < 0) { | ||
| 20 | sign = 1 << 127; | ||
| 21 | aAbs = ~@bitCast(u64, arg)+ 1; | ||
| 22 | } | ||
| 23 | |||
| 24 | // Exponent of (fp_t)a is the width of abs(a). | ||
| 25 | const exponent = 63 - @clz(u64, aAbs); | ||
| 26 | var result: u128 = undefined; | ||
| 27 | |||
| 28 | // Shift a into the significand field, rounding if it is a right-shift | ||
| 29 | const shift = significandBits - exponent; | ||
| 30 | result = @as(u128, aAbs) << shift ^ implicitBit; | ||
| 31 | |||
| 32 | result += (@as(u128, exponent) + exponentBias) << significandBits; | ||
| 33 | return @bitCast(f128, result | sign); | ||
| 34 | } | ||
| 35 | |||
| 36 | test "import floatditf" { | ||
| 37 | _ = @import("floatditf_test.zig"); | ||
| 38 | } | ||
lib/std/special/compiler_rt/floatditf_test.zig created+26| ... | @@ -0,0 +1,26 @@ | ||
| 1 | const __floatditf = @import("floatditf.zig").__floatditf; | ||
| 2 | const testing = @import("std").testing; | ||
| 3 | |||
| 4 | fn test__floatditf(a: i64, expected: f128) void { | ||
| 5 | const x = __floatditf(a); | ||
| 6 | testing.expect(x == expected); | ||
| 7 | } | ||
| 8 | |||
| 9 | test "floatditf" { | ||
| 10 | test__floatditf(0x7fffffffffffffff, make_ti(0x403dffffffffffff, 0xfffc000000000000)); | ||
| 11 | test__floatditf(0x123456789abcdef1, make_ti(0x403b23456789abcd, 0xef10000000000000)); | ||
| 12 | test__floatditf(0x2, make_ti(0x4000000000000000, 0x0)); | ||
| 13 | test__floatditf(0x1, make_ti(0x3fff000000000000, 0x0)); | ||
| 14 | test__floatditf(0x0, make_ti(0x0, 0x0)); | ||
| 15 | test__floatditf(@bitCast(i64, @as(u64, 0xffffffffffffffff)), make_ti(0xbfff000000000000, 0x0)); | ||
| 16 | test__floatditf(@bitCast(i64, @as(u64, 0xfffffffffffffffe)), make_ti(0xc000000000000000, 0x0)); | ||
| 17 | test__floatditf(-0x123456789abcdef1, make_ti(0xc03b23456789abcd, 0xef10000000000000)); | ||
| 18 | test__floatditf(@bitCast(i64, @as(u64, 0x8000000000000000)), make_ti(0xc03e000000000000, 0x0)); | ||
| 19 | } | ||
| 20 | |||
| 21 | fn make_ti(high: u64, low: u64) f128 { | ||
| 22 | var result: u128 = high; | ||
| 23 | result <<= 64; | ||
| 24 | result |= low; | ||
| 25 | return @bitCast(f128, result); | ||
| 26 | } | ||
lib/std/testing.zig+53| ... | @@ -171,6 +171,59 @@ test "expectEqual.union(enum)" { | ... | @@ -171,6 +171,59 @@ test "expectEqual.union(enum)" { |
| 171 | expectEqual(a10, a10); | 171 | expectEqual(a10, a10); |
| 172 | } | 172 | } |
| 173 | 173 | ||
| 174 | /// This function is intended to be used only in tests. When the actual value is not | ||
| 175 | /// within the margin of the expected value, | ||
| 176 | /// prints diagnostics to stderr to show exactly how they are not equal, then aborts. | ||
| 177 | /// The types must be floating point | ||
| 178 | pub fn expectWithinMargin(expected: anytype, actual: @TypeOf(expected), margin: @TypeOf(expected)) void { | ||
| 179 | std.debug.assert(margin >= 0.0); | ||
| 180 | |||
| 181 | switch (@typeInfo(@TypeOf(actual))) { | ||
| 182 | .Float, | ||
| 183 | .ComptimeFloat, | ||
| 184 | => { | ||
| 185 | if (@fabs(expected - actual) > margin) { | ||
| 186 | std.debug.panic("actual {}, not within margin {} of expected {}", .{ actual, margin, expected }); | ||
| 187 | } | ||
| 188 | }, | ||
| 189 | else => @compileError("Unable to compare non floating point values"), | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | test "expectWithinMargin.f32" { | ||
| 194 | const x: f32 = 12.0; | ||
| 195 | const y: f32 = 12.06; | ||
| 196 | |||
| 197 | expectWithinMargin(x, y, 0.1); | ||
| 198 | } | ||
| 199 | |||
| 200 | /// This function is intended to be used only in tests. When the actual value is not | ||
| 201 | /// within the epsilon of the expected value, | ||
| 202 | /// prints diagnostics to stderr to show exactly how they are not equal, then aborts. | ||
| 203 | /// The types must be floating point | ||
| 204 | pub fn expectWithinEpsilon(expected: anytype, actual: @TypeOf(expected), epsilon: @TypeOf(expected)) void { | ||
| 205 | std.debug.assert(epsilon >= 0.0 and epsilon <= 1.0); | ||
| 206 | |||
| 207 | const margin = epsilon * expected; | ||
| 208 | switch (@typeInfo(@TypeOf(actual))) { | ||
| 209 | .Float, | ||
| 210 | .ComptimeFloat, | ||
| 211 | => { | ||
| 212 | if (@fabs(expected - actual) > margin) { | ||
| 213 | std.debug.panic("actual {}, not within epsilon {}, of expected {}", .{ actual, epsilon, expected }); | ||
| 214 | } | ||
| 215 | }, | ||
| 216 | else => @compileError("Unable to compare non floating point values"), | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | test "expectWithinEpsilon.f32" { | ||
| 221 | const x: f32 = 12.0; | ||
| 222 | const y: f32 = 13.2; | ||
| 223 | |||
| 224 | expectWithinEpsilon(x, y, 0.1); | ||
| 225 | } | ||
| 226 | |||
| 174 | /// This function is intended to be used only in tests. When the two slices are not | 227 | /// This function is intended to be used only in tests. When the two slices are not |
| 175 | /// equal, prints diagnostics to stderr to show exactly how they are not equal, | 228 | /// equal, prints diagnostics to stderr to show exactly how they are not equal, |
| 176 | /// then aborts. | 229 | /// then aborts. |
lib/std/zig.zig+16| ... | @@ -43,6 +43,22 @@ pub fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usi | ... | @@ -43,6 +43,22 @@ pub fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usi |
| 43 | return .{ .line = line, .column = column }; | 43 | return .{ .line = line, .column = column }; |
| 44 | } | 44 | } |
| 45 | 45 | ||
| 46 | pub fn lineDelta(source: []const u8, start: usize, end: usize) isize { | ||
| 47 | var line: isize = 0; | ||
| 48 | if (end >= start) { | ||
| 49 | for (source[start..end]) |byte| switch (byte) { | ||
| 50 | '\n' => line += 1, | ||
| 51 | else => continue, | ||
| 52 | }; | ||
| 53 | } else { | ||
| 54 | for (source[end..start]) |byte| switch (byte) { | ||
| 55 | '\n' => line -= 1, | ||
| 56 | else => continue, | ||
| 57 | }; | ||
| 58 | } | ||
| 59 | return line; | ||
| 60 | } | ||
| 61 | |||
| 46 | /// Returns the standard file system basename of a binary generated by the Zig compiler. | 62 | /// Returns the standard file system basename of a binary generated by the Zig compiler. |
| 47 | pub fn binNameAlloc( | 63 | pub fn binNameAlloc( |
| 48 | allocator: *std.mem.Allocator, | 64 | allocator: *std.mem.Allocator, |
lib/std/zig/ast.zig+6-2| ... | @@ -1299,6 +1299,10 @@ pub const Node = struct { | ... | @@ -1299,6 +1299,10 @@ pub const Node = struct { |
| 1299 | }); | 1299 | }); |
| 1300 | } | 1300 | } |
| 1301 | 1301 | ||
| 1302 | pub fn body(self: *const FnProto) ?*Node { | ||
| 1303 | return self.getTrailer("body_node"); | ||
| 1304 | } | ||
| 1305 | |||
| 1302 | pub fn getTrailer(self: *const FnProto, comptime name: []const u8) ?TrailerFlags.Field(name) { | 1306 | pub fn getTrailer(self: *const FnProto, comptime name: []const u8) ?TrailerFlags.Field(name) { |
| 1303 | const trailers_start = @alignCast( | 1307 | const trailers_start = @alignCast( |
| 1304 | @alignOf(ParamDecl), | 1308 | @alignOf(ParamDecl), |
| ... | @@ -1381,7 +1385,7 @@ pub const Node = struct { | ... | @@ -1381,7 +1385,7 @@ pub const Node = struct { |
| 1381 | .Invalid => {}, | 1385 | .Invalid => {}, |
| 1382 | } | 1386 | } |
| 1383 | 1387 | ||
| 1384 | if (self.getTrailer("body_node")) |body_node| { | 1388 | if (self.body()) |body_node| { |
| 1385 | if (i < 1) return body_node; | 1389 | if (i < 1) return body_node; |
| 1386 | i -= 1; | 1390 | i -= 1; |
| 1387 | } | 1391 | } |
| ... | @@ -1397,7 +1401,7 @@ pub const Node = struct { | ... | @@ -1397,7 +1401,7 @@ pub const Node = struct { |
| 1397 | } | 1401 | } |
| 1398 | 1402 | ||
| 1399 | pub fn lastToken(self: *const FnProto) TokenIndex { | 1403 | pub fn lastToken(self: *const FnProto) TokenIndex { |
| 1400 | if (self.getTrailer("body_node")) |body_node| return body_node.lastToken(); | 1404 | if (self.body()) |body_node| return body_node.lastToken(); |
| 1401 | switch (self.return_type) { | 1405 | switch (self.return_type) { |
| 1402 | .Explicit, .InferErrorSet => |node| return node.lastToken(), | 1406 | .Explicit, .InferErrorSet => |node| return node.lastToken(), |
| 1403 | .Invalid => |tok| return tok, | 1407 | .Invalid => |tok| return tok, |
lib/std/zig/parse.zig+10-1| ... | @@ -201,7 +201,16 @@ const Parser = struct { | ... | @@ -201,7 +201,16 @@ const Parser = struct { |
| 201 | p.findNextContainerMember(); | 201 | p.findNextContainerMember(); |
| 202 | const next = p.token_ids[p.tok_i]; | 202 | const next = p.token_ids[p.tok_i]; |
| 203 | switch (next) { | 203 | switch (next) { |
| 204 | .Eof => break, | 204 | .Eof => { |
| 205 | // no invalid tokens were found | ||
| 206 | if (index == p.tok_i) break; | ||
| 207 | |||
| 208 | // Invalid tokens, add error and exit | ||
| 209 | try p.errors.append(p.gpa, .{ | ||
| 210 | .ExpectedToken = .{ .token = index, .expected_id = .Comma }, | ||
| 211 | }); | ||
| 212 | break; | ||
| 213 | }, | ||
| 205 | else => { | 214 | else => { |
| 206 | if (next == .RBrace) { | 215 | if (next == .RBrace) { |
| 207 | if (!top_level) break; | 216 | if (!top_level) break; |
lib/std/zig/parser_test.zig+8| ... | @@ -293,6 +293,14 @@ test "zig fmt: decl between fields" { | ... | @@ -293,6 +293,14 @@ test "zig fmt: decl between fields" { |
| 293 | }); | 293 | }); |
| 294 | } | 294 | } |
| 295 | 295 | ||
| 296 | test "zig fmt: eof after missing comma" { | ||
| 297 | try testError( | ||
| 298 | \\foo() | ||
| 299 | , &[_]Error{ | ||
| 300 | .ExpectedToken, | ||
| 301 | }); | ||
| 302 | } | ||
| 303 | |||
| 296 | test "zig fmt: errdefer with payload" { | 304 | test "zig fmt: errdefer with payload" { |
| 297 | try testCanonical( | 305 | try testCanonical( |
| 298 | \\pub fn main() anyerror!void { | 306 | \\pub fn main() anyerror!void { |
lib/std/zig/string_literal.zig-1| ... | @@ -104,7 +104,6 @@ pub fn parse( | ... | @@ -104,7 +104,6 @@ pub fn parse( |
| 104 | return error.InvalidCharacter; | 104 | return error.InvalidCharacter; |
| 105 | }, | 105 | }, |
| 106 | }, | 106 | }, |
| 107 | else => unreachable, | ||
| 108 | } | 107 | } |
| 109 | } | 108 | } |
| 110 | unreachable; | 109 | unreachable; |
src-self-hosted/Module.zig+196-1281| ... | @@ -6,6 +6,7 @@ const Value = @import("value.zig").Value; | ... | @@ -6,6 +6,7 @@ const Value = @import("value.zig").Value; |
| 6 | const Type = @import("type.zig").Type; | 6 | const Type = @import("type.zig").Type; |
| 7 | const TypedValue = @import("TypedValue.zig"); | 7 | const TypedValue = @import("TypedValue.zig"); |
| 8 | const assert = std.debug.assert; | 8 | const assert = std.debug.assert; |
| 9 | const log = std.log; | ||
| 9 | const BigIntConst = std.math.big.int.Const; | 10 | const BigIntConst = std.math.big.int.Const; |
| 10 | const BigIntMutable = std.math.big.int.Mutable; | 11 | const BigIntMutable = std.math.big.int.Mutable; |
| 11 | const Target = std.Target; | 12 | const Target = std.Target; |
| ... | @@ -20,6 +21,7 @@ const ast = std.zig.ast; | ... | @@ -20,6 +21,7 @@ const ast = std.zig.ast; |
| 20 | const trace = @import("tracy.zig").trace; | 21 | const trace = @import("tracy.zig").trace; |
| 21 | const liveness = @import("liveness.zig"); | 22 | const liveness = @import("liveness.zig"); |
| 22 | const astgen = @import("astgen.zig"); | 23 | const astgen = @import("astgen.zig"); |
| 24 | const zir_sema = @import("zir_sema.zig"); | ||
| 23 | 25 | ||
| 24 | /// General-purpose allocator. Used for both temporary and long-term storage. | 26 | /// General-purpose allocator. Used for both temporary and long-term storage. |
| 25 | gpa: *Allocator, | 27 | gpa: *Allocator, |
| ... | @@ -46,7 +48,6 @@ export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, | ... | @@ -46,7 +48,6 @@ export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, |
| 46 | /// Maps fully qualified namespaced names to the Decl struct for them. | 48 | /// Maps fully qualified namespaced names to the Decl struct for them. |
| 47 | decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{}, | 49 | decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{}, |
| 48 | 50 | ||
| 49 | optimize_mode: std.builtin.Mode, | ||
| 50 | link_error_flags: link.File.ErrorFlags = .{}, | 51 | link_error_flags: link.File.ErrorFlags = .{}, |
| 51 | 52 | ||
| 52 | work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), | 53 | work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), |
| ... | @@ -75,6 +76,8 @@ next_anon_name_index: usize = 0, | ... | @@ -75,6 +76,8 @@ next_anon_name_index: usize = 0, |
| 75 | /// contains Decls that need to be deleted if they end up having no references to them. | 76 | /// contains Decls that need to be deleted if they end up having no references to them. |
| 76 | deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, | 77 | deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, |
| 77 | 78 | ||
| 79 | /// Owned by Module. | ||
| 80 | root_name: []u8, | ||
| 78 | keep_source_files_loaded: bool, | 81 | keep_source_files_loaded: bool, |
| 79 | 82 | ||
| 80 | pub const InnerError = error{ OutOfMemory, AnalysisFail }; | 83 | pub const InnerError = error{ OutOfMemory, AnalysisFail }; |
| ... | @@ -86,6 +89,9 @@ const WorkItem = union(enum) { | ... | @@ -86,6 +89,9 @@ const WorkItem = union(enum) { |
| 86 | /// It may have already be analyzed, or it may have been determined | 89 | /// It may have already be analyzed, or it may have been determined |
| 87 | /// to be outdated; in this case perform semantic analysis again. | 90 | /// to be outdated; in this case perform semantic analysis again. |
| 88 | analyze_decl: *Decl, | 91 | analyze_decl: *Decl, |
| 92 | /// The source file containing the Decl has been updated, and so the | ||
| 93 | /// Decl may need its line number information updated in the debug info. | ||
| 94 | update_line_number: *Decl, | ||
| 89 | }; | 95 | }; |
| 90 | 96 | ||
| 91 | pub const Export = struct { | 97 | pub const Export = struct { |
| ... | @@ -173,6 +179,13 @@ pub const Decl = struct { | ... | @@ -173,6 +179,13 @@ pub const Decl = struct { |
| 173 | /// This is populated regardless of semantic analysis and code generation. | 179 | /// This is populated regardless of semantic analysis and code generation. |
| 174 | link: link.File.Elf.TextBlock = link.File.Elf.TextBlock.empty, | 180 | link: link.File.Elf.TextBlock = link.File.Elf.TextBlock.empty, |
| 175 | 181 | ||
| 182 | /// Represents the function in the linked output file, if the `Decl` is a function. | ||
| 183 | /// This is stored here and not in `Fn` because `Decl` survives across updates but | ||
| 184 | /// `Fn` does not. | ||
| 185 | /// TODO Look into making `Fn` a longer lived structure and moving this field there | ||
| 186 | /// to save on memory usage. | ||
| 187 | fn_link: link.File.Elf.SrcFn = link.File.Elf.SrcFn.empty, | ||
| 188 | |||
| 176 | contents_hash: std.zig.SrcHash, | 189 | contents_hash: std.zig.SrcHash, |
| 177 | 190 | ||
| 178 | /// The shallow set of other decls whose typed_value could possibly change if this Decl's | 191 | /// The shallow set of other decls whose typed_value could possibly change if this Decl's |
| ... | @@ -233,7 +246,7 @@ pub const Decl = struct { | ... | @@ -233,7 +246,7 @@ pub const Decl = struct { |
| 233 | 246 | ||
| 234 | pub fn dump(self: *Decl) void { | 247 | pub fn dump(self: *Decl) void { |
| 235 | const loc = std.zig.findLineColumn(self.scope.source.bytes, self.src); | 248 | const loc = std.zig.findLineColumn(self.scope.source.bytes, self.src); |
| 236 | std.debug.warn("{}:{}:{} name={} status={}", .{ | 249 | std.debug.print("{}:{}:{} name={} status={}", .{ |
| 237 | self.scope.sub_file_path, | 250 | self.scope.sub_file_path, |
| 238 | loc.line + 1, | 251 | loc.line + 1, |
| 239 | loc.column + 1, | 252 | loc.column + 1, |
| ... | @@ -241,12 +254,12 @@ pub const Decl = struct { | ... | @@ -241,12 +254,12 @@ pub const Decl = struct { |
| 241 | @tagName(self.analysis), | 254 | @tagName(self.analysis), |
| 242 | }); | 255 | }); |
| 243 | if (self.typedValueManaged()) |tvm| { | 256 | if (self.typedValueManaged()) |tvm| { |
| 244 | std.debug.warn(" ty={} val={}", .{ tvm.typed_value.ty, tvm.typed_value.val }); | 257 | std.debug.print(" ty={} val={}", .{ tvm.typed_value.ty, tvm.typed_value.val }); |
| 245 | } | 258 | } |
| 246 | std.debug.warn("\n", .{}); | 259 | std.debug.print("\n", .{}); |
| 247 | } | 260 | } |
| 248 | 261 | ||
| 249 | fn typedValueManaged(self: *Decl) ?*TypedValue.Managed { | 262 | pub fn typedValueManaged(self: *Decl) ?*TypedValue.Managed { |
| 250 | switch (self.typed_value) { | 263 | switch (self.typed_value) { |
| 251 | .most_recent => |*x| return x, | 264 | .most_recent => |*x| return x, |
| 252 | .never_succeeded => return null, | 265 | .never_succeeded => return null, |
| ... | @@ -384,18 +397,6 @@ pub const Scope = struct { | ... | @@ -384,18 +397,6 @@ pub const Scope = struct { |
| 384 | }; | 397 | }; |
| 385 | } | 398 | } |
| 386 | 399 | ||
| 387 | pub fn dumpInst(self: *Scope, inst: *Inst) void { | ||
| 388 | const zir_module = self.namespace(); | ||
| 389 | const loc = std.zig.findLineColumn(zir_module.source.bytes, inst.src); | ||
| 390 | std.debug.warn("{}:{}:{}: {}: ty={}\n", .{ | ||
| 391 | zir_module.sub_file_path, | ||
| 392 | loc.line + 1, | ||
| 393 | loc.column + 1, | ||
| 394 | @tagName(inst.tag), | ||
| 395 | inst.ty, | ||
| 396 | }); | ||
| 397 | } | ||
| 398 | |||
| 399 | /// Asserts the scope has a parent which is a ZIRModule or File and | 400 | /// Asserts the scope has a parent which is a ZIRModule or File and |
| 400 | /// returns the sub_file_path field. | 401 | /// returns the sub_file_path field. |
| 401 | pub fn subFilePath(base: *Scope) []const u8 { | 402 | pub fn subFilePath(base: *Scope) []const u8 { |
| ... | @@ -551,7 +552,7 @@ pub const Scope = struct { | ... | @@ -551,7 +552,7 @@ pub const Scope = struct { |
| 551 | 552 | ||
| 552 | pub fn dumpSrc(self: *File, src: usize) void { | 553 | pub fn dumpSrc(self: *File, src: usize) void { |
| 553 | const loc = std.zig.findLineColumn(self.source.bytes, src); | 554 | const loc = std.zig.findLineColumn(self.source.bytes, src); |
| 554 | std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); | 555 | std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); |
| 555 | } | 556 | } |
| 556 | 557 | ||
| 557 | pub fn getSource(self: *File, module: *Module) ![:0]const u8 { | 558 | pub fn getSource(self: *File, module: *Module) ![:0]const u8 { |
| ... | @@ -653,7 +654,7 @@ pub const Scope = struct { | ... | @@ -653,7 +654,7 @@ pub const Scope = struct { |
| 653 | 654 | ||
| 654 | pub fn dumpSrc(self: *ZIRModule, src: usize) void { | 655 | pub fn dumpSrc(self: *ZIRModule, src: usize) void { |
| 655 | const loc = std.zig.findLineColumn(self.source.bytes, src); | 656 | const loc = std.zig.findLineColumn(self.source.bytes, src); |
| 656 | std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); | 657 | std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); |
| 657 | } | 658 | } |
| 658 | 659 | ||
| 659 | pub fn getSource(self: *ZIRModule, module: *Module) ![:0]const u8 { | 660 | pub fn getSource(self: *ZIRModule, module: *Module) ![:0]const u8 { |
| ... | @@ -784,6 +785,7 @@ pub const AllErrors = struct { | ... | @@ -784,6 +785,7 @@ pub const AllErrors = struct { |
| 784 | 785 | ||
| 785 | pub const InitOptions = struct { | 786 | pub const InitOptions = struct { |
| 786 | target: std.Target, | 787 | target: std.Target, |
| 788 | root_name: []const u8, | ||
| 787 | root_pkg: *Package, | 789 | root_pkg: *Package, |
| 788 | output_mode: std.builtin.OutputMode, | 790 | output_mode: std.builtin.OutputMode, |
| 789 | bin_file_dir: ?std.fs.Dir = null, | 791 | bin_file_dir: ?std.fs.Dir = null, |
| ... | @@ -795,12 +797,18 @@ pub const InitOptions = struct { | ... | @@ -795,12 +797,18 @@ pub const InitOptions = struct { |
| 795 | }; | 797 | }; |
| 796 | 798 | ||
| 797 | pub fn init(gpa: *Allocator, options: InitOptions) !Module { | 799 | pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 800 | const root_name = try gpa.dupe(u8, options.root_name); | ||
| 801 | errdefer gpa.free(root_name); | ||
| 802 | |||
| 798 | const bin_file_dir = options.bin_file_dir orelse std.fs.cwd(); | 803 | const bin_file_dir = options.bin_file_dir orelse std.fs.cwd(); |
| 799 | const bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{ | 804 | const bin_file = try link.File.openPath(gpa, bin_file_dir, options.bin_file_path, .{ |
| 805 | .root_name = root_name, | ||
| 806 | .root_pkg = options.root_pkg, | ||
| 800 | .target = options.target, | 807 | .target = options.target, |
| 801 | .output_mode = options.output_mode, | 808 | .output_mode = options.output_mode, |
| 802 | .link_mode = options.link_mode orelse .Static, | 809 | .link_mode = options.link_mode orelse .Static, |
| 803 | .object_format = options.object_format orelse options.target.getObjectFormat(), | 810 | .object_format = options.object_format orelse options.target.getObjectFormat(), |
| 811 | .optimize_mode = options.optimize_mode, | ||
| 804 | }); | 812 | }); |
| 805 | errdefer bin_file.destroy(); | 813 | errdefer bin_file.destroy(); |
| 806 | 814 | ||
| ... | @@ -832,12 +840,12 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { | ... | @@ -832,12 +840,12 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 832 | 840 | ||
| 833 | return Module{ | 841 | return Module{ |
| 834 | .gpa = gpa, | 842 | .gpa = gpa, |
| 843 | .root_name = root_name, | ||
| 835 | .root_pkg = options.root_pkg, | 844 | .root_pkg = options.root_pkg, |
| 836 | .root_scope = root_scope, | 845 | .root_scope = root_scope, |
| 837 | .bin_file_dir = bin_file_dir, | 846 | .bin_file_dir = bin_file_dir, |
| 838 | .bin_file_path = options.bin_file_path, | 847 | .bin_file_path = options.bin_file_path, |
| 839 | .bin_file = bin_file, | 848 | .bin_file = bin_file, |
| 840 | .optimize_mode = options.optimize_mode, | ||
| 841 | .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa), | 849 | .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa), |
| 842 | .keep_source_files_loaded = options.keep_source_files_loaded, | 850 | .keep_source_files_loaded = options.keep_source_files_loaded, |
| 843 | }; | 851 | }; |
| ... | @@ -846,6 +854,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { | ... | @@ -846,6 +854,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 846 | pub fn deinit(self: *Module) void { | 854 | pub fn deinit(self: *Module) void { |
| 847 | self.bin_file.destroy(); | 855 | self.bin_file.destroy(); |
| 848 | const gpa = self.gpa; | 856 | const gpa = self.gpa; |
| 857 | self.gpa.free(self.root_name); | ||
| 849 | self.deletion_set.deinit(gpa); | 858 | self.deletion_set.deinit(gpa); |
| 850 | self.work_queue.deinit(); | 859 | self.work_queue.deinit(); |
| 851 | 860 | ||
| ... | @@ -887,13 +896,18 @@ pub fn deinit(self: *Module) void { | ... | @@ -887,13 +896,18 @@ pub fn deinit(self: *Module) void { |
| 887 | 896 | ||
| 888 | fn freeExportList(gpa: *Allocator, export_list: []*Export) void { | 897 | fn freeExportList(gpa: *Allocator, export_list: []*Export) void { |
| 889 | for (export_list) |exp| { | 898 | for (export_list) |exp| { |
| 899 | gpa.free(exp.options.name); | ||
| 890 | gpa.destroy(exp); | 900 | gpa.destroy(exp); |
| 891 | } | 901 | } |
| 892 | gpa.free(export_list); | 902 | gpa.free(export_list); |
| 893 | } | 903 | } |
| 894 | 904 | ||
| 895 | pub fn target(self: Module) std.Target { | 905 | pub fn target(self: Module) std.Target { |
| 896 | return self.bin_file.options().target; | 906 | return self.bin_file.options.target; |
| 907 | } | ||
| 908 | |||
| 909 | pub fn optimizeMode(self: Module) std.builtin.Mode { | ||
| 910 | return self.bin_file.options.optimize_mode; | ||
| 897 | } | 911 | } |
| 898 | 912 | ||
| 899 | /// Detect changes to source files, perform semantic analysis, and update the output files. | 913 | /// Detect changes to source files, perform semantic analysis, and update the output files. |
| ... | @@ -941,7 +955,6 @@ pub fn update(self: *Module) !void { | ... | @@ -941,7 +955,6 @@ pub fn update(self: *Module) !void { |
| 941 | } | 955 | } |
| 942 | 956 | ||
| 943 | self.link_error_flags = self.bin_file.errorFlags(); | 957 | self.link_error_flags = self.bin_file.errorFlags(); |
| 944 | std.log.debug(.module, "link_error_flags: {}\n", .{self.link_error_flags}); | ||
| 945 | 958 | ||
| 946 | // If there are any errors, we anticipate the source files being loaded | 959 | // If there are any errors, we anticipate the source files being loaded |
| 947 | // to report error messages. Otherwise we unload all source files to save memory. | 960 | // 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 { | ... | @@ -1055,22 +1068,14 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 1055 | error.AnalysisFail => { | 1068 | error.AnalysisFail => { |
| 1056 | decl.analysis = .dependency_failure; | 1069 | decl.analysis = .dependency_failure; |
| 1057 | }, | 1070 | }, |
| 1058 | error.CGenFailure => { | ||
| 1059 | // Error is handled by CBE, don't try adding it again | ||
| 1060 | }, | ||
| 1061 | else => { | 1071 | else => { |
| 1062 | try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); | 1072 | try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); |
| 1063 | const result = self.failed_decls.getOrPutAssumeCapacity(decl); | 1073 | self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( |
| 1064 | if (result.found_existing) { | 1074 | self.gpa, |
| 1065 | std.debug.panic("Internal error: attempted to override error '{}' with 'unable to codegen: {}'", .{ result.entry.value.msg, @errorName(err) }); | 1075 | decl.src(), |
| 1066 | } else { | 1076 | "unable to codegen: {}", |
| 1067 | result.entry.value = try ErrorMsg.create( | 1077 | .{@errorName(err)}, |
| 1068 | self.gpa, | 1078 | )); |
| 1069 | decl.src(), | ||
| 1070 | "unable to codegen: {}", | ||
| 1071 | .{@errorName(err)}, | ||
| 1072 | ); | ||
| 1073 | } | ||
| 1074 | decl.analysis = .codegen_failure_retryable; | 1079 | decl.analysis = .codegen_failure_retryable; |
| 1075 | }, | 1080 | }, |
| 1076 | }; | 1081 | }; |
| ... | @@ -1082,10 +1087,22 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { | ... | @@ -1082,10 +1087,22 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 1082 | error.AnalysisFail => continue, | 1087 | error.AnalysisFail => continue, |
| 1083 | }; | 1088 | }; |
| 1084 | }, | 1089 | }, |
| 1090 | .update_line_number => |decl| { | ||
| 1091 | self.bin_file.updateDeclLineNumber(self, decl) catch |err| { | ||
| 1092 | try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); | ||
| 1093 | self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( | ||
| 1094 | self.gpa, | ||
| 1095 | decl.src(), | ||
| 1096 | "unable to update line number: {}", | ||
| 1097 | .{@errorName(err)}, | ||
| 1098 | )); | ||
| 1099 | decl.analysis = .codegen_failure_retryable; | ||
| 1100 | }; | ||
| 1101 | }, | ||
| 1085 | }; | 1102 | }; |
| 1086 | } | 1103 | } |
| 1087 | 1104 | ||
| 1088 | fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { | 1105 | pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { |
| 1089 | const tracy = trace(@src()); | 1106 | const tracy = trace(@src()); |
| 1090 | defer tracy.end(); | 1107 | defer tracy.end(); |
| 1091 | 1108 | ||
| ... | @@ -1099,12 +1116,10 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { | ... | @@ -1099,12 +1116,10 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { |
| 1099 | .codegen_failure_retryable, | 1116 | .codegen_failure_retryable, |
| 1100 | => return error.AnalysisFail, | 1117 | => return error.AnalysisFail, |
| 1101 | 1118 | ||
| 1102 | .complete, .outdated => blk: { | 1119 | .complete => return, |
| 1103 | if (decl.generation == self.generation) { | 1120 | |
| 1104 | assert(decl.analysis == .complete); | 1121 | .outdated => blk: { |
| 1105 | return; | 1122 | log.debug(.module, "re-analyzing {}\n", .{decl.name}); |
| 1106 | } | ||
| 1107 | //std.debug.warn("re-analyzing {}\n", .{decl.name}); | ||
| 1108 | 1123 | ||
| 1109 | // The exports this Decl performs will be re-discovered, so we remove them here | 1124 | // The exports this Decl performs will be re-discovered, so we remove them here |
| 1110 | // prior to re-analysis. | 1125 | // prior to re-analysis. |
| ... | @@ -1129,7 +1144,7 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { | ... | @@ -1129,7 +1144,7 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { |
| 1129 | }; | 1144 | }; |
| 1130 | 1145 | ||
| 1131 | const type_changed = if (self.root_scope.cast(Scope.ZIRModule)) |zir_module| | 1146 | const type_changed = if (self.root_scope.cast(Scope.ZIRModule)) |zir_module| |
| 1132 | try self.analyzeZirDecl(decl, zir_module.contents.module.decls[decl.src_index]) | 1147 | try zir_sema.analyzeZirDecl(self, decl, zir_module.contents.module.decls[decl.src_index]) |
| 1133 | else | 1148 | else |
| 1134 | self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { | 1149 | self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { |
| 1135 | error.OutOfMemory => return error.OutOfMemory, | 1150 | error.OutOfMemory => return error.OutOfMemory, |
| ... | @@ -1205,7 +1220,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1205,7 +1220,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1205 | const param_types = try fn_type_scope.arena.alloc(*zir.Inst, param_decls.len); | 1220 | const param_types = try fn_type_scope.arena.alloc(*zir.Inst, param_decls.len); |
| 1206 | 1221 | ||
| 1207 | const fn_src = tree.token_locs[fn_proto.fn_token].start; | 1222 | const fn_src = tree.token_locs[fn_proto.fn_token].start; |
| 1208 | const type_type = try self.addZIRInstConst(&fn_type_scope.base, fn_src, .{ | 1223 | const type_type = try astgen.addZIRInstConst(self, &fn_type_scope.base, fn_src, .{ |
| 1209 | .ty = Type.initTag(.type), | 1224 | .ty = Type.initTag(.type), |
| 1210 | .val = Value.initTag(.type_type), | 1225 | .val = Value.initTag(.type_type), |
| 1211 | }); | 1226 | }); |
| ... | @@ -1244,11 +1259,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1244,11 +1259,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1244 | }; | 1259 | }; |
| 1245 | 1260 | ||
| 1246 | const return_type_inst = try astgen.expr(self, &fn_type_scope.base, type_type_rl, return_type_expr); | 1261 | const return_type_inst = try astgen.expr(self, &fn_type_scope.base, type_type_rl, return_type_expr); |
| 1247 | const fn_type_inst = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.FnType, .{ | 1262 | const fn_type_inst = try astgen.addZIRInst(self, &fn_type_scope.base, fn_src, zir.Inst.FnType, .{ |
| 1248 | .return_type = return_type_inst, | 1263 | .return_type = return_type_inst, |
| 1249 | .param_types = param_types, | 1264 | .param_types = param_types, |
| 1250 | }, .{}); | 1265 | }, .{}); |
| 1251 | _ = try self.addZIRUnOp(&fn_type_scope.base, fn_src, .@"return", fn_type_inst); | 1266 | _ = try astgen.addZIRUnOp(self, &fn_type_scope.base, fn_src, .@"return", fn_type_inst); |
| 1252 | 1267 | ||
| 1253 | // We need the memory for the Type to go into the arena for the Decl | 1268 | // We need the memory for the Type to go into the arena for the Decl |
| 1254 | var decl_arena = std.heap.ArenaAllocator.init(self.gpa); | 1269 | var decl_arena = std.heap.ArenaAllocator.init(self.gpa); |
| ... | @@ -1264,7 +1279,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1264,7 +1279,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1264 | }; | 1279 | }; |
| 1265 | defer block_scope.instructions.deinit(self.gpa); | 1280 | defer block_scope.instructions.deinit(self.gpa); |
| 1266 | 1281 | ||
| 1267 | const fn_type = try self.analyzeBodyValueAsType(&block_scope, .{ | 1282 | const fn_type = try zir_sema.analyzeBodyValueAsType(self, &block_scope, .{ |
| 1268 | .instructions = fn_type_scope.instructions.items, | 1283 | .instructions = fn_type_scope.instructions.items, |
| 1269 | }); | 1284 | }); |
| 1270 | const new_func = try decl_arena.allocator.create(Fn); | 1285 | const new_func = try decl_arena.allocator.create(Fn); |
| ... | @@ -1317,7 +1332,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1317,7 +1332,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1317 | !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())) | 1332 | !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())) |
| 1318 | { | 1333 | { |
| 1319 | const src = tree.token_locs[body_block.rbrace].start; | 1334 | const src = tree.token_locs[body_block.rbrace].start; |
| 1320 | _ = try self.addZIRNoOp(&gen_scope.base, src, .returnvoid); | 1335 | _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .returnvoid); |
| 1321 | } | 1336 | } |
| 1322 | 1337 | ||
| 1323 | const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); | 1338 | const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); |
| ... | @@ -1387,19 +1402,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1387,19 +1402,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1387 | } | 1402 | } |
| 1388 | } | 1403 | } |
| 1389 | 1404 | ||
| 1390 | fn analyzeBodyValueAsType(self: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type { | ||
| 1391 | try self.analyzeBody(&block_scope.base, body); | ||
| 1392 | for (block_scope.instructions.items) |inst| { | ||
| 1393 | if (inst.castTag(.ret)) |ret| { | ||
| 1394 | const val = try self.resolveConstValue(&block_scope.base, ret.operand); | ||
| 1395 | return val.toType(); | ||
| 1396 | } else { | ||
| 1397 | return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{}); | ||
| 1398 | } | ||
| 1399 | } | ||
| 1400 | unreachable; | ||
| 1401 | } | ||
| 1402 | |||
| 1403 | fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void { | 1405 | fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void { |
| 1404 | try depender.dependencies.ensureCapacity(self.gpa, depender.dependencies.items().len + 1); | 1406 | try depender.dependencies.ensureCapacity(self.gpa, depender.dependencies.items().len + 1); |
| 1405 | try dependee.dependants.ensureCapacity(self.gpa, dependee.dependants.items().len + 1); | 1407 | 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 { | ... | @@ -1492,6 +1494,9 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { |
| 1492 | } | 1494 | } |
| 1493 | 1495 | ||
| 1494 | fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { | 1496 | fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1497 | const tracy = trace(@src()); | ||
| 1498 | defer tracy.end(); | ||
| 1499 | |||
| 1495 | // We may be analyzing it for the first time, or this may be | 1500 | // We may be analyzing it for the first time, or this may be |
| 1496 | // an incremental update. This code handles both cases. | 1501 | // an incremental update. This code handles both cases. |
| 1497 | const tree = try self.getAstTree(root_scope); | 1502 | const tree = try self.getAstTree(root_scope); |
| ... | @@ -1533,6 +1538,10 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { | ... | @@ -1533,6 +1538,10 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1533 | if (!srcHashEql(decl.contents_hash, contents_hash)) { | 1538 | if (!srcHashEql(decl.contents_hash, contents_hash)) { |
| 1534 | try self.markOutdatedDecl(decl); | 1539 | try self.markOutdatedDecl(decl); |
| 1535 | decl.contents_hash = contents_hash; | 1540 | decl.contents_hash = contents_hash; |
| 1541 | } else if (decl.fn_link.len != 0) { | ||
| 1542 | // TODO Look into detecting when this would be unnecessary by storing enough state | ||
| 1543 | // in `Decl` to notice that the line number did not change. | ||
| 1544 | self.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl }); | ||
| 1536 | } | 1545 | } |
| 1537 | } | 1546 | } |
| 1538 | } else { | 1547 | } else { |
| ... | @@ -1551,7 +1560,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { | ... | @@ -1551,7 +1560,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1551 | // Handle explicitly deleted decls from the source code. Not to be confused | 1560 | // Handle explicitly deleted decls from the source code. Not to be confused |
| 1552 | // with when we delete decls because they are no longer referenced. | 1561 | // with when we delete decls because they are no longer referenced. |
| 1553 | for (deleted_decls.items()) |entry| { | 1562 | for (deleted_decls.items()) |entry| { |
| 1554 | //std.debug.warn("noticed '{}' deleted from source\n", .{entry.key.name}); | 1563 | log.debug(.module, "noticed '{}' deleted from source\n", .{entry.key.name}); |
| 1555 | try self.deleteDecl(entry.key); | 1564 | try self.deleteDecl(entry.key); |
| 1556 | } | 1565 | } |
| 1557 | } | 1566 | } |
| ... | @@ -1580,7 +1589,6 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { | ... | @@ -1580,7 +1589,6 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 1580 | const name_hash = root_scope.fullyQualifiedNameHash(src_decl.name); | 1589 | const name_hash = root_scope.fullyQualifiedNameHash(src_decl.name); |
| 1581 | if (self.decl_table.get(name_hash)) |decl| { | 1590 | if (self.decl_table.get(name_hash)) |decl| { |
| 1582 | deleted_decls.removeAssertDiscard(decl); | 1591 | deleted_decls.removeAssertDiscard(decl); |
| 1583 | //std.debug.warn("'{}' contents: '{}'\n", .{ src_decl.name, src_decl.contents }); | ||
| 1584 | if (!srcHashEql(src_decl.contents_hash, decl.contents_hash)) { | 1592 | if (!srcHashEql(src_decl.contents_hash, decl.contents_hash)) { |
| 1585 | try self.markOutdatedDecl(decl); | 1593 | try self.markOutdatedDecl(decl); |
| 1586 | decl.contents_hash = src_decl.contents_hash; | 1594 | decl.contents_hash = src_decl.contents_hash; |
| ... | @@ -1600,12 +1608,12 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { | ... | @@ -1600,12 +1608,12 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 1600 | } | 1608 | } |
| 1601 | } | 1609 | } |
| 1602 | for (exports_to_resolve.items) |export_decl| { | 1610 | for (exports_to_resolve.items) |export_decl| { |
| 1603 | _ = try self.resolveZirDecl(&root_scope.base, export_decl); | 1611 | _ = try zir_sema.resolveZirDecl(self, &root_scope.base, export_decl); |
| 1604 | } | 1612 | } |
| 1605 | // Handle explicitly deleted decls from the source code. Not to be confused | 1613 | // Handle explicitly deleted decls from the source code. Not to be confused |
| 1606 | // with when we delete decls because they are no longer referenced. | 1614 | // with when we delete decls because they are no longer referenced. |
| 1607 | for (deleted_decls.items()) |entry| { | 1615 | for (deleted_decls.items()) |entry| { |
| 1608 | //std.debug.warn("noticed '{}' deleted from source\n", .{entry.key.name}); | 1616 | log.debug(.module, "noticed '{}' deleted from source\n", .{entry.key.name}); |
| 1609 | try self.deleteDecl(entry.key); | 1617 | try self.deleteDecl(entry.key); |
| 1610 | } | 1618 | } |
| 1611 | } | 1619 | } |
| ... | @@ -1617,7 +1625,7 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { | ... | @@ -1617,7 +1625,7 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { |
| 1617 | // not be present in the set, and this does nothing. | 1625 | // not be present in the set, and this does nothing. |
| 1618 | decl.scope.removeDecl(decl); | 1626 | decl.scope.removeDecl(decl); |
| 1619 | 1627 | ||
| 1620 | //std.debug.warn("deleting decl '{}'\n", .{decl.name}); | 1628 | log.debug(.module, "deleting decl '{}'\n", .{decl.name}); |
| 1621 | const name_hash = decl.fullyQualifiedNameHash(); | 1629 | const name_hash = decl.fullyQualifiedNameHash(); |
| 1622 | self.decl_table.removeAssertDiscard(name_hash); | 1630 | self.decl_table.removeAssertDiscard(name_hash); |
| 1623 | // Remove itself from its dependencies, because we are about to destroy the decl pointer. | 1631 | // 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 { | ... | @@ -1679,6 +1687,7 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { |
| 1679 | entry.value.destroy(self.gpa); | 1687 | entry.value.destroy(self.gpa); |
| 1680 | } | 1688 | } |
| 1681 | _ = self.symbol_exports.remove(exp.options.name); | 1689 | _ = self.symbol_exports.remove(exp.options.name); |
| 1690 | self.gpa.free(exp.options.name); | ||
| 1682 | self.gpa.destroy(exp); | 1691 | self.gpa.destroy(exp); |
| 1683 | } | 1692 | } |
| 1684 | self.gpa.free(kv.value); | 1693 | self.gpa.free(kv.value); |
| ... | @@ -1703,17 +1712,17 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { | ... | @@ -1703,17 +1712,17 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { |
| 1703 | const fn_zir = func.analysis.queued; | 1712 | const fn_zir = func.analysis.queued; |
| 1704 | defer fn_zir.arena.promote(self.gpa).deinit(); | 1713 | defer fn_zir.arena.promote(self.gpa).deinit(); |
| 1705 | func.analysis = .{ .in_progress = {} }; | 1714 | func.analysis = .{ .in_progress = {} }; |
| 1706 | //std.debug.warn("set {} to in_progress\n", .{decl.name}); | 1715 | log.debug(.module, "set {} to in_progress\n", .{decl.name}); |
| 1707 | 1716 | ||
| 1708 | try self.analyzeBody(&inner_block.base, fn_zir.body); | 1717 | try zir_sema.analyzeBody(self, &inner_block.base, fn_zir.body); |
| 1709 | 1718 | ||
| 1710 | const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items); | 1719 | const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items); |
| 1711 | func.analysis = .{ .success = .{ .instructions = instructions } }; | 1720 | func.analysis = .{ .success = .{ .instructions = instructions } }; |
| 1712 | //std.debug.warn("set {} to success\n", .{decl.name}); | 1721 | log.debug(.module, "set {} to success\n", .{decl.name}); |
| 1713 | } | 1722 | } |
| 1714 | 1723 | ||
| 1715 | fn markOutdatedDecl(self: *Module, decl: *Decl) !void { | 1724 | fn markOutdatedDecl(self: *Module, decl: *Decl) !void { |
| 1716 | //std.debug.warn("mark {} outdated\n", .{decl.name}); | 1725 | log.debug(.module, "mark {} outdated\n", .{decl.name}); |
| 1717 | try self.work_queue.writeItem(.{ .analyze_decl = decl }); | 1726 | try self.work_queue.writeItem(.{ .analyze_decl = decl }); |
| 1718 | if (self.failed_decls.remove(decl)) |entry| { | 1727 | if (self.failed_decls.remove(decl)) |entry| { |
| 1719 | entry.value.destroy(self.gpa); | 1728 | entry.value.destroy(self.gpa); |
| ... | @@ -1758,131 +1767,18 @@ fn createNewDecl( | ... | @@ -1758,131 +1767,18 @@ fn createNewDecl( |
| 1758 | return new_decl; | 1767 | return new_decl; |
| 1759 | } | 1768 | } |
| 1760 | 1769 | ||
| 1761 | fn analyzeZirDecl(self: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { | ||
| 1762 | var decl_scope: Scope.DeclAnalysis = .{ | ||
| 1763 | .decl = decl, | ||
| 1764 | .arena = std.heap.ArenaAllocator.init(self.gpa), | ||
| 1765 | }; | ||
| 1766 | errdefer decl_scope.arena.deinit(); | ||
| 1767 | |||
| 1768 | decl.analysis = .in_progress; | ||
| 1769 | |||
| 1770 | const typed_value = try self.analyzeConstInst(&decl_scope.base, src_decl.inst); | ||
| 1771 | const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); | ||
| 1772 | |||
| 1773 | var prev_type_has_bits = false; | ||
| 1774 | var type_changed = true; | ||
| 1775 | |||
| 1776 | if (decl.typedValueManaged()) |tvm| { | ||
| 1777 | prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); | ||
| 1778 | type_changed = !tvm.typed_value.ty.eql(typed_value.ty); | ||
| 1779 | |||
| 1780 | tvm.deinit(self.gpa); | ||
| 1781 | } | ||
| 1782 | |||
| 1783 | arena_state.* = decl_scope.arena.state; | ||
| 1784 | decl.typed_value = .{ | ||
| 1785 | .most_recent = .{ | ||
| 1786 | .typed_value = typed_value, | ||
| 1787 | .arena = arena_state, | ||
| 1788 | }, | ||
| 1789 | }; | ||
| 1790 | decl.analysis = .complete; | ||
| 1791 | decl.generation = self.generation; | ||
| 1792 | if (typed_value.ty.hasCodeGenBits()) { | ||
| 1793 | // We don't fully codegen the decl until later, but we do need to reserve a global | ||
| 1794 | // offset table index for it. This allows us to codegen decls out of dependency order, | ||
| 1795 | // increasing how many computations can be done in parallel. | ||
| 1796 | try self.bin_file.allocateDeclIndexes(decl); | ||
| 1797 | try self.work_queue.writeItem(.{ .codegen_decl = decl }); | ||
| 1798 | } else if (prev_type_has_bits) { | ||
| 1799 | self.bin_file.freeDecl(decl); | ||
| 1800 | } | ||
| 1801 | |||
| 1802 | return type_changed; | ||
| 1803 | } | ||
| 1804 | |||
| 1805 | fn resolveZirDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { | ||
| 1806 | const zir_module = self.root_scope.cast(Scope.ZIRModule).?; | ||
| 1807 | const entry = zir_module.contents.module.findDecl(src_decl.name).?; | ||
| 1808 | return self.resolveZirDeclHavingIndex(scope, src_decl, entry.index); | ||
| 1809 | } | ||
| 1810 | |||
| 1811 | fn resolveZirDeclHavingIndex(self: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl { | ||
| 1812 | const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name); | ||
| 1813 | const decl = self.decl_table.get(name_hash).?; | ||
| 1814 | decl.src_index = src_index; | ||
| 1815 | try self.ensureDeclAnalyzed(decl); | ||
| 1816 | return decl; | ||
| 1817 | } | ||
| 1818 | |||
| 1819 | /// Declares a dependency on the decl. | ||
| 1820 | fn resolveCompleteZirDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { | ||
| 1821 | const decl = try self.resolveZirDecl(scope, src_decl); | ||
| 1822 | switch (decl.analysis) { | ||
| 1823 | .unreferenced => unreachable, | ||
| 1824 | .in_progress => unreachable, | ||
| 1825 | .outdated => unreachable, | ||
| 1826 | |||
| 1827 | .dependency_failure, | ||
| 1828 | .sema_failure, | ||
| 1829 | .sema_failure_retryable, | ||
| 1830 | .codegen_failure, | ||
| 1831 | .codegen_failure_retryable, | ||
| 1832 | => return error.AnalysisFail, | ||
| 1833 | |||
| 1834 | .complete => {}, | ||
| 1835 | } | ||
| 1836 | return decl; | ||
| 1837 | } | ||
| 1838 | |||
| 1839 | /// TODO Look into removing this function. The body is only needed for .zir files, not .zig files. | ||
| 1840 | fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { | ||
| 1841 | if (old_inst.analyzed_inst) |inst| return inst; | ||
| 1842 | |||
| 1843 | // If this assert trips, the instruction that was referenced did not get properly | ||
| 1844 | // analyzed before it was referenced. | ||
| 1845 | const zir_module = scope.namespace().cast(Scope.ZIRModule).?; | ||
| 1846 | const entry = if (old_inst.cast(zir.Inst.DeclVal)) |declval| blk: { | ||
| 1847 | const decl_name = declval.positionals.name; | ||
| 1848 | const entry = zir_module.contents.module.findDecl(decl_name) orelse | ||
| 1849 | return self.fail(scope, old_inst.src, "decl '{}' not found", .{decl_name}); | ||
| 1850 | break :blk entry; | ||
| 1851 | } else blk: { | ||
| 1852 | // If this assert trips, the instruction that was referenced did not get | ||
| 1853 | // properly analyzed by a previous instruction analysis before it was | ||
| 1854 | // referenced by the current one. | ||
| 1855 | break :blk zir_module.contents.module.findInstDecl(old_inst).?; | ||
| 1856 | }; | ||
| 1857 | const decl = try self.resolveCompleteZirDecl(scope, entry.decl); | ||
| 1858 | const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl); | ||
| 1859 | // Note: it would be tempting here to store the result into old_inst.analyzed_inst field, | ||
| 1860 | // but this would prevent the analyzeDeclRef from happening, which is needed to properly | ||
| 1861 | // detect Decl dependencies and dependency failures on updates. | ||
| 1862 | return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); | ||
| 1863 | } | ||
| 1864 | |||
| 1865 | /// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites. | 1770 | /// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites. |
| 1866 | fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { | 1771 | pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { |
| 1867 | return scope.cast(Scope.Block) orelse | 1772 | return scope.cast(Scope.Block) orelse |
| 1868 | return self.fail(scope, src, "instruction illegal outside function body", .{}); | 1773 | return self.fail(scope, src, "instruction illegal outside function body", .{}); |
| 1869 | } | 1774 | } |
| 1870 | 1775 | ||
| 1871 | fn resolveInstConst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { | 1776 | pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value { |
| 1872 | const new_inst = try self.resolveInst(scope, old_inst); | ||
| 1873 | const val = try self.resolveConstValue(scope, new_inst); | ||
| 1874 | return TypedValue{ | ||
| 1875 | .ty = new_inst.ty, | ||
| 1876 | .val = val, | ||
| 1877 | }; | ||
| 1878 | } | ||
| 1879 | |||
| 1880 | fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value { | ||
| 1881 | return (try self.resolveDefinedValue(scope, base)) orelse | 1777 | return (try self.resolveDefinedValue(scope, base)) orelse |
| 1882 | return self.fail(scope, base.src, "unable to resolve comptime value", .{}); | 1778 | return self.fail(scope, base.src, "unable to resolve comptime value", .{}); |
| 1883 | } | 1779 | } |
| 1884 | 1780 | ||
| 1885 | fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value { | 1781 | pub fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value { |
| 1886 | if (base.value()) |val| { | 1782 | if (base.value()) |val| { |
| 1887 | if (val.isUndef()) { | 1783 | if (val.isUndef()) { |
| 1888 | return self.fail(scope, base.src, "use of undefined value here causes undefined behavior", .{}); | 1784 | 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 { | ... | @@ -1892,23 +1788,7 @@ fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value { |
| 1892 | return null; | 1788 | return null; |
| 1893 | } | 1789 | } |
| 1894 | 1790 | ||
| 1895 | fn resolveConstString(self: *Module, scope: *Scope, old_inst: *zir.Inst) ![]u8 { | 1791 | pub fn analyzeExport(self: *Module, scope: *Scope, src: usize, borrowed_symbol_name: []const u8, exported_decl: *Decl) !void { |
| 1896 | const new_inst = try self.resolveInst(scope, old_inst); | ||
| 1897 | const wanted_type = Type.initTag(.const_slice_u8); | ||
| 1898 | const coerced_inst = try self.coerce(scope, wanted_type, new_inst); | ||
| 1899 | const val = try self.resolveConstValue(scope, coerced_inst); | ||
| 1900 | return val.toAllocatedBytes(scope.arena()); | ||
| 1901 | } | ||
| 1902 | |||
| 1903 | fn resolveType(self: *Module, scope: *Scope, old_inst: *zir.Inst) !Type { | ||
| 1904 | const new_inst = try self.resolveInst(scope, old_inst); | ||
| 1905 | const wanted_type = Type.initTag(.@"type"); | ||
| 1906 | const coerced_inst = try self.coerce(scope, wanted_type, new_inst); | ||
| 1907 | const val = try self.resolveConstValue(scope, coerced_inst); | ||
| 1908 | return val.toType(); | ||
| 1909 | } | ||
| 1910 | |||
| 1911 | fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const u8, exported_decl: *Decl) !void { | ||
| 1912 | try self.ensureDeclAnalyzed(exported_decl); | 1792 | try self.ensureDeclAnalyzed(exported_decl); |
| 1913 | const typed_value = exported_decl.typed_value.most_recent.typed_value; | 1793 | const typed_value = exported_decl.typed_value.most_recent.typed_value; |
| 1914 | switch (typed_value.ty.zigTypeTag()) { | 1794 | switch (typed_value.ty.zigTypeTag()) { |
| ... | @@ -1922,6 +1802,9 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const | ... | @@ -1922,6 +1802,9 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const |
| 1922 | const new_export = try self.gpa.create(Export); | 1802 | const new_export = try self.gpa.create(Export); |
| 1923 | errdefer self.gpa.destroy(new_export); | 1803 | errdefer self.gpa.destroy(new_export); |
| 1924 | 1804 | ||
| 1805 | const symbol_name = try self.gpa.dupe(u8, borrowed_symbol_name); | ||
| 1806 | errdefer self.gpa.free(symbol_name); | ||
| 1807 | |||
| 1925 | const owner_decl = scope.decl().?; | 1808 | const owner_decl = scope.decl().?; |
| 1926 | 1809 | ||
| 1927 | new_export.* = .{ | 1810 | new_export.* = .{ |
| ... | @@ -1934,7 +1817,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const | ... | @@ -1934,7 +1817,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const |
| 1934 | }; | 1817 | }; |
| 1935 | 1818 | ||
| 1936 | // Add to export_owners table. | 1819 | // Add to export_owners table. |
| 1937 | const eo_gop = self.export_owners.getOrPut(self.gpa, owner_decl) catch unreachable; | 1820 | const eo_gop = self.export_owners.getOrPutAssumeCapacity(owner_decl); |
| 1938 | if (!eo_gop.found_existing) { | 1821 | if (!eo_gop.found_existing) { |
| 1939 | eo_gop.entry.value = &[0]*Export{}; | 1822 | eo_gop.entry.value = &[0]*Export{}; |
| 1940 | } | 1823 | } |
| ... | @@ -1943,7 +1826,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const | ... | @@ -1943,7 +1826,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const |
| 1943 | errdefer eo_gop.entry.value = self.gpa.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); | 1826 | errdefer eo_gop.entry.value = self.gpa.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); |
| 1944 | 1827 | ||
| 1945 | // Add to exported_decl table. | 1828 | // Add to exported_decl table. |
| 1946 | const de_gop = self.decl_exports.getOrPut(self.gpa, exported_decl) catch unreachable; | 1829 | const de_gop = self.decl_exports.getOrPutAssumeCapacity(exported_decl); |
| 1947 | if (!de_gop.found_existing) { | 1830 | if (!de_gop.found_existing) { |
| 1948 | de_gop.entry.value = &[0]*Export{}; | 1831 | de_gop.entry.value = &[0]*Export{}; |
| 1949 | } | 1832 | } |
| ... | @@ -1980,7 +1863,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const | ... | @@ -1980,7 +1863,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const |
| 1980 | }; | 1863 | }; |
| 1981 | } | 1864 | } |
| 1982 | 1865 | ||
| 1983 | fn addNoOp( | 1866 | pub fn addNoOp( |
| 1984 | self: *Module, | 1867 | self: *Module, |
| 1985 | block: *Scope.Block, | 1868 | block: *Scope.Block, |
| 1986 | src: usize, | 1869 | src: usize, |
| ... | @@ -1999,7 +1882,7 @@ fn addNoOp( | ... | @@ -1999,7 +1882,7 @@ fn addNoOp( |
| 1999 | return &inst.base; | 1882 | return &inst.base; |
| 2000 | } | 1883 | } |
| 2001 | 1884 | ||
| 2002 | fn addUnOp( | 1885 | pub fn addUnOp( |
| 2003 | self: *Module, | 1886 | self: *Module, |
| 2004 | block: *Scope.Block, | 1887 | block: *Scope.Block, |
| 2005 | src: usize, | 1888 | src: usize, |
| ... | @@ -2020,7 +1903,7 @@ fn addUnOp( | ... | @@ -2020,7 +1903,7 @@ fn addUnOp( |
| 2020 | return &inst.base; | 1903 | return &inst.base; |
| 2021 | } | 1904 | } |
| 2022 | 1905 | ||
| 2023 | fn addBinOp( | 1906 | pub fn addBinOp( |
| 2024 | self: *Module, | 1907 | self: *Module, |
| 2025 | block: *Scope.Block, | 1908 | block: *Scope.Block, |
| 2026 | src: usize, | 1909 | src: usize, |
| ... | @@ -2043,7 +1926,7 @@ fn addBinOp( | ... | @@ -2043,7 +1926,7 @@ fn addBinOp( |
| 2043 | return &inst.base; | 1926 | return &inst.base; |
| 2044 | } | 1927 | } |
| 2045 | 1928 | ||
| 2046 | fn addBr( | 1929 | pub fn addBr( |
| 2047 | self: *Module, | 1930 | self: *Module, |
| 2048 | scope_block: *Scope.Block, | 1931 | scope_block: *Scope.Block, |
| 2049 | src: usize, | 1932 | src: usize, |
| ... | @@ -2064,7 +1947,7 @@ fn addBr( | ... | @@ -2064,7 +1947,7 @@ fn addBr( |
| 2064 | return &inst.base; | 1947 | return &inst.base; |
| 2065 | } | 1948 | } |
| 2066 | 1949 | ||
| 2067 | fn addCondBr( | 1950 | pub fn addCondBr( |
| 2068 | self: *Module, | 1951 | self: *Module, |
| 2069 | block: *Scope.Block, | 1952 | block: *Scope.Block, |
| 2070 | src: usize, | 1953 | src: usize, |
| ... | @@ -2087,7 +1970,7 @@ fn addCondBr( | ... | @@ -2087,7 +1970,7 @@ fn addCondBr( |
| 2087 | return &inst.base; | 1970 | return &inst.base; |
| 2088 | } | 1971 | } |
| 2089 | 1972 | ||
| 2090 | fn addCall( | 1973 | pub fn addCall( |
| 2091 | self: *Module, | 1974 | self: *Module, |
| 2092 | block: *Scope.Block, | 1975 | block: *Scope.Block, |
| 2093 | src: usize, | 1976 | src: usize, |
| ... | @@ -2109,138 +1992,7 @@ fn addCall( | ... | @@ -2109,138 +1992,7 @@ fn addCall( |
| 2109 | return &inst.base; | 1992 | return &inst.base; |
| 2110 | } | 1993 | } |
| 2111 | 1994 | ||
| 2112 | pub fn addZIRInstSpecial( | 1995 | pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { |
| 2113 | self: *Module, | ||
| 2114 | scope: *Scope, | ||
| 2115 | src: usize, | ||
| 2116 | comptime T: type, | ||
| 2117 | positionals: std.meta.fieldInfo(T, "positionals").field_type, | ||
| 2118 | kw_args: std.meta.fieldInfo(T, "kw_args").field_type, | ||
| 2119 | ) !*T { | ||
| 2120 | const gen_zir = scope.getGenZIR(); | ||
| 2121 | try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); | ||
| 2122 | const inst = try gen_zir.arena.create(T); | ||
| 2123 | inst.* = .{ | ||
| 2124 | .base = .{ | ||
| 2125 | .tag = T.base_tag, | ||
| 2126 | .src = src, | ||
| 2127 | }, | ||
| 2128 | .positionals = positionals, | ||
| 2129 | .kw_args = kw_args, | ||
| 2130 | }; | ||
| 2131 | gen_zir.instructions.appendAssumeCapacity(&inst.base); | ||
| 2132 | return inst; | ||
| 2133 | } | ||
| 2134 | |||
| 2135 | pub fn addZIRNoOpT(self: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst.NoOp { | ||
| 2136 | const gen_zir = scope.getGenZIR(); | ||
| 2137 | try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); | ||
| 2138 | const inst = try gen_zir.arena.create(zir.Inst.NoOp); | ||
| 2139 | inst.* = .{ | ||
| 2140 | .base = .{ | ||
| 2141 | .tag = tag, | ||
| 2142 | .src = src, | ||
| 2143 | }, | ||
| 2144 | .positionals = .{}, | ||
| 2145 | .kw_args = .{}, | ||
| 2146 | }; | ||
| 2147 | gen_zir.instructions.appendAssumeCapacity(&inst.base); | ||
| 2148 | return inst; | ||
| 2149 | } | ||
| 2150 | |||
| 2151 | pub fn addZIRNoOp(self: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst { | ||
| 2152 | const inst = try self.addZIRNoOpT(scope, src, tag); | ||
| 2153 | return &inst.base; | ||
| 2154 | } | ||
| 2155 | |||
| 2156 | pub fn addZIRUnOp( | ||
| 2157 | self: *Module, | ||
| 2158 | scope: *Scope, | ||
| 2159 | src: usize, | ||
| 2160 | tag: zir.Inst.Tag, | ||
| 2161 | operand: *zir.Inst, | ||
| 2162 | ) !*zir.Inst { | ||
| 2163 | const gen_zir = scope.getGenZIR(); | ||
| 2164 | try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); | ||
| 2165 | const inst = try gen_zir.arena.create(zir.Inst.UnOp); | ||
| 2166 | inst.* = .{ | ||
| 2167 | .base = .{ | ||
| 2168 | .tag = tag, | ||
| 2169 | .src = src, | ||
| 2170 | }, | ||
| 2171 | .positionals = .{ | ||
| 2172 | .operand = operand, | ||
| 2173 | }, | ||
| 2174 | .kw_args = .{}, | ||
| 2175 | }; | ||
| 2176 | gen_zir.instructions.appendAssumeCapacity(&inst.base); | ||
| 2177 | return &inst.base; | ||
| 2178 | } | ||
| 2179 | |||
| 2180 | pub fn addZIRBinOp( | ||
| 2181 | self: *Module, | ||
| 2182 | scope: *Scope, | ||
| 2183 | src: usize, | ||
| 2184 | tag: zir.Inst.Tag, | ||
| 2185 | lhs: *zir.Inst, | ||
| 2186 | rhs: *zir.Inst, | ||
| 2187 | ) !*zir.Inst { | ||
| 2188 | const gen_zir = scope.getGenZIR(); | ||
| 2189 | try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); | ||
| 2190 | const inst = try gen_zir.arena.create(zir.Inst.BinOp); | ||
| 2191 | inst.* = .{ | ||
| 2192 | .base = .{ | ||
| 2193 | .tag = tag, | ||
| 2194 | .src = src, | ||
| 2195 | }, | ||
| 2196 | .positionals = .{ | ||
| 2197 | .lhs = lhs, | ||
| 2198 | .rhs = rhs, | ||
| 2199 | }, | ||
| 2200 | .kw_args = .{}, | ||
| 2201 | }; | ||
| 2202 | gen_zir.instructions.appendAssumeCapacity(&inst.base); | ||
| 2203 | return &inst.base; | ||
| 2204 | } | ||
| 2205 | |||
| 2206 | pub fn addZIRInst( | ||
| 2207 | self: *Module, | ||
| 2208 | scope: *Scope, | ||
| 2209 | src: usize, | ||
| 2210 | comptime T: type, | ||
| 2211 | positionals: std.meta.fieldInfo(T, "positionals").field_type, | ||
| 2212 | kw_args: std.meta.fieldInfo(T, "kw_args").field_type, | ||
| 2213 | ) !*zir.Inst { | ||
| 2214 | const inst_special = try self.addZIRInstSpecial(scope, src, T, positionals, kw_args); | ||
| 2215 | return &inst_special.base; | ||
| 2216 | } | ||
| 2217 | |||
| 2218 | /// TODO The existence of this function is a workaround for a bug in stage1. | ||
| 2219 | pub fn addZIRInstConst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst { | ||
| 2220 | const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type; | ||
| 2221 | return self.addZIRInst(scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{}); | ||
| 2222 | } | ||
| 2223 | |||
| 2224 | /// TODO The existence of this function is a workaround for a bug in stage1. | ||
| 2225 | pub fn addZIRInstBlock(self: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block { | ||
| 2226 | const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type; | ||
| 2227 | return self.addZIRInstSpecial(scope, src, zir.Inst.Block, P{ .body = body }, .{}); | ||
| 2228 | } | ||
| 2229 | |||
| 2230 | fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime T: type) !*T { | ||
| 2231 | const inst = try block.arena.create(T); | ||
| 2232 | inst.* = .{ | ||
| 2233 | .base = .{ | ||
| 2234 | .tag = T.base_tag, | ||
| 2235 | .ty = ty, | ||
| 2236 | .src = src, | ||
| 2237 | }, | ||
| 2238 | }; | ||
| 2239 | try block.instructions.append(self.gpa, &inst.base); | ||
| 2240 | return inst; | ||
| 2241 | } | ||
| 2242 | |||
| 2243 | fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { | ||
| 2244 | const const_inst = try scope.arena().create(Inst.Constant); | 1996 | const const_inst = try scope.arena().create(Inst.Constant); |
| 2245 | const_inst.* = .{ | 1997 | const_inst.* = .{ |
| 2246 | .base = .{ | 1998 | .base = .{ |
| ... | @@ -2253,42 +2005,42 @@ fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) | ... | @@ -2253,42 +2005,42 @@ fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) |
| 2253 | return &const_inst.base; | 2005 | return &const_inst.base; |
| 2254 | } | 2006 | } |
| 2255 | 2007 | ||
| 2256 | fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { | 2008 | pub fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { |
| 2257 | return self.constInst(scope, src, .{ | 2009 | return self.constInst(scope, src, .{ |
| 2258 | .ty = Type.initTag(.type), | 2010 | .ty = Type.initTag(.type), |
| 2259 | .val = try ty.toValue(scope.arena()), | 2011 | .val = try ty.toValue(scope.arena()), |
| 2260 | }); | 2012 | }); |
| 2261 | } | 2013 | } |
| 2262 | 2014 | ||
| 2263 | fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst { | 2015 | pub fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst { |
| 2264 | return self.constInst(scope, src, .{ | 2016 | return self.constInst(scope, src, .{ |
| 2265 | .ty = Type.initTag(.void), | 2017 | .ty = Type.initTag(.void), |
| 2266 | .val = Value.initTag(.the_one_possible_value), | 2018 | .val = Value.initTag(.void_value), |
| 2267 | }); | 2019 | }); |
| 2268 | } | 2020 | } |
| 2269 | 2021 | ||
| 2270 | fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst { | 2022 | pub fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst { |
| 2271 | return self.constInst(scope, src, .{ | 2023 | return self.constInst(scope, src, .{ |
| 2272 | .ty = Type.initTag(.noreturn), | 2024 | .ty = Type.initTag(.noreturn), |
| 2273 | .val = Value.initTag(.the_one_possible_value), | 2025 | .val = Value.initTag(.unreachable_value), |
| 2274 | }); | 2026 | }); |
| 2275 | } | 2027 | } |
| 2276 | 2028 | ||
| 2277 | fn constUndef(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { | 2029 | pub fn constUndef(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { |
| 2278 | return self.constInst(scope, src, .{ | 2030 | return self.constInst(scope, src, .{ |
| 2279 | .ty = ty, | 2031 | .ty = ty, |
| 2280 | .val = Value.initTag(.undef), | 2032 | .val = Value.initTag(.undef), |
| 2281 | }); | 2033 | }); |
| 2282 | } | 2034 | } |
| 2283 | 2035 | ||
| 2284 | fn constBool(self: *Module, scope: *Scope, src: usize, v: bool) !*Inst { | 2036 | pub fn constBool(self: *Module, scope: *Scope, src: usize, v: bool) !*Inst { |
| 2285 | return self.constInst(scope, src, .{ | 2037 | return self.constInst(scope, src, .{ |
| 2286 | .ty = Type.initTag(.bool), | 2038 | .ty = Type.initTag(.bool), |
| 2287 | .val = ([2]Value{ Value.initTag(.bool_false), Value.initTag(.bool_true) })[@boolToInt(v)], | 2039 | .val = ([2]Value{ Value.initTag(.bool_false), Value.initTag(.bool_true) })[@boolToInt(v)], |
| 2288 | }); | 2040 | }); |
| 2289 | } | 2041 | } |
| 2290 | 2042 | ||
| 2291 | fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64) !*Inst { | 2043 | pub fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64) !*Inst { |
| 2292 | const int_payload = try scope.arena().create(Value.Payload.Int_u64); | 2044 | const int_payload = try scope.arena().create(Value.Payload.Int_u64); |
| 2293 | int_payload.* = .{ .int = int }; | 2045 | int_payload.* = .{ .int = int }; |
| 2294 | 2046 | ||
| ... | @@ -2298,7 +2050,7 @@ fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64 | ... | @@ -2298,7 +2050,7 @@ fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64 |
| 2298 | }); | 2050 | }); |
| 2299 | } | 2051 | } |
| 2300 | 2052 | ||
| 2301 | fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) !*Inst { | 2053 | pub fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) !*Inst { |
| 2302 | const int_payload = try scope.arena().create(Value.Payload.Int_i64); | 2054 | const int_payload = try scope.arena().create(Value.Payload.Int_i64); |
| 2303 | int_payload.* = .{ .int = int }; | 2055 | int_payload.* = .{ .int = int }; |
| 2304 | 2056 | ||
| ... | @@ -2308,7 +2060,7 @@ fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) | ... | @@ -2308,7 +2060,7 @@ fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) |
| 2308 | }); | 2060 | }); |
| 2309 | } | 2061 | } |
| 2310 | 2062 | ||
| 2311 | fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst { | 2063 | pub fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst { |
| 2312 | const val_payload = if (big_int.positive) blk: { | 2064 | const val_payload = if (big_int.positive) blk: { |
| 2313 | if (big_int.to(u64)) |x| { | 2065 | if (big_int.to(u64)) |x| { |
| 2314 | return self.constIntUnsigned(scope, src, ty, x); | 2066 | return self.constIntUnsigned(scope, src, ty, x); |
| ... | @@ -2337,191 +2089,7 @@ fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigI | ... | @@ -2337,191 +2089,7 @@ fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigI |
| 2337 | }); | 2089 | }); |
| 2338 | } | 2090 | } |
| 2339 | 2091 | ||
| 2340 | fn analyzeConstInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { | 2092 | pub fn createAnonymousDecl( |
| 2341 | const new_inst = try self.analyzeInst(scope, old_inst); | ||
| 2342 | return TypedValue{ | ||
| 2343 | .ty = new_inst.ty, | ||
| 2344 | .val = try self.resolveConstValue(scope, new_inst), | ||
| 2345 | }; | ||
| 2346 | } | ||
| 2347 | |||
| 2348 | fn analyzeInstConst(self: *Module, scope: *Scope, const_inst: *zir.Inst.Const) InnerError!*Inst { | ||
| 2349 | // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions | ||
| 2350 | // after analysis. | ||
| 2351 | const typed_value_copy = try const_inst.positionals.typed_value.copy(scope.arena()); | ||
| 2352 | return self.constInst(scope, const_inst.base.src, typed_value_copy); | ||
| 2353 | } | ||
| 2354 | |||
| 2355 | fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { | ||
| 2356 | switch (old_inst.tag) { | ||
| 2357 | .alloc => return self.analyzeInstAlloc(scope, old_inst.castTag(.alloc).?), | ||
| 2358 | .alloc_inferred => return self.analyzeInstAllocInferred(scope, old_inst.castTag(.alloc_inferred).?), | ||
| 2359 | .arg => return self.analyzeInstArg(scope, old_inst.castTag(.arg).?), | ||
| 2360 | .bitcast_result_ptr => return self.analyzeInstBitCastResultPtr(scope, old_inst.castTag(.bitcast_result_ptr).?), | ||
| 2361 | .block => return self.analyzeInstBlock(scope, old_inst.castTag(.block).?), | ||
| 2362 | .@"break" => return self.analyzeInstBreak(scope, old_inst.castTag(.@"break").?), | ||
| 2363 | .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.castTag(.breakpoint).?), | ||
| 2364 | .breakvoid => return self.analyzeInstBreakVoid(scope, old_inst.castTag(.breakvoid).?), | ||
| 2365 | .call => return self.analyzeInstCall(scope, old_inst.castTag(.call).?), | ||
| 2366 | .coerce_result_block_ptr => return self.analyzeInstCoerceResultBlockPtr(scope, old_inst.castTag(.coerce_result_block_ptr).?), | ||
| 2367 | .coerce_result_ptr => return self.analyzeInstCoerceResultPtr(scope, old_inst.castTag(.coerce_result_ptr).?), | ||
| 2368 | .coerce_to_ptr_elem => return self.analyzeInstCoerceToPtrElem(scope, old_inst.castTag(.coerce_to_ptr_elem).?), | ||
| 2369 | .compileerror => return self.analyzeInstCompileError(scope, old_inst.castTag(.compileerror).?), | ||
| 2370 | .@"const" => return self.analyzeInstConst(scope, old_inst.castTag(.@"const").?), | ||
| 2371 | .declref => return self.analyzeInstDeclRef(scope, old_inst.castTag(.declref).?), | ||
| 2372 | .declref_str => return self.analyzeInstDeclRefStr(scope, old_inst.castTag(.declref_str).?), | ||
| 2373 | .declval => return self.analyzeInstDeclVal(scope, old_inst.castTag(.declval).?), | ||
| 2374 | .declval_in_module => return self.analyzeInstDeclValInModule(scope, old_inst.castTag(.declval_in_module).?), | ||
| 2375 | .ensure_result_used => return self.analyzeInstEnsureResultUsed(scope, old_inst.castTag(.ensure_result_used).?), | ||
| 2376 | .ensure_result_non_error => return self.analyzeInstEnsureResultNonError(scope, old_inst.castTag(.ensure_result_non_error).?), | ||
| 2377 | .ret_ptr => return self.analyzeInstRetPtr(scope, old_inst.castTag(.ret_ptr).?), | ||
| 2378 | .ret_type => return self.analyzeInstRetType(scope, old_inst.castTag(.ret_type).?), | ||
| 2379 | .store => return self.analyzeInstStore(scope, old_inst.castTag(.store).?), | ||
| 2380 | .str => return self.analyzeInstStr(scope, old_inst.castTag(.str).?), | ||
| 2381 | .int => { | ||
| 2382 | const big_int = old_inst.castTag(.int).?.positionals.int; | ||
| 2383 | return self.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int); | ||
| 2384 | }, | ||
| 2385 | .inttype => return self.analyzeInstIntType(scope, old_inst.castTag(.inttype).?), | ||
| 2386 | .param_type => return self.analyzeInstParamType(scope, old_inst.castTag(.param_type).?), | ||
| 2387 | .ptrtoint => return self.analyzeInstPtrToInt(scope, old_inst.castTag(.ptrtoint).?), | ||
| 2388 | .fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.castTag(.fieldptr).?), | ||
| 2389 | .deref => return self.analyzeInstDeref(scope, old_inst.castTag(.deref).?), | ||
| 2390 | .as => return self.analyzeInstAs(scope, old_inst.castTag(.as).?), | ||
| 2391 | .@"asm" => return self.analyzeInstAsm(scope, old_inst.castTag(.@"asm").?), | ||
| 2392 | .@"unreachable" => return self.analyzeInstUnreachable(scope, old_inst.castTag(.@"unreachable").?), | ||
| 2393 | .@"return" => return self.analyzeInstRet(scope, old_inst.castTag(.@"return").?), | ||
| 2394 | .returnvoid => return self.analyzeInstRetVoid(scope, old_inst.castTag(.returnvoid).?), | ||
| 2395 | .@"fn" => return self.analyzeInstFn(scope, old_inst.castTag(.@"fn").?), | ||
| 2396 | .@"export" => return self.analyzeInstExport(scope, old_inst.castTag(.@"export").?), | ||
| 2397 | .primitive => return self.analyzeInstPrimitive(scope, old_inst.castTag(.primitive).?), | ||
| 2398 | .fntype => return self.analyzeInstFnType(scope, old_inst.castTag(.fntype).?), | ||
| 2399 | .intcast => return self.analyzeInstIntCast(scope, old_inst.castTag(.intcast).?), | ||
| 2400 | .bitcast => return self.analyzeInstBitCast(scope, old_inst.castTag(.bitcast).?), | ||
| 2401 | .floatcast => return self.analyzeInstFloatCast(scope, old_inst.castTag(.floatcast).?), | ||
| 2402 | .elemptr => return self.analyzeInstElemPtr(scope, old_inst.castTag(.elemptr).?), | ||
| 2403 | .add, .sub => return self.analyzeInstArithmetic(scope, old_inst.cast(zir.Inst.BinOp).?), | ||
| 2404 | .cmp_lt => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_lt).?, .lt), | ||
| 2405 | .cmp_lte => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_lte).?, .lte), | ||
| 2406 | .cmp_eq => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_eq).?, .eq), | ||
| 2407 | .cmp_gte => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_gte).?, .gte), | ||
| 2408 | .cmp_gt => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_gt).?, .gt), | ||
| 2409 | .cmp_neq => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_neq).?, .neq), | ||
| 2410 | .condbr => return self.analyzeInstCondBr(scope, old_inst.castTag(.condbr).?), | ||
| 2411 | .isnull => return self.analyzeInstIsNonNull(scope, old_inst.castTag(.isnull).?, true), | ||
| 2412 | .isnonnull => return self.analyzeInstIsNonNull(scope, old_inst.castTag(.isnonnull).?, false), | ||
| 2413 | .boolnot => return self.analyzeInstBoolNot(scope, old_inst.castTag(.boolnot).?), | ||
| 2414 | } | ||
| 2415 | } | ||
| 2416 | |||
| 2417 | fn analyzeInstCoerceResultBlockPtr( | ||
| 2418 | self: *Module, | ||
| 2419 | scope: *Scope, | ||
| 2420 | inst: *zir.Inst.CoerceResultBlockPtr, | ||
| 2421 | ) InnerError!*Inst { | ||
| 2422 | return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultBlockPtr", .{}); | ||
| 2423 | } | ||
| 2424 | |||
| 2425 | fn analyzeInstBitCastResultPtr(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 2426 | return self.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastResultPtr", .{}); | ||
| 2427 | } | ||
| 2428 | |||
| 2429 | fn analyzeInstCoerceResultPtr(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 2430 | return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultPtr", .{}); | ||
| 2431 | } | ||
| 2432 | |||
| 2433 | fn analyzeInstCoerceToPtrElem(self: *Module, scope: *Scope, inst: *zir.Inst.CoerceToPtrElem) InnerError!*Inst { | ||
| 2434 | return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceToPtrElem", .{}); | ||
| 2435 | } | ||
| 2436 | |||
| 2437 | fn analyzeInstRetPtr(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 2438 | return self.fail(scope, inst.base.src, "TODO implement analyzeInstRetPtr", .{}); | ||
| 2439 | } | ||
| 2440 | |||
| 2441 | fn analyzeInstRetType(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 2442 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 2443 | const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; | ||
| 2444 | const ret_type = fn_ty.fnReturnType(); | ||
| 2445 | return self.constType(scope, inst.base.src, ret_type); | ||
| 2446 | } | ||
| 2447 | |||
| 2448 | fn analyzeInstEnsureResultUsed(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 2449 | const operand = try self.resolveInst(scope, inst.positionals.operand); | ||
| 2450 | switch (operand.ty.zigTypeTag()) { | ||
| 2451 | .Void, .NoReturn => return self.constVoid(scope, operand.src), | ||
| 2452 | else => return self.fail(scope, operand.src, "expression value is ignored", .{}), | ||
| 2453 | } | ||
| 2454 | } | ||
| 2455 | |||
| 2456 | fn analyzeInstEnsureResultNonError(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 2457 | const operand = try self.resolveInst(scope, inst.positionals.operand); | ||
| 2458 | switch (operand.ty.zigTypeTag()) { | ||
| 2459 | .ErrorSet, .ErrorUnion => return self.fail(scope, operand.src, "error is discarded", .{}), | ||
| 2460 | else => return self.constVoid(scope, operand.src), | ||
| 2461 | } | ||
| 2462 | } | ||
| 2463 | |||
| 2464 | fn analyzeInstAlloc(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 2465 | return self.fail(scope, inst.base.src, "TODO implement analyzeInstAlloc", .{}); | ||
| 2466 | } | ||
| 2467 | |||
| 2468 | fn analyzeInstAllocInferred(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 2469 | return self.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{}); | ||
| 2470 | } | ||
| 2471 | |||
| 2472 | fn analyzeInstStore(self: *Module, scope: *Scope, inst: *zir.Inst.Store) InnerError!*Inst { | ||
| 2473 | return self.fail(scope, inst.base.src, "TODO implement analyzeInstStore", .{}); | ||
| 2474 | } | ||
| 2475 | |||
| 2476 | fn analyzeInstParamType(self: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerError!*Inst { | ||
| 2477 | const fn_inst = try self.resolveInst(scope, inst.positionals.func); | ||
| 2478 | const arg_index = inst.positionals.arg_index; | ||
| 2479 | |||
| 2480 | const fn_ty: Type = switch (fn_inst.ty.zigTypeTag()) { | ||
| 2481 | .Fn => fn_inst.ty, | ||
| 2482 | .BoundFn => { | ||
| 2483 | return self.fail(scope, fn_inst.src, "TODO implement analyzeInstParamType for method call syntax", .{}); | ||
| 2484 | }, | ||
| 2485 | else => { | ||
| 2486 | return self.fail(scope, fn_inst.src, "expected function, found '{}'", .{fn_inst.ty}); | ||
| 2487 | }, | ||
| 2488 | }; | ||
| 2489 | |||
| 2490 | // TODO support C-style var args | ||
| 2491 | const param_count = fn_ty.fnParamLen(); | ||
| 2492 | if (arg_index >= param_count) { | ||
| 2493 | return self.fail(scope, inst.base.src, "arg index {} out of bounds; '{}' has {} arguments", .{ | ||
| 2494 | arg_index, | ||
| 2495 | fn_ty, | ||
| 2496 | param_count, | ||
| 2497 | }); | ||
| 2498 | } | ||
| 2499 | |||
| 2500 | // TODO support generic functions | ||
| 2501 | const param_type = fn_ty.fnParamType(arg_index); | ||
| 2502 | return self.constType(scope, inst.base.src, param_type); | ||
| 2503 | } | ||
| 2504 | |||
| 2505 | fn analyzeInstStr(self: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst { | ||
| 2506 | // The bytes references memory inside the ZIR module, which can get deallocated | ||
| 2507 | // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. | ||
| 2508 | var new_decl_arena = std.heap.ArenaAllocator.init(self.gpa); | ||
| 2509 | const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes); | ||
| 2510 | |||
| 2511 | const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); | ||
| 2512 | ty_payload.* = .{ .len = arena_bytes.len }; | ||
| 2513 | |||
| 2514 | const bytes_payload = try scope.arena().create(Value.Payload.Bytes); | ||
| 2515 | bytes_payload.* = .{ .data = arena_bytes }; | ||
| 2516 | |||
| 2517 | const new_decl = try self.createAnonymousDecl(scope, &new_decl_arena, .{ | ||
| 2518 | .ty = Type.initPayload(&ty_payload.base), | ||
| 2519 | .val = Value.initPayload(&bytes_payload.base), | ||
| 2520 | }); | ||
| 2521 | return self.analyzeDeclRef(scope, str_inst.base.src, new_decl); | ||
| 2522 | } | ||
| 2523 | |||
| 2524 | fn createAnonymousDecl( | ||
| 2525 | self: *Module, | 2093 | self: *Module, |
| 2526 | scope: *Scope, | 2094 | scope: *Scope, |
| 2527 | decl_arena: *std.heap.ArenaAllocator, | 2095 | decl_arena: *std.heap.ArenaAllocator, |
| ... | @@ -2567,151 +2135,7 @@ pub fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*De | ... | @@ -2567,151 +2135,7 @@ pub fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*De |
| 2567 | return self.decl_table.get(name_hash); | 2135 | return self.decl_table.get(name_hash); |
| 2568 | } | 2136 | } |
| 2569 | 2137 | ||
| 2570 | fn analyzeInstExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst { | 2138 | pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst { |
| 2571 | const symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name); | ||
| 2572 | const exported_decl = self.lookupDeclName(scope, export_inst.positionals.decl_name) orelse | ||
| 2573 | return self.fail(scope, export_inst.base.src, "decl '{}' not found", .{export_inst.positionals.decl_name}); | ||
| 2574 | try self.analyzeExport(scope, export_inst.base.src, symbol_name, exported_decl); | ||
| 2575 | return self.constVoid(scope, export_inst.base.src); | ||
| 2576 | } | ||
| 2577 | |||
| 2578 | fn analyzeInstCompileError(self: *Module, scope: *Scope, inst: *zir.Inst.CompileError) InnerError!*Inst { | ||
| 2579 | return self.fail(scope, inst.base.src, "{}", .{inst.positionals.msg}); | ||
| 2580 | } | ||
| 2581 | |||
| 2582 | fn analyzeInstArg(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 2583 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 2584 | const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; | ||
| 2585 | const param_index = b.instructions.items.len; | ||
| 2586 | const param_count = fn_ty.fnParamLen(); | ||
| 2587 | if (param_index >= param_count) { | ||
| 2588 | return self.fail(scope, inst.base.src, "parameter index {} outside list of length {}", .{ | ||
| 2589 | param_index, | ||
| 2590 | param_count, | ||
| 2591 | }); | ||
| 2592 | } | ||
| 2593 | const param_type = fn_ty.fnParamType(param_index); | ||
| 2594 | return self.addNoOp(b, inst.base.src, param_type, .arg); | ||
| 2595 | } | ||
| 2596 | |||
| 2597 | fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst { | ||
| 2598 | const parent_block = scope.cast(Scope.Block).?; | ||
| 2599 | |||
| 2600 | // Reserve space for a Block instruction so that generated Break instructions can | ||
| 2601 | // point to it, even if it doesn't end up getting used because the code ends up being | ||
| 2602 | // comptime evaluated. | ||
| 2603 | const block_inst = try parent_block.arena.create(Inst.Block); | ||
| 2604 | block_inst.* = .{ | ||
| 2605 | .base = .{ | ||
| 2606 | .tag = Inst.Block.base_tag, | ||
| 2607 | .ty = undefined, // Set after analysis. | ||
| 2608 | .src = inst.base.src, | ||
| 2609 | }, | ||
| 2610 | .body = undefined, | ||
| 2611 | }; | ||
| 2612 | |||
| 2613 | var child_block: Scope.Block = .{ | ||
| 2614 | .parent = parent_block, | ||
| 2615 | .func = parent_block.func, | ||
| 2616 | .decl = parent_block.decl, | ||
| 2617 | .instructions = .{}, | ||
| 2618 | .arena = parent_block.arena, | ||
| 2619 | // TODO @as here is working around a miscompilation compiler bug :( | ||
| 2620 | .label = @as(?Scope.Block.Label, Scope.Block.Label{ | ||
| 2621 | .zir_block = inst, | ||
| 2622 | .results = .{}, | ||
| 2623 | .block_inst = block_inst, | ||
| 2624 | }), | ||
| 2625 | }; | ||
| 2626 | const label = &child_block.label.?; | ||
| 2627 | |||
| 2628 | defer child_block.instructions.deinit(self.gpa); | ||
| 2629 | defer label.results.deinit(self.gpa); | ||
| 2630 | |||
| 2631 | try self.analyzeBody(&child_block.base, inst.positionals.body); | ||
| 2632 | |||
| 2633 | // Blocks must terminate with noreturn instruction. | ||
| 2634 | assert(child_block.instructions.items.len != 0); | ||
| 2635 | assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn()); | ||
| 2636 | |||
| 2637 | // Need to set the type and emit the Block instruction. This allows machine code generation | ||
| 2638 | // to emit a jump instruction to after the block when it encounters the break. | ||
| 2639 | try parent_block.instructions.append(self.gpa, &block_inst.base); | ||
| 2640 | block_inst.base.ty = try self.resolvePeerTypes(scope, label.results.items); | ||
| 2641 | block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; | ||
| 2642 | return &block_inst.base; | ||
| 2643 | } | ||
| 2644 | |||
| 2645 | fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 2646 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 2647 | return self.addNoOp(b, inst.base.src, Type.initTag(.void), .breakpoint); | ||
| 2648 | } | ||
| 2649 | |||
| 2650 | fn analyzeInstBreak(self: *Module, scope: *Scope, inst: *zir.Inst.Break) InnerError!*Inst { | ||
| 2651 | const operand = try self.resolveInst(scope, inst.positionals.operand); | ||
| 2652 | const block = inst.positionals.block; | ||
| 2653 | return self.analyzeBreak(scope, inst.base.src, block, operand); | ||
| 2654 | } | ||
| 2655 | |||
| 2656 | fn analyzeInstBreakVoid(self: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) InnerError!*Inst { | ||
| 2657 | const block = inst.positionals.block; | ||
| 2658 | const void_inst = try self.constVoid(scope, inst.base.src); | ||
| 2659 | return self.analyzeBreak(scope, inst.base.src, block, void_inst); | ||
| 2660 | } | ||
| 2661 | |||
| 2662 | fn analyzeBreak( | ||
| 2663 | self: *Module, | ||
| 2664 | scope: *Scope, | ||
| 2665 | src: usize, | ||
| 2666 | zir_block: *zir.Inst.Block, | ||
| 2667 | operand: *Inst, | ||
| 2668 | ) InnerError!*Inst { | ||
| 2669 | var opt_block = scope.cast(Scope.Block); | ||
| 2670 | while (opt_block) |block| { | ||
| 2671 | if (block.label) |*label| { | ||
| 2672 | if (label.zir_block == zir_block) { | ||
| 2673 | try label.results.append(self.gpa, operand); | ||
| 2674 | const b = try self.requireRuntimeBlock(scope, src); | ||
| 2675 | return self.addBr(b, src, label.block_inst, operand); | ||
| 2676 | } | ||
| 2677 | } | ||
| 2678 | opt_block = block.parent; | ||
| 2679 | } else unreachable; | ||
| 2680 | } | ||
| 2681 | |||
| 2682 | fn analyzeInstDeclRefStr(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst { | ||
| 2683 | const decl_name = try self.resolveConstString(scope, inst.positionals.name); | ||
| 2684 | return self.analyzeDeclRefByName(scope, inst.base.src, decl_name); | ||
| 2685 | } | ||
| 2686 | |||
| 2687 | fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst { | ||
| 2688 | return self.analyzeDeclRefByName(scope, inst.base.src, inst.positionals.name); | ||
| 2689 | } | ||
| 2690 | |||
| 2691 | fn analyzeDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl { | ||
| 2692 | const decl_name = inst.positionals.name; | ||
| 2693 | const zir_module = scope.namespace().cast(Scope.ZIRModule).?; | ||
| 2694 | const src_decl = zir_module.contents.module.findDecl(decl_name) orelse | ||
| 2695 | return self.fail(scope, inst.base.src, "use of undeclared identifier '{}'", .{decl_name}); | ||
| 2696 | |||
| 2697 | const decl = try self.resolveCompleteZirDecl(scope, src_decl.decl); | ||
| 2698 | |||
| 2699 | return decl; | ||
| 2700 | } | ||
| 2701 | |||
| 2702 | fn analyzeInstDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst { | ||
| 2703 | const decl = try self.analyzeDeclVal(scope, inst); | ||
| 2704 | const ptr = try self.analyzeDeclRef(scope, inst.base.src, decl); | ||
| 2705 | return self.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); | ||
| 2706 | } | ||
| 2707 | |||
| 2708 | fn analyzeInstDeclValInModule(self: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst { | ||
| 2709 | const decl = inst.positionals.decl; | ||
| 2710 | const ptr = try self.analyzeDeclRef(scope, inst.base.src, decl); | ||
| 2711 | return self.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); | ||
| 2712 | } | ||
| 2713 | |||
| 2714 | fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst { | ||
| 2715 | const scope_decl = scope.decl().?; | 2139 | const scope_decl = scope.decl().?; |
| 2716 | try self.declareDeclDependency(scope_decl, decl); | 2140 | try self.declareDeclDependency(scope_decl, decl); |
| 2717 | self.ensureDeclAnalyzed(decl) catch |err| { | 2141 | self.ensureDeclAnalyzed(decl) catch |err| { |
| ... | @@ -2739,410 +2163,7 @@ fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerEr | ... | @@ -2739,410 +2163,7 @@ fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerEr |
| 2739 | }); | 2163 | }); |
| 2740 | } | 2164 | } |
| 2741 | 2165 | ||
| 2742 | fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst { | 2166 | pub fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: usize) InnerError!*Inst { |
| 2743 | const decl = self.lookupDeclName(scope, decl_name) orelse | ||
| 2744 | return self.fail(scope, src, "decl '{}' not found", .{decl_name}); | ||
| 2745 | return self.analyzeDeclRef(scope, src, decl); | ||
| 2746 | } | ||
| 2747 | |||
| 2748 | fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { | ||
| 2749 | const func = try self.resolveInst(scope, inst.positionals.func); | ||
| 2750 | if (func.ty.zigTypeTag() != .Fn) | ||
| 2751 | return self.fail(scope, inst.positionals.func.src, "type '{}' not a function", .{func.ty}); | ||
| 2752 | |||
| 2753 | const cc = func.ty.fnCallingConvention(); | ||
| 2754 | if (cc == .Naked) { | ||
| 2755 | // TODO add error note: declared here | ||
| 2756 | return self.fail( | ||
| 2757 | scope, | ||
| 2758 | inst.positionals.func.src, | ||
| 2759 | "unable to call function with naked calling convention", | ||
| 2760 | .{}, | ||
| 2761 | ); | ||
| 2762 | } | ||
| 2763 | const call_params_len = inst.positionals.args.len; | ||
| 2764 | const fn_params_len = func.ty.fnParamLen(); | ||
| 2765 | if (func.ty.fnIsVarArgs()) { | ||
| 2766 | if (call_params_len < fn_params_len) { | ||
| 2767 | // TODO add error note: declared here | ||
| 2768 | return self.fail( | ||
| 2769 | scope, | ||
| 2770 | inst.positionals.func.src, | ||
| 2771 | "expected at least {} arguments, found {}", | ||
| 2772 | .{ fn_params_len, call_params_len }, | ||
| 2773 | ); | ||
| 2774 | } | ||
| 2775 | return self.fail(scope, inst.base.src, "TODO implement support for calling var args functions", .{}); | ||
| 2776 | } else if (fn_params_len != call_params_len) { | ||
| 2777 | // TODO add error note: declared here | ||
| 2778 | return self.fail( | ||
| 2779 | scope, | ||
| 2780 | inst.positionals.func.src, | ||
| 2781 | "expected {} arguments, found {}", | ||
| 2782 | .{ fn_params_len, call_params_len }, | ||
| 2783 | ); | ||
| 2784 | } | ||
| 2785 | |||
| 2786 | if (inst.kw_args.modifier == .compile_time) { | ||
| 2787 | return self.fail(scope, inst.base.src, "TODO implement comptime function calls", .{}); | ||
| 2788 | } | ||
| 2789 | if (inst.kw_args.modifier != .auto) { | ||
| 2790 | return self.fail(scope, inst.base.src, "TODO implement call with modifier {}", .{inst.kw_args.modifier}); | ||
| 2791 | } | ||
| 2792 | |||
| 2793 | // TODO handle function calls of generic functions | ||
| 2794 | |||
| 2795 | const fn_param_types = try self.gpa.alloc(Type, fn_params_len); | ||
| 2796 | defer self.gpa.free(fn_param_types); | ||
| 2797 | func.ty.fnParamTypes(fn_param_types); | ||
| 2798 | |||
| 2799 | const casted_args = try scope.arena().alloc(*Inst, fn_params_len); | ||
| 2800 | for (inst.positionals.args) |src_arg, i| { | ||
| 2801 | const uncasted_arg = try self.resolveInst(scope, src_arg); | ||
| 2802 | casted_args[i] = try self.coerce(scope, fn_param_types[i], uncasted_arg); | ||
| 2803 | } | ||
| 2804 | |||
| 2805 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 2806 | return self.addCall(b, inst.base.src, Type.initTag(.void), func, casted_args); | ||
| 2807 | } | ||
| 2808 | |||
| 2809 | fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst { | ||
| 2810 | const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type); | ||
| 2811 | const fn_zir = blk: { | ||
| 2812 | var fn_arena = std.heap.ArenaAllocator.init(self.gpa); | ||
| 2813 | errdefer fn_arena.deinit(); | ||
| 2814 | |||
| 2815 | const fn_zir = try scope.arena().create(Fn.ZIR); | ||
| 2816 | fn_zir.* = .{ | ||
| 2817 | .body = .{ | ||
| 2818 | .instructions = fn_inst.positionals.body.instructions, | ||
| 2819 | }, | ||
| 2820 | .arena = fn_arena.state, | ||
| 2821 | }; | ||
| 2822 | break :blk fn_zir; | ||
| 2823 | }; | ||
| 2824 | const new_func = try scope.arena().create(Fn); | ||
| 2825 | new_func.* = .{ | ||
| 2826 | .analysis = .{ .queued = fn_zir }, | ||
| 2827 | .owner_decl = scope.decl().?, | ||
| 2828 | }; | ||
| 2829 | const fn_payload = try scope.arena().create(Value.Payload.Function); | ||
| 2830 | fn_payload.* = .{ .func = new_func }; | ||
| 2831 | return self.constInst(scope, fn_inst.base.src, .{ | ||
| 2832 | .ty = fn_type, | ||
| 2833 | .val = Value.initPayload(&fn_payload.base), | ||
| 2834 | }); | ||
| 2835 | } | ||
| 2836 | |||
| 2837 | fn analyzeInstIntType(self: *Module, scope: *Scope, inttype: *zir.Inst.IntType) InnerError!*Inst { | ||
| 2838 | return self.fail(scope, inttype.base.src, "TODO implement inttype", .{}); | ||
| 2839 | } | ||
| 2840 | |||
| 2841 | fn analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { | ||
| 2842 | const return_type = try self.resolveType(scope, fntype.positionals.return_type); | ||
| 2843 | |||
| 2844 | // Hot path for some common function types. | ||
| 2845 | if (fntype.positionals.param_types.len == 0) { | ||
| 2846 | if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Unspecified) { | ||
| 2847 | return self.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args)); | ||
| 2848 | } | ||
| 2849 | |||
| 2850 | if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .Unspecified) { | ||
| 2851 | return self.constType(scope, fntype.base.src, Type.initTag(.fn_void_no_args)); | ||
| 2852 | } | ||
| 2853 | |||
| 2854 | if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Naked) { | ||
| 2855 | return self.constType(scope, fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args)); | ||
| 2856 | } | ||
| 2857 | |||
| 2858 | if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .C) { | ||
| 2859 | return self.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args)); | ||
| 2860 | } | ||
| 2861 | } | ||
| 2862 | |||
| 2863 | const arena = scope.arena(); | ||
| 2864 | const param_types = try arena.alloc(Type, fntype.positionals.param_types.len); | ||
| 2865 | for (fntype.positionals.param_types) |param_type, i| { | ||
| 2866 | param_types[i] = try self.resolveType(scope, param_type); | ||
| 2867 | } | ||
| 2868 | |||
| 2869 | const payload = try arena.create(Type.Payload.Function); | ||
| 2870 | payload.* = .{ | ||
| 2871 | .cc = fntype.kw_args.cc, | ||
| 2872 | .return_type = return_type, | ||
| 2873 | .param_types = param_types, | ||
| 2874 | }; | ||
| 2875 | return self.constType(scope, fntype.base.src, Type.initPayload(&payload.base)); | ||
| 2876 | } | ||
| 2877 | |||
| 2878 | fn analyzeInstPrimitive(self: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst { | ||
| 2879 | return self.constInst(scope, primitive.base.src, primitive.positionals.tag.toTypedValue()); | ||
| 2880 | } | ||
| 2881 | |||
| 2882 | fn analyzeInstAs(self: *Module, scope: *Scope, as: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 2883 | const dest_type = try self.resolveType(scope, as.positionals.lhs); | ||
| 2884 | const new_inst = try self.resolveInst(scope, as.positionals.rhs); | ||
| 2885 | return self.coerce(scope, dest_type, new_inst); | ||
| 2886 | } | ||
| 2887 | |||
| 2888 | fn analyzeInstPtrToInt(self: *Module, scope: *Scope, ptrtoint: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 2889 | const ptr = try self.resolveInst(scope, ptrtoint.positionals.operand); | ||
| 2890 | if (ptr.ty.zigTypeTag() != .Pointer) { | ||
| 2891 | return self.fail(scope, ptrtoint.positionals.operand.src, "expected pointer, found '{}'", .{ptr.ty}); | ||
| 2892 | } | ||
| 2893 | // TODO handle known-pointer-address | ||
| 2894 | const b = try self.requireRuntimeBlock(scope, ptrtoint.base.src); | ||
| 2895 | const ty = Type.initTag(.usize); | ||
| 2896 | return self.addUnOp(b, ptrtoint.base.src, ty, .ptrtoint, ptr); | ||
| 2897 | } | ||
| 2898 | |||
| 2899 | fn analyzeInstFieldPtr(self: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr) InnerError!*Inst { | ||
| 2900 | const object_ptr = try self.resolveInst(scope, fieldptr.positionals.object_ptr); | ||
| 2901 | const field_name = try self.resolveConstString(scope, fieldptr.positionals.field_name); | ||
| 2902 | |||
| 2903 | const elem_ty = switch (object_ptr.ty.zigTypeTag()) { | ||
| 2904 | .Pointer => object_ptr.ty.elemType(), | ||
| 2905 | else => return self.fail(scope, fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}), | ||
| 2906 | }; | ||
| 2907 | switch (elem_ty.zigTypeTag()) { | ||
| 2908 | .Array => { | ||
| 2909 | if (mem.eql(u8, field_name, "len")) { | ||
| 2910 | const len_payload = try scope.arena().create(Value.Payload.Int_u64); | ||
| 2911 | len_payload.* = .{ .int = elem_ty.arrayLen() }; | ||
| 2912 | |||
| 2913 | const ref_payload = try scope.arena().create(Value.Payload.RefVal); | ||
| 2914 | ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) }; | ||
| 2915 | |||
| 2916 | return self.constInst(scope, fieldptr.base.src, .{ | ||
| 2917 | .ty = Type.initTag(.single_const_pointer_to_comptime_int), | ||
| 2918 | .val = Value.initPayload(&ref_payload.base), | ||
| 2919 | }); | ||
| 2920 | } else { | ||
| 2921 | return self.fail( | ||
| 2922 | scope, | ||
| 2923 | fieldptr.positionals.field_name.src, | ||
| 2924 | "no member named '{}' in '{}'", | ||
| 2925 | .{ field_name, elem_ty }, | ||
| 2926 | ); | ||
| 2927 | } | ||
| 2928 | }, | ||
| 2929 | else => return self.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}), | ||
| 2930 | } | ||
| 2931 | } | ||
| 2932 | |||
| 2933 | fn analyzeInstIntCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 2934 | const dest_type = try self.resolveType(scope, inst.positionals.lhs); | ||
| 2935 | const operand = try self.resolveInst(scope, inst.positionals.rhs); | ||
| 2936 | |||
| 2937 | const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { | ||
| 2938 | .ComptimeInt => true, | ||
| 2939 | .Int => false, | ||
| 2940 | else => return self.fail( | ||
| 2941 | scope, | ||
| 2942 | inst.positionals.lhs.src, | ||
| 2943 | "expected integer type, found '{}'", | ||
| 2944 | .{ | ||
| 2945 | dest_type, | ||
| 2946 | }, | ||
| 2947 | ), | ||
| 2948 | }; | ||
| 2949 | |||
| 2950 | switch (operand.ty.zigTypeTag()) { | ||
| 2951 | .ComptimeInt, .Int => {}, | ||
| 2952 | else => return self.fail( | ||
| 2953 | scope, | ||
| 2954 | inst.positionals.rhs.src, | ||
| 2955 | "expected integer type, found '{}'", | ||
| 2956 | .{operand.ty}, | ||
| 2957 | ), | ||
| 2958 | } | ||
| 2959 | |||
| 2960 | if (operand.value() != null) { | ||
| 2961 | return self.coerce(scope, dest_type, operand); | ||
| 2962 | } else if (dest_is_comptime_int) { | ||
| 2963 | return self.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_int'", .{}); | ||
| 2964 | } | ||
| 2965 | |||
| 2966 | return self.fail(scope, inst.base.src, "TODO implement analyze widen or shorten int", .{}); | ||
| 2967 | } | ||
| 2968 | |||
| 2969 | fn analyzeInstBitCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 2970 | const dest_type = try self.resolveType(scope, inst.positionals.lhs); | ||
| 2971 | const operand = try self.resolveInst(scope, inst.positionals.rhs); | ||
| 2972 | return self.bitcast(scope, dest_type, operand); | ||
| 2973 | } | ||
| 2974 | |||
| 2975 | fn analyzeInstFloatCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 2976 | const dest_type = try self.resolveType(scope, inst.positionals.lhs); | ||
| 2977 | const operand = try self.resolveInst(scope, inst.positionals.rhs); | ||
| 2978 | |||
| 2979 | const dest_is_comptime_float = switch (dest_type.zigTypeTag()) { | ||
| 2980 | .ComptimeFloat => true, | ||
| 2981 | .Float => false, | ||
| 2982 | else => return self.fail( | ||
| 2983 | scope, | ||
| 2984 | inst.positionals.lhs.src, | ||
| 2985 | "expected float type, found '{}'", | ||
| 2986 | .{ | ||
| 2987 | dest_type, | ||
| 2988 | }, | ||
| 2989 | ), | ||
| 2990 | }; | ||
| 2991 | |||
| 2992 | switch (operand.ty.zigTypeTag()) { | ||
| 2993 | .ComptimeFloat, .Float, .ComptimeInt => {}, | ||
| 2994 | else => return self.fail( | ||
| 2995 | scope, | ||
| 2996 | inst.positionals.rhs.src, | ||
| 2997 | "expected float type, found '{}'", | ||
| 2998 | .{operand.ty}, | ||
| 2999 | ), | ||
| 3000 | } | ||
| 3001 | |||
| 3002 | if (operand.value() != null) { | ||
| 3003 | return self.coerce(scope, dest_type, operand); | ||
| 3004 | } else if (dest_is_comptime_float) { | ||
| 3005 | return self.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_float'", .{}); | ||
| 3006 | } | ||
| 3007 | |||
| 3008 | return self.fail(scope, inst.base.src, "TODO implement analyze widen or shorten float", .{}); | ||
| 3009 | } | ||
| 3010 | |||
| 3011 | fn analyzeInstElemPtr(self: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) InnerError!*Inst { | ||
| 3012 | const array_ptr = try self.resolveInst(scope, inst.positionals.array_ptr); | ||
| 3013 | const uncasted_index = try self.resolveInst(scope, inst.positionals.index); | ||
| 3014 | const elem_index = try self.coerce(scope, Type.initTag(.usize), uncasted_index); | ||
| 3015 | |||
| 3016 | if (array_ptr.ty.isSinglePointer() and array_ptr.ty.elemType().zigTypeTag() == .Array) { | ||
| 3017 | if (array_ptr.value()) |array_ptr_val| { | ||
| 3018 | if (elem_index.value()) |index_val| { | ||
| 3019 | // Both array pointer and index are compile-time known. | ||
| 3020 | const index_u64 = index_val.toUnsignedInt(); | ||
| 3021 | // @intCast here because it would have been impossible to construct a value that | ||
| 3022 | // required a larger index. | ||
| 3023 | const elem_ptr = try array_ptr_val.elemPtr(scope.arena(), @intCast(usize, index_u64)); | ||
| 3024 | |||
| 3025 | const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer); | ||
| 3026 | type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() }; | ||
| 3027 | |||
| 3028 | return self.constInst(scope, inst.base.src, .{ | ||
| 3029 | .ty = Type.initPayload(&type_payload.base), | ||
| 3030 | .val = elem_ptr, | ||
| 3031 | }); | ||
| 3032 | } | ||
| 3033 | } | ||
| 3034 | } | ||
| 3035 | |||
| 3036 | return self.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{}); | ||
| 3037 | } | ||
| 3038 | |||
| 3039 | fn floatOpAllowed(tag: zir.Inst.Tag) bool { | ||
| 3040 | // extend this swich as additional operators are implemented | ||
| 3041 | return switch (tag) { | ||
| 3042 | .add, .sub => true, | ||
| 3043 | else => false, | ||
| 3044 | }; | ||
| 3045 | } | ||
| 3046 | |||
| 3047 | fn analyzeInstArithmetic(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 3048 | const tracy = trace(@src()); | ||
| 3049 | defer tracy.end(); | ||
| 3050 | |||
| 3051 | const lhs = try self.resolveInst(scope, inst.positionals.lhs); | ||
| 3052 | const rhs = try self.resolveInst(scope, inst.positionals.rhs); | ||
| 3053 | |||
| 3054 | const instructions = &[_]*Inst{ lhs, rhs }; | ||
| 3055 | const resolved_type = try self.resolvePeerTypes(scope, instructions); | ||
| 3056 | const casted_lhs = try self.coerce(scope, resolved_type, lhs); | ||
| 3057 | const casted_rhs = try self.coerce(scope, resolved_type, rhs); | ||
| 3058 | |||
| 3059 | const scalar_type = if (resolved_type.zigTypeTag() == .Vector) | ||
| 3060 | resolved_type.elemType() | ||
| 3061 | else | ||
| 3062 | resolved_type; | ||
| 3063 | |||
| 3064 | const scalar_tag = scalar_type.zigTypeTag(); | ||
| 3065 | |||
| 3066 | if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) { | ||
| 3067 | if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) { | ||
| 3068 | return self.fail(scope, inst.base.src, "vector length mismatch: {} and {}", .{ | ||
| 3069 | lhs.ty.arrayLen(), | ||
| 3070 | rhs.ty.arrayLen(), | ||
| 3071 | }); | ||
| 3072 | } | ||
| 3073 | return self.fail(scope, inst.base.src, "TODO implement support for vectors in analyzeInstBinOp", .{}); | ||
| 3074 | } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) { | ||
| 3075 | return self.fail(scope, inst.base.src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{ | ||
| 3076 | lhs.ty, | ||
| 3077 | rhs.ty, | ||
| 3078 | }); | ||
| 3079 | } | ||
| 3080 | |||
| 3081 | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; | ||
| 3082 | const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; | ||
| 3083 | |||
| 3084 | if (!is_int and !(is_float and floatOpAllowed(inst.base.tag))) { | ||
| 3085 | return self.fail(scope, inst.base.src, "invalid operands to binary expression: '{}' and '{}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) }); | ||
| 3086 | } | ||
| 3087 | |||
| 3088 | if (casted_lhs.value()) |lhs_val| { | ||
| 3089 | if (casted_rhs.value()) |rhs_val| { | ||
| 3090 | return self.analyzeInstComptimeOp(scope, scalar_type, inst, lhs_val, rhs_val); | ||
| 3091 | } | ||
| 3092 | } | ||
| 3093 | |||
| 3094 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 3095 | const ir_tag = switch (inst.base.tag) { | ||
| 3096 | .add => Inst.Tag.add, | ||
| 3097 | .sub => Inst.Tag.sub, | ||
| 3098 | else => return self.fail(scope, inst.base.src, "TODO implement arithmetic for operand '{}''", .{@tagName(inst.base.tag)}), | ||
| 3099 | }; | ||
| 3100 | |||
| 3101 | return self.addBinOp(b, inst.base.src, scalar_type, ir_tag, casted_lhs, casted_rhs); | ||
| 3102 | } | ||
| 3103 | |||
| 3104 | /// Analyzes operands that are known at comptime | ||
| 3105 | fn analyzeInstComptimeOp(self: *Module, scope: *Scope, res_type: Type, inst: *zir.Inst.BinOp, lhs_val: Value, rhs_val: Value) InnerError!*Inst { | ||
| 3106 | // incase rhs is 0, simply return lhs without doing any calculations | ||
| 3107 | // TODO Once division is implemented we should throw an error when dividing by 0. | ||
| 3108 | if (rhs_val.tag() == .zero or rhs_val.tag() == .the_one_possible_value) { | ||
| 3109 | return self.constInst(scope, inst.base.src, .{ | ||
| 3110 | .ty = res_type, | ||
| 3111 | .val = lhs_val, | ||
| 3112 | }); | ||
| 3113 | } | ||
| 3114 | const is_int = res_type.isInt() or res_type.zigTypeTag() == .ComptimeInt; | ||
| 3115 | |||
| 3116 | const value = try switch (inst.base.tag) { | ||
| 3117 | .add => blk: { | ||
| 3118 | const val = if (is_int) | ||
| 3119 | intAdd(scope.arena(), lhs_val, rhs_val) | ||
| 3120 | else | ||
| 3121 | self.floatAdd(scope, res_type, inst, lhs_val, rhs_val); | ||
| 3122 | break :blk val; | ||
| 3123 | }, | ||
| 3124 | .sub => blk: { | ||
| 3125 | const val = if (is_int) | ||
| 3126 | intSub(scope.arena(), lhs_val, rhs_val) | ||
| 3127 | else | ||
| 3128 | self.floatSub(scope, res_type, inst, lhs_val, rhs_val); | ||
| 3129 | break :blk val; | ||
| 3130 | }, | ||
| 3131 | else => return self.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{}'", .{@tagName(inst.base.tag)}), | ||
| 3132 | }; | ||
| 3133 | |||
| 3134 | return self.constInst(scope, inst.base.src, .{ | ||
| 3135 | .ty = res_type, | ||
| 3136 | .val = value, | ||
| 3137 | }); | ||
| 3138 | } | ||
| 3139 | |||
| 3140 | fn analyzeInstDeref(self: *Module, scope: *Scope, deref: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 3141 | const ptr = try self.resolveInst(scope, deref.positionals.operand); | ||
| 3142 | return self.analyzeDeref(scope, deref.base.src, ptr, deref.positionals.operand.src); | ||
| 3143 | } | ||
| 3144 | |||
| 3145 | fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: usize) InnerError!*Inst { | ||
| 3146 | const elem_ty = switch (ptr.ty.zigTypeTag()) { | 2167 | const elem_ty = switch (ptr.ty.zigTypeTag()) { |
| 3147 | .Pointer => ptr.ty.elemType(), | 2168 | .Pointer => ptr.ty.elemType(), |
| 3148 | else => return self.fail(scope, ptr_src, "expected pointer, found '{}'", .{ptr.ty}), | 2169 | 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 | ... | @@ -3154,164 +2175,19 @@ fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: u |
| 3154 | }); | 2175 | }); |
| 3155 | } | 2176 | } |
| 3156 | 2177 | ||
| 3157 | return self.fail(scope, src, "TODO implement runtime deref", .{}); | 2178 | const b = try self.requireRuntimeBlock(scope, src); |
| 3158 | } | 2179 | return self.addUnOp(b, src, elem_ty, .load, ptr); |
| 3159 | |||
| 3160 | fn analyzeInstAsm(self: *Module, scope: *Scope, assembly: *zir.Inst.Asm) InnerError!*Inst { | ||
| 3161 | const return_type = try self.resolveType(scope, assembly.positionals.return_type); | ||
| 3162 | const asm_source = try self.resolveConstString(scope, assembly.positionals.asm_source); | ||
| 3163 | const output = if (assembly.kw_args.output) |o| try self.resolveConstString(scope, o) else null; | ||
| 3164 | |||
| 3165 | const inputs = try scope.arena().alloc([]const u8, assembly.kw_args.inputs.len); | ||
| 3166 | const clobbers = try scope.arena().alloc([]const u8, assembly.kw_args.clobbers.len); | ||
| 3167 | const args = try scope.arena().alloc(*Inst, assembly.kw_args.args.len); | ||
| 3168 | |||
| 3169 | for (inputs) |*elem, i| { | ||
| 3170 | elem.* = try self.resolveConstString(scope, assembly.kw_args.inputs[i]); | ||
| 3171 | } | ||
| 3172 | for (clobbers) |*elem, i| { | ||
| 3173 | elem.* = try self.resolveConstString(scope, assembly.kw_args.clobbers[i]); | ||
| 3174 | } | ||
| 3175 | for (args) |*elem, i| { | ||
| 3176 | const arg = try self.resolveInst(scope, assembly.kw_args.args[i]); | ||
| 3177 | elem.* = try self.coerce(scope, Type.initTag(.usize), arg); | ||
| 3178 | } | ||
| 3179 | |||
| 3180 | const b = try self.requireRuntimeBlock(scope, assembly.base.src); | ||
| 3181 | const inst = try b.arena.create(Inst.Assembly); | ||
| 3182 | inst.* = .{ | ||
| 3183 | .base = .{ | ||
| 3184 | .tag = .assembly, | ||
| 3185 | .ty = return_type, | ||
| 3186 | .src = assembly.base.src, | ||
| 3187 | }, | ||
| 3188 | .asm_source = asm_source, | ||
| 3189 | .is_volatile = assembly.kw_args.@"volatile", | ||
| 3190 | .output = output, | ||
| 3191 | .inputs = inputs, | ||
| 3192 | .clobbers = clobbers, | ||
| 3193 | .args = args, | ||
| 3194 | }; | ||
| 3195 | try b.instructions.append(self.gpa, &inst.base); | ||
| 3196 | return &inst.base; | ||
| 3197 | } | ||
| 3198 | |||
| 3199 | fn analyzeInstCmp( | ||
| 3200 | self: *Module, | ||
| 3201 | scope: *Scope, | ||
| 3202 | inst: *zir.Inst.BinOp, | ||
| 3203 | op: std.math.CompareOperator, | ||
| 3204 | ) InnerError!*Inst { | ||
| 3205 | const lhs = try self.resolveInst(scope, inst.positionals.lhs); | ||
| 3206 | const rhs = try self.resolveInst(scope, inst.positionals.rhs); | ||
| 3207 | |||
| 3208 | const is_equality_cmp = switch (op) { | ||
| 3209 | .eq, .neq => true, | ||
| 3210 | else => false, | ||
| 3211 | }; | ||
| 3212 | const lhs_ty_tag = lhs.ty.zigTypeTag(); | ||
| 3213 | const rhs_ty_tag = rhs.ty.zigTypeTag(); | ||
| 3214 | if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) { | ||
| 3215 | // null == null, null != null | ||
| 3216 | return self.constBool(scope, inst.base.src, op == .eq); | ||
| 3217 | } else if (is_equality_cmp and | ||
| 3218 | ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or | ||
| 3219 | rhs_ty_tag == .Null and lhs_ty_tag == .Optional)) | ||
| 3220 | { | ||
| 3221 | // comparing null with optionals | ||
| 3222 | const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs; | ||
| 3223 | if (opt_operand.value()) |opt_val| { | ||
| 3224 | const is_null = opt_val.isNull(); | ||
| 3225 | return self.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null); | ||
| 3226 | } | ||
| 3227 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 3228 | const inst_tag: Inst.Tag = switch (op) { | ||
| 3229 | .eq => .isnull, | ||
| 3230 | .neq => .isnonnull, | ||
| 3231 | else => unreachable, | ||
| 3232 | }; | ||
| 3233 | return self.addUnOp(b, inst.base.src, Type.initTag(.bool), inst_tag, opt_operand); | ||
| 3234 | } else if (is_equality_cmp and | ||
| 3235 | ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr()))) | ||
| 3236 | { | ||
| 3237 | return self.fail(scope, inst.base.src, "TODO implement C pointer cmp", .{}); | ||
| 3238 | } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) { | ||
| 3239 | const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty; | ||
| 3240 | return self.fail(scope, inst.base.src, "comparison of '{}' with null", .{non_null_type}); | ||
| 3241 | } else if (is_equality_cmp and | ||
| 3242 | ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or | ||
| 3243 | (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union))) | ||
| 3244 | { | ||
| 3245 | return self.fail(scope, inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); | ||
| 3246 | } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { | ||
| 3247 | if (!is_equality_cmp) { | ||
| 3248 | return self.fail(scope, inst.base.src, "{} operator not allowed for errors", .{@tagName(op)}); | ||
| 3249 | } | ||
| 3250 | return self.fail(scope, inst.base.src, "TODO implement equality comparison between errors", .{}); | ||
| 3251 | } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) { | ||
| 3252 | // This operation allows any combination of integer and float types, regardless of the | ||
| 3253 | // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for | ||
| 3254 | // numeric types. | ||
| 3255 | return self.cmpNumeric(scope, inst.base.src, lhs, rhs, op); | ||
| 3256 | } | ||
| 3257 | return self.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{}); | ||
| 3258 | } | ||
| 3259 | |||
| 3260 | fn analyzeInstBoolNot(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 3261 | const uncasted_operand = try self.resolveInst(scope, inst.positionals.operand); | ||
| 3262 | const bool_type = Type.initTag(.bool); | ||
| 3263 | const operand = try self.coerce(scope, bool_type, uncasted_operand); | ||
| 3264 | if (try self.resolveDefinedValue(scope, operand)) |val| { | ||
| 3265 | return self.constBool(scope, inst.base.src, !val.toBool()); | ||
| 3266 | } | ||
| 3267 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 3268 | return self.addUnOp(b, inst.base.src, bool_type, .not, operand); | ||
| 3269 | } | ||
| 3270 | |||
| 3271 | fn analyzeInstIsNonNull(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst { | ||
| 3272 | const operand = try self.resolveInst(scope, inst.positionals.operand); | ||
| 3273 | return self.analyzeIsNull(scope, inst.base.src, operand, invert_logic); | ||
| 3274 | } | 2180 | } |
| 3275 | 2181 | ||
| 3276 | fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst { | 2182 | pub fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst { |
| 3277 | const uncasted_cond = try self.resolveInst(scope, inst.positionals.condition); | 2183 | const decl = self.lookupDeclName(scope, decl_name) orelse |
| 3278 | const cond = try self.coerce(scope, Type.initTag(.bool), uncasted_cond); | 2184 | return self.fail(scope, src, "decl '{}' not found", .{decl_name}); |
| 3279 | 2185 | return self.analyzeDeclRef(scope, src, decl); | |
| 3280 | if (try self.resolveDefinedValue(scope, cond)) |cond_val| { | ||
| 3281 | const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body; | ||
| 3282 | try self.analyzeBody(scope, body.*); | ||
| 3283 | return self.constVoid(scope, inst.base.src); | ||
| 3284 | } | ||
| 3285 | |||
| 3286 | const parent_block = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 3287 | |||
| 3288 | var true_block: Scope.Block = .{ | ||
| 3289 | .parent = parent_block, | ||
| 3290 | .func = parent_block.func, | ||
| 3291 | .decl = parent_block.decl, | ||
| 3292 | .instructions = .{}, | ||
| 3293 | .arena = parent_block.arena, | ||
| 3294 | }; | ||
| 3295 | defer true_block.instructions.deinit(self.gpa); | ||
| 3296 | try self.analyzeBody(&true_block.base, inst.positionals.then_body); | ||
| 3297 | |||
| 3298 | var false_block: Scope.Block = .{ | ||
| 3299 | .parent = parent_block, | ||
| 3300 | .func = parent_block.func, | ||
| 3301 | .decl = parent_block.decl, | ||
| 3302 | .instructions = .{}, | ||
| 3303 | .arena = parent_block.arena, | ||
| 3304 | }; | ||
| 3305 | defer false_block.instructions.deinit(self.gpa); | ||
| 3306 | try self.analyzeBody(&false_block.base, inst.positionals.else_body); | ||
| 3307 | |||
| 3308 | const then_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) }; | ||
| 3309 | const else_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) }; | ||
| 3310 | return self.addCondBr(parent_block, inst.base.src, cond, then_body, else_body); | ||
| 3311 | } | 2186 | } |
| 3312 | 2187 | ||
| 3313 | fn wantSafety(self: *Module, scope: *Scope) bool { | 2188 | pub fn wantSafety(self: *Module, scope: *Scope) bool { |
| 3314 | return switch (self.optimize_mode) { | 2189 | // TODO take into account scope's safety overrides |
| 2190 | return switch (self.optimizeMode()) { | ||
| 3315 | .Debug => true, | 2191 | .Debug => true, |
| 3316 | .ReleaseSafe => true, | 2192 | .ReleaseSafe => true, |
| 3317 | .ReleaseFast => false, | 2193 | .ReleaseFast => false, |
| ... | @@ -3319,33 +2195,12 @@ fn wantSafety(self: *Module, scope: *Scope) bool { | ... | @@ -3319,33 +2195,12 @@ fn wantSafety(self: *Module, scope: *Scope) bool { |
| 3319 | }; | 2195 | }; |
| 3320 | } | 2196 | } |
| 3321 | 2197 | ||
| 3322 | fn analyzeInstUnreachable(self: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { | 2198 | pub fn analyzeUnreach(self: *Module, scope: *Scope, src: usize) InnerError!*Inst { |
| 3323 | const b = try self.requireRuntimeBlock(scope, unreach.base.src); | 2199 | const b = try self.requireRuntimeBlock(scope, src); |
| 3324 | if (self.wantSafety(scope)) { | 2200 | return self.addNoOp(b, src, Type.initTag(.noreturn), .unreach); |
| 3325 | // TODO Once we have a panic function to call, call it here instead of this. | ||
| 3326 | _ = try self.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint); | ||
| 3327 | } | ||
| 3328 | return self.addNoOp(b, unreach.base.src, Type.initTag(.noreturn), .unreach); | ||
| 3329 | } | ||
| 3330 | |||
| 3331 | fn analyzeInstRet(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 3332 | const operand = try self.resolveInst(scope, inst.positionals.operand); | ||
| 3333 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 3334 | return self.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand); | ||
| 3335 | } | ||
| 3336 | |||
| 3337 | fn analyzeInstRetVoid(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 3338 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 3339 | return self.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid); | ||
| 3340 | } | ||
| 3341 | |||
| 3342 | fn analyzeBody(self: *Module, scope: *Scope, body: zir.Module.Body) !void { | ||
| 3343 | for (body.instructions) |src_inst| { | ||
| 3344 | src_inst.analyzed_inst = try self.analyzeInst(scope, src_inst); | ||
| 3345 | } | ||
| 3346 | } | 2201 | } |
| 3347 | 2202 | ||
| 3348 | fn analyzeIsNull( | 2203 | pub fn analyzeIsNull( |
| 3349 | self: *Module, | 2204 | self: *Module, |
| 3350 | scope: *Scope, | 2205 | scope: *Scope, |
| 3351 | src: usize, | 2206 | src: usize, |
| ... | @@ -3356,7 +2211,7 @@ fn analyzeIsNull( | ... | @@ -3356,7 +2211,7 @@ fn analyzeIsNull( |
| 3356 | } | 2211 | } |
| 3357 | 2212 | ||
| 3358 | /// Asserts that lhs and rhs types are both numeric. | 2213 | /// Asserts that lhs and rhs types are both numeric. |
| 3359 | fn cmpNumeric( | 2214 | pub fn cmpNumeric( |
| 3360 | self: *Module, | 2215 | self: *Module, |
| 3361 | scope: *Scope, | 2216 | scope: *Scope, |
| 3362 | src: usize, | 2217 | src: usize, |
| ... | @@ -3539,7 +2394,7 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { | ... | @@ -3539,7 +2394,7 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { |
| 3539 | } | 2394 | } |
| 3540 | } | 2395 | } |
| 3541 | 2396 | ||
| 3542 | fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { | 2397 | pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { |
| 3543 | if (instructions.len == 0) | 2398 | if (instructions.len == 0) |
| 3544 | return Type.initTag(.noreturn); | 2399 | return Type.initTag(.noreturn); |
| 3545 | 2400 | ||
| ... | @@ -3579,7 +2434,7 @@ fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { | ... | @@ -3579,7 +2434,7 @@ fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { |
| 3579 | return prev_inst.ty; | 2434 | return prev_inst.ty; |
| 3580 | } | 2435 | } |
| 3581 | 2436 | ||
| 3582 | fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { | 2437 | pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { |
| 3583 | // If the types are the same, we can return the operand. | 2438 | // If the types are the same, we can return the operand. |
| 3584 | if (dest_type.eql(inst.ty)) | 2439 | if (dest_type.eql(inst.ty)) |
| 3585 | return inst; | 2440 | return inst; |
| ... | @@ -3599,7 +2454,7 @@ fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { | ... | @@ -3599,7 +2454,7 @@ fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { |
| 3599 | 2454 | ||
| 3600 | // *[N]T to []T | 2455 | // *[N]T to []T |
| 3601 | if (inst.ty.isSinglePointer() and dest_type.isSlice() and | 2456 | if (inst.ty.isSinglePointer() and dest_type.isSlice() and |
| 3602 | (!inst.ty.pointerIsConst() or dest_type.pointerIsConst())) | 2457 | (!inst.ty.isConstPtr() or dest_type.isConstPtr())) |
| 3603 | { | 2458 | { |
| 3604 | const array_type = inst.ty.elemType(); | 2459 | const array_type = inst.ty.elemType(); |
| 3605 | const dst_elem_type = dest_type.elemType(); | 2460 | const dst_elem_type = dest_type.elemType(); |
| ... | @@ -3675,7 +2530,23 @@ fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { | ... | @@ -3675,7 +2530,23 @@ fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { |
| 3675 | return self.fail(scope, inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type }); | 2530 | return self.fail(scope, inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type }); |
| 3676 | } | 2531 | } |
| 3677 | 2532 | ||
| 3678 | fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { | 2533 | pub fn storePtr(self: *Module, scope: *Scope, src: usize, ptr: *Inst, uncasted_value: *Inst) !*Inst { |
| 2534 | if (ptr.ty.isConstPtr()) | ||
| 2535 | return self.fail(scope, src, "cannot assign to constant", .{}); | ||
| 2536 | |||
| 2537 | const elem_ty = ptr.ty.elemType(); | ||
| 2538 | const value = try self.coerce(scope, elem_ty, uncasted_value); | ||
| 2539 | if (elem_ty.onePossibleValue() != null) | ||
| 2540 | return self.constVoid(scope, src); | ||
| 2541 | |||
| 2542 | // TODO handle comptime pointer writes | ||
| 2543 | // TODO handle if the element type requires comptime | ||
| 2544 | |||
| 2545 | const b = try self.requireRuntimeBlock(scope, src); | ||
| 2546 | return self.addBinOp(b, src, Type.initTag(.void), .store, ptr, value); | ||
| 2547 | } | ||
| 2548 | |||
| 2549 | pub fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { | ||
| 3679 | if (inst.value()) |val| { | 2550 | if (inst.value()) |val| { |
| 3680 | // Keep the comptime Value representation; take the new type. | 2551 | // Keep the comptime Value representation; take the new type. |
| 3681 | return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); | 2552 | 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 { | ... | @@ -3822,7 +2693,7 @@ fn srcHashEql(a: std.zig.SrcHash, b: std.zig.SrcHash) bool { |
| 3822 | return @bitCast(u128, a) == @bitCast(u128, b); | 2693 | return @bitCast(u128, a) == @bitCast(u128, b); |
| 3823 | } | 2694 | } |
| 3824 | 2695 | ||
| 3825 | fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { | 2696 | pub fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { |
| 3826 | // TODO is this a performance issue? maybe we should try the operation without | 2697 | // TODO is this a performance issue? maybe we should try the operation without |
| 3827 | // resorting to BigInt first. | 2698 | // resorting to BigInt first. |
| 3828 | var lhs_space: Value.BigIntSpace = undefined; | 2699 | var lhs_space: Value.BigIntSpace = undefined; |
| ... | @@ -3850,7 +2721,7 @@ fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { | ... | @@ -3850,7 +2721,7 @@ fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { |
| 3850 | return Value.initPayload(val_payload); | 2721 | return Value.initPayload(val_payload); |
| 3851 | } | 2722 | } |
| 3852 | 2723 | ||
| 3853 | fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { | 2724 | pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { |
| 3854 | // TODO is this a performance issue? maybe we should try the operation without | 2725 | // TODO is this a performance issue? maybe we should try the operation without |
| 3855 | // resorting to BigInt first. | 2726 | // resorting to BigInt first. |
| 3856 | var lhs_space: Value.BigIntSpace = undefined; | 2727 | var lhs_space: Value.BigIntSpace = undefined; |
| ... | @@ -3878,7 +2749,7 @@ fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { | ... | @@ -3878,7 +2749,7 @@ fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { |
| 3878 | return Value.initPayload(val_payload); | 2749 | return Value.initPayload(val_payload); |
| 3879 | } | 2750 | } |
| 3880 | 2751 | ||
| 3881 | fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinOp, lhs: Value, rhs: Value) !Value { | 2752 | pub fn floatAdd(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value { |
| 3882 | var bit_count = switch (float_type.tag()) { | 2753 | var bit_count = switch (float_type.tag()) { |
| 3883 | .comptime_float => 128, | 2754 | .comptime_float => 128, |
| 3884 | else => float_type.floatBits(self.target()), | 2755 | else => float_type.floatBits(self.target()), |
| ... | @@ -3887,7 +2758,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO | ... | @@ -3887,7 +2758,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO |
| 3887 | const allocator = scope.arena(); | 2758 | const allocator = scope.arena(); |
| 3888 | const val_payload = switch (bit_count) { | 2759 | const val_payload = switch (bit_count) { |
| 3889 | 16 => { | 2760 | 16 => { |
| 3890 | return self.fail(scope, inst.base.src, "TODO Implement addition for soft floats", .{}); | 2761 | return self.fail(scope, src, "TODO Implement addition for soft floats", .{}); |
| 3891 | }, | 2762 | }, |
| 3892 | 32 => blk: { | 2763 | 32 => blk: { |
| 3893 | const lhs_val = lhs.toFloat(f32); | 2764 | const lhs_val = lhs.toFloat(f32); |
| ... | @@ -3904,7 +2775,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO | ... | @@ -3904,7 +2775,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO |
| 3904 | break :blk &val_payload.base; | 2775 | break :blk &val_payload.base; |
| 3905 | }, | 2776 | }, |
| 3906 | 128 => blk: { | 2777 | 128 => blk: { |
| 3907 | return self.fail(scope, inst.base.src, "TODO Implement addition for big floats", .{}); | 2778 | return self.fail(scope, src, "TODO Implement addition for big floats", .{}); |
| 3908 | }, | 2779 | }, |
| 3909 | else => unreachable, | 2780 | else => unreachable, |
| 3910 | }; | 2781 | }; |
| ... | @@ -3912,7 +2783,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO | ... | @@ -3912,7 +2783,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO |
| 3912 | return Value.initPayload(val_payload); | 2783 | return Value.initPayload(val_payload); |
| 3913 | } | 2784 | } |
| 3914 | 2785 | ||
| 3915 | fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinOp, lhs: Value, rhs: Value) !Value { | 2786 | pub fn floatSub(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value { |
| 3916 | var bit_count = switch (float_type.tag()) { | 2787 | var bit_count = switch (float_type.tag()) { |
| 3917 | .comptime_float => 128, | 2788 | .comptime_float => 128, |
| 3918 | else => float_type.floatBits(self.target()), | 2789 | else => float_type.floatBits(self.target()), |
| ... | @@ -3921,7 +2792,7 @@ fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO | ... | @@ -3921,7 +2792,7 @@ fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO |
| 3921 | const allocator = scope.arena(); | 2792 | const allocator = scope.arena(); |
| 3922 | const val_payload = switch (bit_count) { | 2793 | const val_payload = switch (bit_count) { |
| 3923 | 16 => { | 2794 | 16 => { |
| 3924 | return self.fail(scope, inst.base.src, "TODO Implement substraction for soft floats", .{}); | 2795 | return self.fail(scope, src, "TODO Implement substraction for soft floats", .{}); |
| 3925 | }, | 2796 | }, |
| 3926 | 32 => blk: { | 2797 | 32 => blk: { |
| 3927 | const lhs_val = lhs.toFloat(f32); | 2798 | const lhs_val = lhs.toFloat(f32); |
| ... | @@ -3938,10 +2809,54 @@ fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO | ... | @@ -3938,10 +2809,54 @@ fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO |
| 3938 | break :blk &val_payload.base; | 2809 | break :blk &val_payload.base; |
| 3939 | }, | 2810 | }, |
| 3940 | 128 => blk: { | 2811 | 128 => blk: { |
| 3941 | return self.fail(scope, inst.base.src, "TODO Implement substraction for big floats", .{}); | 2812 | return self.fail(scope, src, "TODO Implement substraction for big floats", .{}); |
| 3942 | }, | 2813 | }, |
| 3943 | else => unreachable, | 2814 | else => unreachable, |
| 3944 | }; | 2815 | }; |
| 3945 | 2816 | ||
| 3946 | return Value.initPayload(val_payload); | 2817 | return Value.initPayload(val_payload); |
| 3947 | } | 2818 | } |
| 2819 | |||
| 2820 | pub fn singleMutPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) error{OutOfMemory}!Type { | ||
| 2821 | const type_payload = try scope.arena().create(Type.Payload.SingleMutPointer); | ||
| 2822 | type_payload.* = .{ .pointee_type = elem_ty }; | ||
| 2823 | return Type.initPayload(&type_payload.base); | ||
| 2824 | } | ||
| 2825 | |||
| 2826 | pub fn singleConstPtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type) error{OutOfMemory}!Type { | ||
| 2827 | const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer); | ||
| 2828 | type_payload.* = .{ .pointee_type = elem_ty }; | ||
| 2829 | return Type.initPayload(&type_payload.base); | ||
| 2830 | } | ||
| 2831 | |||
| 2832 | pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void { | ||
| 2833 | const zir_module = scope.namespace(); | ||
| 2834 | const source = zir_module.getSource(self) catch @panic("dumpInst failed to get source"); | ||
| 2835 | const loc = std.zig.findLineColumn(source, inst.src); | ||
| 2836 | if (inst.tag == .constant) { | ||
| 2837 | std.debug.print("constant ty={} val={} src={}:{}:{}\n", .{ | ||
| 2838 | inst.ty, | ||
| 2839 | inst.castTag(.constant).?.val, | ||
| 2840 | zir_module.subFilePath(), | ||
| 2841 | loc.line + 1, | ||
| 2842 | loc.column + 1, | ||
| 2843 | }); | ||
| 2844 | } else if (inst.deaths == 0) { | ||
| 2845 | std.debug.print("{} ty={} src={}:{}:{}\n", .{ | ||
| 2846 | @tagName(inst.tag), | ||
| 2847 | inst.ty, | ||
| 2848 | zir_module.subFilePath(), | ||
| 2849 | loc.line + 1, | ||
| 2850 | loc.column + 1, | ||
| 2851 | }); | ||
| 2852 | } else { | ||
| 2853 | std.debug.print("{} ty={} deaths={b} src={}:{}:{}\n", .{ | ||
| 2854 | @tagName(inst.tag), | ||
| 2855 | inst.ty, | ||
| 2856 | inst.deaths, | ||
| 2857 | zir_module.subFilePath(), | ||
| 2858 | loc.line + 1, | ||
| 2859 | loc.column + 1, | ||
| 2860 | }); | ||
| 2861 | } | ||
| 2862 | } |
src-self-hosted/Package.zig+11-5| ... | @@ -1,8 +1,11 @@ | ... | @@ -1,8 +1,11 @@ |
| 1 | pub const Table = std.StringHashMap(*Package); | 1 | pub const Table = std.StringHashMap(*Package); |
| 2 | 2 | ||
| 3 | /// This should be used for file operations. | ||
| 3 | root_src_dir: std.fs.Dir, | 4 | root_src_dir: std.fs.Dir, |
| 4 | /// Relative to `root_src_dir`. | 5 | /// This is for metadata purposes, for example putting into debug information. |
| 5 | root_src_path: []const u8, | 6 | root_src_dir_path: []u8, |
| 7 | /// Relative to `root_src_dir` and `root_src_dir_path`. | ||
| 8 | root_src_path: []u8, | ||
| 6 | table: Table, | 9 | table: Table, |
| 7 | 10 | ||
| 8 | /// No references to `root_src_dir` and `root_src_path` are kept. | 11 | /// No references to `root_src_dir` and `root_src_path` are kept. |
| ... | @@ -18,8 +21,11 @@ pub fn create( | ... | @@ -18,8 +21,11 @@ pub fn create( |
| 18 | errdefer allocator.destroy(ptr); | 21 | errdefer allocator.destroy(ptr); |
| 19 | const root_src_path_dupe = try mem.dupe(allocator, u8, root_src_path); | 22 | const root_src_path_dupe = try mem.dupe(allocator, u8, root_src_path); |
| 20 | errdefer allocator.free(root_src_path_dupe); | 23 | errdefer allocator.free(root_src_path_dupe); |
| 24 | const root_src_dir_path = try mem.dupe(allocator, u8, root_src_dir); | ||
| 25 | errdefer allocator.free(root_src_dir_path); | ||
| 21 | ptr.* = .{ | 26 | ptr.* = .{ |
| 22 | .root_src_dir = try base_dir.openDir(root_src_dir, .{}), | 27 | .root_src_dir = try base_dir.openDir(root_src_dir, .{}), |
| 28 | .root_src_dir_path = root_src_dir_path, | ||
| 23 | .root_src_path = root_src_path_dupe, | 29 | .root_src_path = root_src_path_dupe, |
| 24 | .table = Table.init(allocator), | 30 | .table = Table.init(allocator), |
| 25 | }; | 31 | }; |
| ... | @@ -30,6 +36,7 @@ pub fn destroy(self: *Package) void { | ... | @@ -30,6 +36,7 @@ pub fn destroy(self: *Package) void { |
| 30 | const allocator = self.table.allocator; | 36 | const allocator = self.table.allocator; |
| 31 | self.root_src_dir.close(); | 37 | self.root_src_dir.close(); |
| 32 | allocator.free(self.root_src_path); | 38 | allocator.free(self.root_src_path); |
| 39 | allocator.free(self.root_src_dir_path); | ||
| 33 | { | 40 | { |
| 34 | var it = self.table.iterator(); | 41 | var it = self.table.iterator(); |
| 35 | while (it.next()) |kv| { | 42 | while (it.next()) |kv| { |
| ... | @@ -41,10 +48,9 @@ pub fn destroy(self: *Package) void { | ... | @@ -41,10 +48,9 @@ pub fn destroy(self: *Package) void { |
| 41 | } | 48 | } |
| 42 | 49 | ||
| 43 | pub fn add(self: *Package, name: []const u8, package: *Package) !void { | 50 | pub fn add(self: *Package, name: []const u8, package: *Package) !void { |
| 51 | try self.table.ensureCapacity(self.table.items().len + 1); | ||
| 44 | const name_dupe = try mem.dupe(self.table.allocator, u8, name); | 52 | const name_dupe = try mem.dupe(self.table.allocator, u8, name); |
| 45 | errdefer self.table.allocator.deinit(name_dupe); | 53 | self.table.putAssumeCapacityNoClobber(name_dupe, package); |
| 46 | const entry = try self.table.put(name_dupe, package); | ||
| 47 | assert(entry == null); | ||
| 48 | } | 54 | } |
| 49 | 55 | ||
| 50 | const std = @import("std"); | 56 | const std = @import("std"); |
src-self-hosted/astgen.zig+287-102| ... | @@ -17,6 +17,9 @@ pub const ResultLoc = union(enum) { | ... | @@ -17,6 +17,9 @@ pub const ResultLoc = union(enum) { |
| 17 | discard, | 17 | discard, |
| 18 | /// The expression has an inferred type, and it will be evaluated as an rvalue. | 18 | /// The expression has an inferred type, and it will be evaluated as an rvalue. |
| 19 | none, | 19 | none, |
| 20 | /// The expression must generate a pointer rather than a value. For example, the left hand side | ||
| 21 | /// of an assignment uses an "LValue" result location. | ||
| 22 | lvalue, | ||
| 20 | /// The expression will be type coerced into this type, but it will be evaluated as an rvalue. | 23 | /// The expression will be type coerced into this type, but it will be evaluated as an rvalue. |
| 21 | ty: *zir.Inst, | 24 | ty: *zir.Inst, |
| 22 | /// The expression must store its result into this typed pointer. | 25 | /// The expression must store its result into this typed pointer. |
| ... | @@ -33,7 +36,7 @@ pub const ResultLoc = union(enum) { | ... | @@ -33,7 +36,7 @@ pub const ResultLoc = union(enum) { |
| 33 | 36 | ||
| 34 | pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*zir.Inst { | 37 | pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*zir.Inst { |
| 35 | const type_src = scope.tree().token_locs[type_node.firstToken()].start; | 38 | const type_src = scope.tree().token_locs[type_node.firstToken()].start; |
| 36 | const type_type = try mod.addZIRInstConst(scope, type_src, .{ | 39 | const type_type = try addZIRInstConst(mod, scope, type_src, .{ |
| 37 | .ty = Type.initTag(.type), | 40 | .ty = Type.initTag(.type), |
| 38 | .val = Value.initTag(.type_type), | 41 | .val = Value.initTag(.type_type), |
| 39 | }); | 42 | }); |
| ... | @@ -46,18 +49,45 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr | ... | @@ -46,18 +49,45 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr |
| 46 | switch (node.tag) { | 49 | switch (node.tag) { |
| 47 | .VarDecl => unreachable, // Handled in `blockExpr`. | 50 | .VarDecl => unreachable, // Handled in `blockExpr`. |
| 48 | .Assign => unreachable, // Handled in `blockExpr`. | 51 | .Assign => unreachable, // Handled in `blockExpr`. |
| 49 | 52 | .AssignBitAnd => unreachable, // Handled in `blockExpr`. | |
| 50 | .Add => return arithmetic(mod, scope, rl, node.castTag(.Add).?, .add), | 53 | .AssignBitOr => unreachable, // Handled in `blockExpr`. |
| 51 | .Sub => return arithmetic(mod, scope, rl, node.castTag(.Sub).?, .sub), | 54 | .AssignBitShiftLeft => unreachable, // Handled in `blockExpr`. |
| 52 | 55 | .AssignBitShiftRight => unreachable, // Handled in `blockExpr`. | |
| 53 | .BangEqual => return cmp(mod, scope, rl, node.castTag(.BangEqual).?, .cmp_neq), | 56 | .AssignBitXor => unreachable, // Handled in `blockExpr`. |
| 54 | .EqualEqual => return cmp(mod, scope, rl, node.castTag(.EqualEqual).?, .cmp_eq), | 57 | .AssignDiv => unreachable, // Handled in `blockExpr`. |
| 55 | .GreaterThan => return cmp(mod, scope, rl, node.castTag(.GreaterThan).?, .cmp_gt), | 58 | .AssignSub => unreachable, // Handled in `blockExpr`. |
| 56 | .GreaterOrEqual => return cmp(mod, scope, rl, node.castTag(.GreaterOrEqual).?, .cmp_gte), | 59 | .AssignSubWrap => unreachable, // Handled in `blockExpr`. |
| 57 | .LessThan => return cmp(mod, scope, rl, node.castTag(.LessThan).?, .cmp_lt), | 60 | .AssignMod => unreachable, // Handled in `blockExpr`. |
| 58 | .LessOrEqual => return cmp(mod, scope, rl, node.castTag(.LessOrEqual).?, .cmp_lte), | 61 | .AssignAdd => unreachable, // Handled in `blockExpr`. |
| 59 | 62 | .AssignAddWrap => unreachable, // Handled in `blockExpr`. | |
| 60 | .Identifier => return rlWrap(mod, scope, rl, try identifier(mod, scope, node.castTag(.Identifier).?)), | 63 | .AssignMul => unreachable, // Handled in `blockExpr`. |
| 64 | .AssignMulWrap => unreachable, // Handled in `blockExpr`. | ||
| 65 | |||
| 66 | .Add => return simpleBinOp(mod, scope, rl, node.castTag(.Add).?, .add), | ||
| 67 | .AddWrap => return simpleBinOp(mod, scope, rl, node.castTag(.AddWrap).?, .addwrap), | ||
| 68 | .Sub => return simpleBinOp(mod, scope, rl, node.castTag(.Sub).?, .sub), | ||
| 69 | .SubWrap => return simpleBinOp(mod, scope, rl, node.castTag(.SubWrap).?, .subwrap), | ||
| 70 | .Mul => return simpleBinOp(mod, scope, rl, node.castTag(.Mul).?, .mul), | ||
| 71 | .MulWrap => return simpleBinOp(mod, scope, rl, node.castTag(.MulWrap).?, .mulwrap), | ||
| 72 | .Div => return simpleBinOp(mod, scope, rl, node.castTag(.Div).?, .div), | ||
| 73 | .Mod => return simpleBinOp(mod, scope, rl, node.castTag(.Mod).?, .mod_rem), | ||
| 74 | .BitAnd => return simpleBinOp(mod, scope, rl, node.castTag(.BitAnd).?, .bitand), | ||
| 75 | .BitOr => return simpleBinOp(mod, scope, rl, node.castTag(.BitOr).?, .bitor), | ||
| 76 | .BitShiftLeft => return simpleBinOp(mod, scope, rl, node.castTag(.BitShiftLeft).?, .shl), | ||
| 77 | .BitShiftRight => return simpleBinOp(mod, scope, rl, node.castTag(.BitShiftRight).?, .shr), | ||
| 78 | .BitXor => return simpleBinOp(mod, scope, rl, node.castTag(.BitXor).?, .xor), | ||
| 79 | |||
| 80 | .BangEqual => return simpleBinOp(mod, scope, rl, node.castTag(.BangEqual).?, .cmp_neq), | ||
| 81 | .EqualEqual => return simpleBinOp(mod, scope, rl, node.castTag(.EqualEqual).?, .cmp_eq), | ||
| 82 | .GreaterThan => return simpleBinOp(mod, scope, rl, node.castTag(.GreaterThan).?, .cmp_gt), | ||
| 83 | .GreaterOrEqual => return simpleBinOp(mod, scope, rl, node.castTag(.GreaterOrEqual).?, .cmp_gte), | ||
| 84 | .LessThan => return simpleBinOp(mod, scope, rl, node.castTag(.LessThan).?, .cmp_lt), | ||
| 85 | .LessOrEqual => return simpleBinOp(mod, scope, rl, node.castTag(.LessOrEqual).?, .cmp_lte), | ||
| 86 | |||
| 87 | .ArrayCat => return simpleBinOp(mod, scope, rl, node.castTag(.ArrayCat).?, .array_cat), | ||
| 88 | .ArrayMult => return simpleBinOp(mod, scope, rl, node.castTag(.ArrayMult).?, .array_mul), | ||
| 89 | |||
| 90 | .Identifier => return try identifier(mod, scope, rl, node.castTag(.Identifier).?), | ||
| 61 | .Asm => return rlWrap(mod, scope, rl, try assembly(mod, scope, node.castTag(.Asm).?)), | 91 | .Asm => return rlWrap(mod, scope, rl, try assembly(mod, scope, node.castTag(.Asm).?)), |
| 62 | .StringLiteral => return rlWrap(mod, scope, rl, try stringLiteral(mod, scope, node.castTag(.StringLiteral).?)), | 92 | .StringLiteral => return rlWrap(mod, scope, rl, try stringLiteral(mod, scope, node.castTag(.StringLiteral).?)), |
| 63 | .IntegerLiteral => return rlWrap(mod, scope, rl, try integerLiteral(mod, scope, node.castTag(.IntegerLiteral).?)), | 93 | .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 | ... | @@ -90,6 +120,8 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block |
| 90 | 120 | ||
| 91 | var scope = parent_scope; | 121 | var scope = parent_scope; |
| 92 | for (block_node.statements()) |statement| { | 122 | for (block_node.statements()) |statement| { |
| 123 | const src = scope.tree().token_locs[statement.firstToken()].start; | ||
| 124 | _ = try addZIRNoOp(mod, scope, src, .dbg_stmt); | ||
| 93 | switch (statement.tag) { | 125 | switch (statement.tag) { |
| 94 | .VarDecl => { | 126 | .VarDecl => { |
| 95 | const var_decl_node = statement.castTag(.VarDecl).?; | 127 | const var_decl_node = statement.castTag(.VarDecl).?; |
| ... | @@ -99,10 +131,25 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block | ... | @@ -99,10 +131,25 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block |
| 99 | const ass = statement.castTag(.Assign).?; | 131 | const ass = statement.castTag(.Assign).?; |
| 100 | try assign(mod, scope, ass); | 132 | try assign(mod, scope, ass); |
| 101 | }, | 133 | }, |
| 134 | .AssignBitAnd => try assignOp(mod, scope, statement.castTag(.AssignBitAnd).?, .bitand), | ||
| 135 | .AssignBitOr => try assignOp(mod, scope, statement.castTag(.AssignBitOr).?, .bitor), | ||
| 136 | .AssignBitShiftLeft => try assignOp(mod, scope, statement.castTag(.AssignBitShiftLeft).?, .shl), | ||
| 137 | .AssignBitShiftRight => try assignOp(mod, scope, statement.castTag(.AssignBitShiftRight).?, .shr), | ||
| 138 | .AssignBitXor => try assignOp(mod, scope, statement.castTag(.AssignBitXor).?, .xor), | ||
| 139 | .AssignDiv => try assignOp(mod, scope, statement.castTag(.AssignDiv).?, .div), | ||
| 140 | .AssignSub => try assignOp(mod, scope, statement.castTag(.AssignSub).?, .sub), | ||
| 141 | .AssignSubWrap => try assignOp(mod, scope, statement.castTag(.AssignSubWrap).?, .subwrap), | ||
| 142 | .AssignMod => try assignOp(mod, scope, statement.castTag(.AssignMod).?, .mod_rem), | ||
| 143 | .AssignAdd => try assignOp(mod, scope, statement.castTag(.AssignAdd).?, .add), | ||
| 144 | .AssignAddWrap => try assignOp(mod, scope, statement.castTag(.AssignAddWrap).?, .addwrap), | ||
| 145 | .AssignMul => try assignOp(mod, scope, statement.castTag(.AssignMul).?, .mul), | ||
| 146 | .AssignMulWrap => try assignOp(mod, scope, statement.castTag(.AssignMulWrap).?, .mulwrap), | ||
| 147 | |||
| 102 | else => { | 148 | else => { |
| 103 | const possibly_unused_result = try expr(mod, scope, .none, statement); | 149 | const possibly_unused_result = try expr(mod, scope, .none, statement); |
| 104 | const src = scope.tree().token_locs[statement.firstToken()].start; | 150 | if (!possibly_unused_result.tag.isNoReturn()) { |
| 105 | _ = try mod.addZIRUnOp(scope, src, .ensure_result_used, possibly_unused_result); | 151 | _ = try addZIRUnOp(mod, scope, src, .ensure_result_used, possibly_unused_result); |
| 152 | } | ||
| 106 | }, | 153 | }, |
| 107 | } | 154 | } |
| 108 | } | 155 | } |
| ... | @@ -133,7 +180,7 @@ fn varDecl( | ... | @@ -133,7 +180,7 @@ fn varDecl( |
| 133 | if (nodeMayNeedMemoryLocation(init_node)) { | 180 | if (nodeMayNeedMemoryLocation(init_node)) { |
| 134 | if (node.getTrailer("type_node")) |type_node| { | 181 | if (node.getTrailer("type_node")) |type_node| { |
| 135 | const type_inst = try typeExpr(mod, scope, type_node); | 182 | const type_inst = try typeExpr(mod, scope, type_node); |
| 136 | const alloc = try mod.addZIRUnOp(scope, name_src, .alloc, type_inst); | 183 | const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst); |
| 137 | const result_loc: ResultLoc = .{ .ptr = alloc }; | 184 | const result_loc: ResultLoc = .{ .ptr = alloc }; |
| 138 | const init_inst = try expr(mod, scope, result_loc, init_node); | 185 | const init_inst = try expr(mod, scope, result_loc, init_node); |
| 139 | const sub_scope = try block_arena.create(Scope.LocalVal); | 186 | const sub_scope = try block_arena.create(Scope.LocalVal); |
| ... | @@ -145,7 +192,7 @@ fn varDecl( | ... | @@ -145,7 +192,7 @@ fn varDecl( |
| 145 | }; | 192 | }; |
| 146 | return &sub_scope.base; | 193 | return &sub_scope.base; |
| 147 | } else { | 194 | } else { |
| 148 | const alloc = try mod.addZIRNoOpT(scope, name_src, .alloc_inferred); | 195 | const alloc = try addZIRNoOpT(mod, scope, name_src, .alloc_inferred); |
| 149 | const result_loc: ResultLoc = .{ .inferred_ptr = alloc }; | 196 | const result_loc: ResultLoc = .{ .inferred_ptr = alloc }; |
| 150 | const init_inst = try expr(mod, scope, result_loc, init_node); | 197 | const init_inst = try expr(mod, scope, result_loc, init_node); |
| 151 | const sub_scope = try block_arena.create(Scope.LocalVal); | 198 | const sub_scope = try block_arena.create(Scope.LocalVal); |
| ... | @@ -176,7 +223,7 @@ fn varDecl( | ... | @@ -176,7 +223,7 @@ fn varDecl( |
| 176 | .Keyword_var => { | 223 | .Keyword_var => { |
| 177 | if (node.getTrailer("type_node")) |type_node| { | 224 | if (node.getTrailer("type_node")) |type_node| { |
| 178 | const type_inst = try typeExpr(mod, scope, type_node); | 225 | const type_inst = try typeExpr(mod, scope, type_node); |
| 179 | const alloc = try mod.addZIRUnOp(scope, name_src, .alloc, type_inst); | 226 | const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst); |
| 180 | const result_loc: ResultLoc = .{ .ptr = alloc }; | 227 | const result_loc: ResultLoc = .{ .ptr = alloc }; |
| 181 | const init_inst = try expr(mod, scope, result_loc, init_node); | 228 | const init_inst = try expr(mod, scope, result_loc, init_node); |
| 182 | const sub_scope = try block_arena.create(Scope.LocalPtr); | 229 | const sub_scope = try block_arena.create(Scope.LocalPtr); |
| ... | @@ -188,7 +235,7 @@ fn varDecl( | ... | @@ -188,7 +235,7 @@ fn varDecl( |
| 188 | }; | 235 | }; |
| 189 | return &sub_scope.base; | 236 | return &sub_scope.base; |
| 190 | } else { | 237 | } else { |
| 191 | const alloc = try mod.addZIRNoOp(scope, name_src, .alloc_inferred); | 238 | const alloc = try addZIRNoOp(mod, scope, name_src, .alloc_inferred); |
| 192 | const result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred).? }; | 239 | const result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred).? }; |
| 193 | const init_inst = try expr(mod, scope, result_loc, init_node); | 240 | const init_inst = try expr(mod, scope, result_loc, init_node); |
| 194 | const sub_scope = try block_arena.create(Scope.LocalPtr); | 241 | const sub_scope = try block_arena.create(Scope.LocalPtr); |
| ... | @@ -207,28 +254,44 @@ fn varDecl( | ... | @@ -207,28 +254,44 @@ fn varDecl( |
| 207 | 254 | ||
| 208 | fn assign(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) InnerError!void { | 255 | fn assign(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) InnerError!void { |
| 209 | if (infix_node.lhs.castTag(.Identifier)) |ident| { | 256 | if (infix_node.lhs.castTag(.Identifier)) |ident| { |
| 210 | const tree = scope.tree(); | 257 | // This intentionally does not support @"_" syntax. |
| 211 | const ident_name = try identifierTokenString(mod, scope, ident.token); | 258 | const ident_name = scope.tree().tokenSlice(ident.token); |
| 212 | if (std.mem.eql(u8, ident_name, "_")) { | 259 | if (std.mem.eql(u8, ident_name, "_")) { |
| 213 | _ = try expr(mod, scope, .discard, infix_node.rhs); | 260 | _ = try expr(mod, scope, .discard, infix_node.rhs); |
| 214 | return; | 261 | return; |
| 215 | } else { | ||
| 216 | return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); | ||
| 217 | } | 262 | } |
| 218 | } else { | ||
| 219 | return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); | ||
| 220 | } | 263 | } |
| 264 | const lvalue = try expr(mod, scope, .lvalue, infix_node.lhs); | ||
| 265 | _ = try expr(mod, scope, .{ .ptr = lvalue }, infix_node.rhs); | ||
| 266 | } | ||
| 267 | |||
| 268 | fn assignOp( | ||
| 269 | mod: *Module, | ||
| 270 | scope: *Scope, | ||
| 271 | infix_node: *ast.Node.SimpleInfixOp, | ||
| 272 | op_inst_tag: zir.Inst.Tag, | ||
| 273 | ) InnerError!void { | ||
| 274 | const lhs_ptr = try expr(mod, scope, .lvalue, infix_node.lhs); | ||
| 275 | const lhs = try addZIRUnOp(mod, scope, lhs_ptr.src, .deref, lhs_ptr); | ||
| 276 | const lhs_type = try addZIRUnOp(mod, scope, lhs_ptr.src, .typeof, lhs); | ||
| 277 | const rhs = try expr(mod, scope, .{ .ty = lhs_type }, infix_node.rhs); | ||
| 278 | |||
| 279 | const tree = scope.tree(); | ||
| 280 | const src = tree.token_locs[infix_node.op_token].start; | ||
| 281 | |||
| 282 | const result = try addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs); | ||
| 283 | _ = try addZIRBinOp(mod, scope, src, .store, lhs_ptr, result); | ||
| 221 | } | 284 | } |
| 222 | 285 | ||
| 223 | fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { | 286 | fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { |
| 224 | const tree = scope.tree(); | 287 | const tree = scope.tree(); |
| 225 | const src = tree.token_locs[node.op_token].start; | 288 | const src = tree.token_locs[node.op_token].start; |
| 226 | const bool_type = try mod.addZIRInstConst(scope, src, .{ | 289 | const bool_type = try addZIRInstConst(mod, scope, src, .{ |
| 227 | .ty = Type.initTag(.type), | 290 | .ty = Type.initTag(.type), |
| 228 | .val = Value.initTag(.bool_type), | 291 | .val = Value.initTag(.bool_type), |
| 229 | }); | 292 | }); |
| 230 | const operand = try expr(mod, scope, .{ .ty = bool_type }, node.rhs); | 293 | const operand = try expr(mod, scope, .{ .ty = bool_type }, node.rhs); |
| 231 | return mod.addZIRUnOp(scope, src, .boolnot, operand); | 294 | return addZIRUnOp(mod, scope, src, .boolnot, operand); |
| 232 | } | 295 | } |
| 233 | 296 | ||
| 234 | /// Identifier token -> String (allocated in scope.arena()) | 297 | /// Identifier token -> String (allocated in scope.arena()) |
| ... | @@ -257,7 +320,7 @@ pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToke | ... | @@ -257,7 +320,7 @@ pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToke |
| 257 | 320 | ||
| 258 | const ident_name = try identifierTokenString(mod, scope, node.token); | 321 | const ident_name = try identifierTokenString(mod, scope, node.token); |
| 259 | 322 | ||
| 260 | return mod.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{}); | 323 | return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{}); |
| 261 | } | 324 | } |
| 262 | 325 | ||
| 263 | fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { | 326 | 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! | ... | @@ -268,47 +331,31 @@ fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError! |
| 268 | const lhs = try expr(mod, scope, .none, node.lhs); | 331 | const lhs = try expr(mod, scope, .none, node.lhs); |
| 269 | const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?); | 332 | const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?); |
| 270 | 333 | ||
| 271 | const pointer = try mod.addZIRInst(scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{}); | 334 | const pointer = try addZIRInst(mod, scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{}); |
| 272 | return mod.addZIRUnOp(scope, src, .deref, pointer); | 335 | return addZIRUnOp(mod, scope, src, .deref, pointer); |
| 273 | } | 336 | } |
| 274 | 337 | ||
| 275 | fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst { | 338 | fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst { |
| 276 | const tree = scope.tree(); | 339 | const tree = scope.tree(); |
| 277 | const src = tree.token_locs[node.rtoken].start; | 340 | const src = tree.token_locs[node.rtoken].start; |
| 278 | const lhs = try expr(mod, scope, .none, node.lhs); | 341 | const lhs = try expr(mod, scope, .none, node.lhs); |
| 279 | return mod.addZIRUnOp(scope, src, .deref, lhs); | 342 | return addZIRUnOp(mod, scope, src, .deref, lhs); |
| 280 | } | 343 | } |
| 281 | 344 | ||
| 282 | fn cmp( | 345 | fn simpleBinOp( |
| 283 | mod: *Module, | 346 | mod: *Module, |
| 284 | scope: *Scope, | 347 | scope: *Scope, |
| 285 | rl: ResultLoc, | 348 | rl: ResultLoc, |
| 286 | infix_node: *ast.Node.SimpleInfixOp, | 349 | infix_node: *ast.Node.SimpleInfixOp, |
| 287 | cmp_inst_tag: zir.Inst.Tag, | 350 | op_inst_tag: zir.Inst.Tag, |
| 288 | ) InnerError!*zir.Inst { | 351 | ) InnerError!*zir.Inst { |
| 289 | const tree = scope.tree(); | 352 | const tree = scope.tree(); |
| 290 | const src = tree.token_locs[infix_node.op_token].start; | 353 | const src = tree.token_locs[infix_node.op_token].start; |
| 291 | 354 | ||
| 292 | const lhs = try expr(mod, scope, .none, infix_node.lhs); | ||
| 293 | const rhs = try expr(mod, scope, .none, infix_node.rhs); | ||
| 294 | const result = try mod.addZIRBinOp(scope, src, cmp_inst_tag, lhs, rhs); | ||
| 295 | return rlWrap(mod, scope, rl, result); | ||
| 296 | } | ||
| 297 | |||
| 298 | fn arithmetic( | ||
| 299 | mod: *Module, | ||
| 300 | scope: *Scope, | ||
| 301 | rl: ResultLoc, | ||
| 302 | infix_node: *ast.Node.SimpleInfixOp, | ||
| 303 | op_inst_tag: zir.Inst.Tag, | ||
| 304 | ) InnerError!*zir.Inst { | ||
| 305 | const lhs = try expr(mod, scope, .none, infix_node.lhs); | 355 | const lhs = try expr(mod, scope, .none, infix_node.lhs); |
| 306 | const rhs = try expr(mod, scope, .none, infix_node.rhs); | 356 | const rhs = try expr(mod, scope, .none, infix_node.rhs); |
| 307 | 357 | ||
| 308 | const tree = scope.tree(); | 358 | const result = try addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs); |
| 309 | const src = tree.token_locs[infix_node.op_token].start; | ||
| 310 | |||
| 311 | const result = try mod.addZIRBinOp(scope, src, op_inst_tag, lhs, rhs); | ||
| 312 | return rlWrap(mod, scope, rl, result); | 359 | return rlWrap(mod, scope, rl, result); |
| 313 | } | 360 | } |
| 314 | 361 | ||
| ... | @@ -331,19 +378,19 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn | ... | @@ -331,19 +378,19 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 331 | 378 | ||
| 332 | const tree = scope.tree(); | 379 | const tree = scope.tree(); |
| 333 | const if_src = tree.token_locs[if_node.if_token].start; | 380 | const if_src = tree.token_locs[if_node.if_token].start; |
| 334 | const bool_type = try mod.addZIRInstConst(scope, if_src, .{ | 381 | const bool_type = try addZIRInstConst(mod, scope, if_src, .{ |
| 335 | .ty = Type.initTag(.type), | 382 | .ty = Type.initTag(.type), |
| 336 | .val = Value.initTag(.bool_type), | 383 | .val = Value.initTag(.bool_type), |
| 337 | }); | 384 | }); |
| 338 | const cond = try expr(mod, &block_scope.base, .{ .ty = bool_type }, if_node.condition); | 385 | const cond = try expr(mod, &block_scope.base, .{ .ty = bool_type }, if_node.condition); |
| 339 | 386 | ||
| 340 | const condbr = try mod.addZIRInstSpecial(&block_scope.base, if_src, zir.Inst.CondBr, .{ | 387 | const condbr = try addZIRInstSpecial(mod, &block_scope.base, if_src, zir.Inst.CondBr, .{ |
| 341 | .condition = cond, | 388 | .condition = cond, |
| 342 | .then_body = undefined, // populated below | 389 | .then_body = undefined, // populated below |
| 343 | .else_body = undefined, // populated below | 390 | .else_body = undefined, // populated below |
| 344 | }, .{}); | 391 | }, .{}); |
| 345 | 392 | ||
| 346 | const block = try mod.addZIRInstBlock(scope, if_src, .{ | 393 | const block = try addZIRInstBlock(mod, scope, if_src, .{ |
| 347 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), | 394 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), |
| 348 | }); | 395 | }); |
| 349 | var then_scope: Scope.GenZIR = .{ | 396 | var then_scope: Scope.GenZIR = .{ |
| ... | @@ -359,14 +406,14 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn | ... | @@ -359,14 +406,14 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 359 | // proper type inference requires peer type resolution on the if's | 406 | // proper type inference requires peer type resolution on the if's |
| 360 | // branches. | 407 | // branches. |
| 361 | const branch_rl: ResultLoc = switch (rl) { | 408 | const branch_rl: ResultLoc = switch (rl) { |
| 362 | .discard, .none, .ty, .ptr => rl, | 409 | .discard, .none, .ty, .ptr, .lvalue => rl, |
| 363 | .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, | 410 | .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, |
| 364 | }; | 411 | }; |
| 365 | 412 | ||
| 366 | const then_result = try expr(mod, &then_scope.base, branch_rl, if_node.body); | 413 | const then_result = try expr(mod, &then_scope.base, branch_rl, if_node.body); |
| 367 | if (!then_result.tag.isNoReturn()) { | 414 | if (!then_result.tag.isNoReturn()) { |
| 368 | const then_src = tree.token_locs[if_node.body.lastToken()].start; | 415 | const then_src = tree.token_locs[if_node.body.lastToken()].start; |
| 369 | _ = try mod.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ | 416 | _ = try addZIRInst(mod, &then_scope.base, then_src, zir.Inst.Break, .{ |
| 370 | .block = block, | 417 | .block = block, |
| 371 | .operand = then_result, | 418 | .operand = then_result, |
| 372 | }, .{}); | 419 | }, .{}); |
| ... | @@ -387,7 +434,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn | ... | @@ -387,7 +434,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 387 | const else_result = try expr(mod, &else_scope.base, branch_rl, else_node.body); | 434 | const else_result = try expr(mod, &else_scope.base, branch_rl, else_node.body); |
| 388 | if (!else_result.tag.isNoReturn()) { | 435 | if (!else_result.tag.isNoReturn()) { |
| 389 | const else_src = tree.token_locs[else_node.body.lastToken()].start; | 436 | const else_src = tree.token_locs[else_node.body.lastToken()].start; |
| 390 | _ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ | 437 | _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.Break, .{ |
| 391 | .block = block, | 438 | .block = block, |
| 392 | .operand = else_result, | 439 | .operand = else_result, |
| 393 | }, .{}); | 440 | }, .{}); |
| ... | @@ -396,7 +443,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn | ... | @@ -396,7 +443,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 396 | // TODO Optimization opportunity: we can avoid an allocation and a memcpy here | 443 | // TODO Optimization opportunity: we can avoid an allocation and a memcpy here |
| 397 | // by directly allocating the body for this one instruction. | 444 | // by directly allocating the body for this one instruction. |
| 398 | const else_src = tree.token_locs[if_node.lastToken()].start; | 445 | const else_src = tree.token_locs[if_node.lastToken()].start; |
| 399 | _ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.BreakVoid, .{ | 446 | _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.BreakVoid, .{ |
| 400 | .block = block, | 447 | .block = block, |
| 401 | }, .{}); | 448 | }, .{}); |
| 402 | } | 449 | } |
| ... | @@ -412,20 +459,20 @@ fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerE | ... | @@ -412,20 +459,20 @@ fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerE |
| 412 | const src = tree.token_locs[cfe.ltoken].start; | 459 | const src = tree.token_locs[cfe.ltoken].start; |
| 413 | if (cfe.getRHS()) |rhs_node| { | 460 | if (cfe.getRHS()) |rhs_node| { |
| 414 | if (nodeMayNeedMemoryLocation(rhs_node)) { | 461 | if (nodeMayNeedMemoryLocation(rhs_node)) { |
| 415 | const ret_ptr = try mod.addZIRNoOp(scope, src, .ret_ptr); | 462 | const ret_ptr = try addZIRNoOp(mod, scope, src, .ret_ptr); |
| 416 | const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node); | 463 | const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node); |
| 417 | return mod.addZIRUnOp(scope, src, .@"return", operand); | 464 | return addZIRUnOp(mod, scope, src, .@"return", operand); |
| 418 | } else { | 465 | } else { |
| 419 | const fn_ret_ty = try mod.addZIRNoOp(scope, src, .ret_type); | 466 | const fn_ret_ty = try addZIRNoOp(mod, scope, src, .ret_type); |
| 420 | const operand = try expr(mod, scope, .{ .ty = fn_ret_ty }, rhs_node); | 467 | const operand = try expr(mod, scope, .{ .ty = fn_ret_ty }, rhs_node); |
| 421 | return mod.addZIRUnOp(scope, src, .@"return", operand); | 468 | return addZIRUnOp(mod, scope, src, .@"return", operand); |
| 422 | } | 469 | } |
| 423 | } else { | 470 | } else { |
| 424 | return mod.addZIRNoOp(scope, src, .returnvoid); | 471 | return addZIRNoOp(mod, scope, src, .returnvoid); |
| 425 | } | 472 | } |
| 426 | } | 473 | } |
| 427 | 474 | ||
| 428 | fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError!*zir.Inst { | 475 | fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneToken) InnerError!*zir.Inst { |
| 429 | const tracy = trace(@src()); | 476 | const tracy = trace(@src()); |
| 430 | defer tracy.end(); | 477 | defer tracy.end(); |
| 431 | 478 | ||
| ... | @@ -437,7 +484,8 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError | ... | @@ -437,7 +484,8 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError |
| 437 | } | 484 | } |
| 438 | 485 | ||
| 439 | if (getSimplePrimitiveValue(ident_name)) |typed_value| { | 486 | if (getSimplePrimitiveValue(ident_name)) |typed_value| { |
| 440 | return mod.addZIRInstConst(scope, src, typed_value); | 487 | const result = try addZIRInstConst(mod, scope, src, typed_value); |
| 488 | return rlWrap(mod, scope, rl, result); | ||
| 441 | } | 489 | } |
| 442 | 490 | ||
| 443 | if (ident_name.len >= 2) integer: { | 491 | if (ident_name.len >= 2) integer: { |
| ... | @@ -461,16 +509,18 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError | ... | @@ -461,16 +509,18 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError |
| 461 | else => { | 509 | else => { |
| 462 | const int_type_payload = try scope.arena().create(Value.Payload.IntType); | 510 | const int_type_payload = try scope.arena().create(Value.Payload.IntType); |
| 463 | int_type_payload.* = .{ .signed = is_signed, .bits = bit_count }; | 511 | int_type_payload.* = .{ .signed = is_signed, .bits = bit_count }; |
| 464 | return mod.addZIRInstConst(scope, src, .{ | 512 | const result = try addZIRInstConst(mod, scope, src, .{ |
| 465 | .ty = Type.initTag(.comptime_int), | 513 | .ty = Type.initTag(.comptime_int), |
| 466 | .val = Value.initPayload(&int_type_payload.base), | 514 | .val = Value.initPayload(&int_type_payload.base), |
| 467 | }); | 515 | }); |
| 516 | return rlWrap(mod, scope, rl, result); | ||
| 468 | }, | 517 | }, |
| 469 | }; | 518 | }; |
| 470 | return mod.addZIRInstConst(scope, src, .{ | 519 | const result = try addZIRInstConst(mod, scope, src, .{ |
| 471 | .ty = Type.initTag(.type), | 520 | .ty = Type.initTag(.type), |
| 472 | .val = val, | 521 | .val = val, |
| 473 | }); | 522 | }); |
| 523 | return rlWrap(mod, scope, rl, result); | ||
| 474 | } | 524 | } |
| 475 | } | 525 | } |
| 476 | 526 | ||
| ... | @@ -481,14 +531,19 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError | ... | @@ -481,14 +531,19 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError |
| 481 | .local_val => { | 531 | .local_val => { |
| 482 | const local_val = s.cast(Scope.LocalVal).?; | 532 | const local_val = s.cast(Scope.LocalVal).?; |
| 483 | if (mem.eql(u8, local_val.name, ident_name)) { | 533 | if (mem.eql(u8, local_val.name, ident_name)) { |
| 484 | return local_val.inst; | 534 | return rlWrap(mod, scope, rl, local_val.inst); |
| 485 | } | 535 | } |
| 486 | s = local_val.parent; | 536 | s = local_val.parent; |
| 487 | }, | 537 | }, |
| 488 | .local_ptr => { | 538 | .local_ptr => { |
| 489 | const local_ptr = s.cast(Scope.LocalPtr).?; | 539 | const local_ptr = s.cast(Scope.LocalPtr).?; |
| 490 | if (mem.eql(u8, local_ptr.name, ident_name)) { | 540 | if (mem.eql(u8, local_ptr.name, ident_name)) { |
| 491 | return try mod.addZIRUnOp(scope, src, .deref, local_ptr.ptr); | 541 | if (rl == .lvalue) { |
| 542 | return local_ptr.ptr; | ||
| 543 | } else { | ||
| 544 | const result = try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr); | ||
| 545 | return rlWrap(mod, scope, rl, result); | ||
| 546 | } | ||
| 492 | } | 547 | } |
| 493 | s = local_ptr.parent; | 548 | s = local_ptr.parent; |
| 494 | }, | 549 | }, |
| ... | @@ -498,7 +553,9 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError | ... | @@ -498,7 +553,9 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError |
| 498 | } | 553 | } |
| 499 | 554 | ||
| 500 | if (mod.lookupDeclName(scope, ident_name)) |decl| { | 555 | if (mod.lookupDeclName(scope, ident_name)) |decl| { |
| 501 | return try mod.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); | 556 | // TODO handle lvalues |
| 557 | const result = try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); | ||
| 558 | return rlWrap(mod, scope, rl, result); | ||
| 502 | } | 559 | } |
| 503 | 560 | ||
| 504 | return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name}); | 561 | 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 | ... | @@ -520,7 +577,7 @@ fn stringLiteral(mod: *Module, scope: *Scope, str_lit: *ast.Node.OneToken) Inner |
| 520 | }; | 577 | }; |
| 521 | 578 | ||
| 522 | const src = tree.token_locs[str_lit.token].start; | 579 | const src = tree.token_locs[str_lit.token].start; |
| 523 | return mod.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); | 580 | return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); |
| 524 | } | 581 | } |
| 525 | 582 | ||
| 526 | fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) InnerError!*zir.Inst { | 583 | 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 | ... | @@ -545,7 +602,7 @@ fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) Inne |
| 545 | const int_payload = try arena.create(Value.Payload.Int_u64); | 602 | const int_payload = try arena.create(Value.Payload.Int_u64); |
| 546 | int_payload.* = .{ .int = small_int }; | 603 | int_payload.* = .{ .int = small_int }; |
| 547 | const src = tree.token_locs[int_lit.token].start; | 604 | const src = tree.token_locs[int_lit.token].start; |
| 548 | return mod.addZIRInstConst(scope, src, .{ | 605 | return addZIRInstConst(mod, scope, src, .{ |
| 549 | .ty = Type.initTag(.comptime_int), | 606 | .ty = Type.initTag(.comptime_int), |
| 550 | .val = Value.initPayload(&int_payload.base), | 607 | .val = Value.initPayload(&int_payload.base), |
| 551 | }); | 608 | }); |
| ... | @@ -568,7 +625,7 @@ fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.OneToken) Inne | ... | @@ -568,7 +625,7 @@ fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.OneToken) Inne |
| 568 | const float_payload = try arena.create(Value.Payload.Float_128); | 625 | const float_payload = try arena.create(Value.Payload.Float_128); |
| 569 | float_payload.* = .{ .val = val }; | 626 | float_payload.* = .{ .val = val }; |
| 570 | const src = tree.token_locs[float_lit.token].start; | 627 | const src = tree.token_locs[float_lit.token].start; |
| 571 | return mod.addZIRInstConst(scope, src, .{ | 628 | return addZIRInstConst(mod, scope, src, .{ |
| 572 | .ty = Type.initTag(.comptime_float), | 629 | .ty = Type.initTag(.comptime_float), |
| 573 | .val = Value.initPayload(&float_payload.base), | 630 | .val = Value.initPayload(&float_payload.base), |
| 574 | }); | 631 | }); |
| ... | @@ -578,7 +635,7 @@ fn undefLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerErro | ... | @@ -578,7 +635,7 @@ fn undefLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerErro |
| 578 | const arena = scope.arena(); | 635 | const arena = scope.arena(); |
| 579 | const tree = scope.tree(); | 636 | const tree = scope.tree(); |
| 580 | const src = tree.token_locs[node.token].start; | 637 | const src = tree.token_locs[node.token].start; |
| 581 | return mod.addZIRInstConst(scope, src, .{ | 638 | return addZIRInstConst(mod, scope, src, .{ |
| 582 | .ty = Type.initTag(.@"undefined"), | 639 | .ty = Type.initTag(.@"undefined"), |
| 583 | .val = Value.initTag(.undef), | 640 | .val = Value.initTag(.undef), |
| 584 | }); | 641 | }); |
| ... | @@ -588,7 +645,7 @@ fn boolLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError | ... | @@ -588,7 +645,7 @@ fn boolLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError |
| 588 | const arena = scope.arena(); | 645 | const arena = scope.arena(); |
| 589 | const tree = scope.tree(); | 646 | const tree = scope.tree(); |
| 590 | const src = tree.token_locs[node.token].start; | 647 | const src = tree.token_locs[node.token].start; |
| 591 | return mod.addZIRInstConst(scope, src, .{ | 648 | return addZIRInstConst(mod, scope, src, .{ |
| 592 | .ty = Type.initTag(.bool), | 649 | .ty = Type.initTag(.bool), |
| 593 | .val = switch (tree.token_ids[node.token]) { | 650 | .val = switch (tree.token_ids[node.token]) { |
| 594 | .Keyword_true => Value.initTag(.bool_true), | 651 | .Keyword_true => Value.initTag(.bool_true), |
| ... | @@ -602,7 +659,7 @@ fn nullLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError | ... | @@ -602,7 +659,7 @@ fn nullLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError |
| 602 | const arena = scope.arena(); | 659 | const arena = scope.arena(); |
| 603 | const tree = scope.tree(); | 660 | const tree = scope.tree(); |
| 604 | const src = tree.token_locs[node.token].start; | 661 | const src = tree.token_locs[node.token].start; |
| 605 | return mod.addZIRInstConst(scope, src, .{ | 662 | return addZIRInstConst(mod, scope, src, .{ |
| 606 | .ty = Type.initTag(.@"null"), | 663 | .ty = Type.initTag(.@"null"), |
| 607 | .val = Value.initTag(.null_value), | 664 | .val = Value.initTag(.null_value), |
| 608 | }); | 665 | }); |
| ... | @@ -620,7 +677,7 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi | ... | @@ -620,7 +677,7 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi |
| 620 | 677 | ||
| 621 | const src = tree.token_locs[asm_node.asm_token].start; | 678 | const src = tree.token_locs[asm_node.asm_token].start; |
| 622 | 679 | ||
| 623 | const str_type = try mod.addZIRInstConst(scope, src, .{ | 680 | const str_type = try addZIRInstConst(mod, scope, src, .{ |
| 624 | .ty = Type.initTag(.type), | 681 | .ty = Type.initTag(.type), |
| 625 | .val = Value.initTag(.const_slice_u8_type), | 682 | .val = Value.initTag(.const_slice_u8_type), |
| 626 | }); | 683 | }); |
| ... | @@ -632,11 +689,11 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi | ... | @@ -632,11 +689,11 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi |
| 632 | args[i] = try expr(mod, scope, .none, input.expr); | 689 | args[i] = try expr(mod, scope, .none, input.expr); |
| 633 | } | 690 | } |
| 634 | 691 | ||
| 635 | const return_type = try mod.addZIRInstConst(scope, src, .{ | 692 | const return_type = try addZIRInstConst(mod, scope, src, .{ |
| 636 | .ty = Type.initTag(.type), | 693 | .ty = Type.initTag(.type), |
| 637 | .val = Value.initTag(.void_type), | 694 | .val = Value.initTag(.void_type), |
| 638 | }); | 695 | }); |
| 639 | const asm_inst = try mod.addZIRInst(scope, src, zir.Inst.Asm, .{ | 696 | const asm_inst = try addZIRInst(mod, scope, src, zir.Inst.Asm, .{ |
| 640 | .asm_source = try expr(mod, scope, str_type_rl, asm_node.template), | 697 | .asm_source = try expr(mod, scope, str_type_rl, asm_node.template), |
| 641 | .return_type = return_type, | 698 | .return_type = return_type, |
| 642 | }, .{ | 699 | }, .{ |
| ... | @@ -666,14 +723,14 @@ fn simpleCast( | ... | @@ -666,14 +723,14 @@ fn simpleCast( |
| 666 | try ensureBuiltinParamCount(mod, scope, call, 2); | 723 | try ensureBuiltinParamCount(mod, scope, call, 2); |
| 667 | const tree = scope.tree(); | 724 | const tree = scope.tree(); |
| 668 | const src = tree.token_locs[call.builtin_token].start; | 725 | const src = tree.token_locs[call.builtin_token].start; |
| 669 | const type_type = try mod.addZIRInstConst(scope, src, .{ | 726 | const type_type = try addZIRInstConst(mod, scope, src, .{ |
| 670 | .ty = Type.initTag(.type), | 727 | .ty = Type.initTag(.type), |
| 671 | .val = Value.initTag(.type_type), | 728 | .val = Value.initTag(.type_type), |
| 672 | }); | 729 | }); |
| 673 | const params = call.params(); | 730 | const params = call.params(); |
| 674 | const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]); | 731 | const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]); |
| 675 | const rhs = try expr(mod, scope, .none, params[1]); | 732 | const rhs = try expr(mod, scope, .none, params[1]); |
| 676 | const result = try mod.addZIRBinOp(scope, src, inst_tag, dest_type, rhs); | 733 | const result = try addZIRBinOp(mod, scope, src, inst_tag, dest_type, rhs); |
| 677 | return rlWrap(mod, scope, rl, result); | 734 | return rlWrap(mod, scope, rl, result); |
| 678 | } | 735 | } |
| 679 | 736 | ||
| ... | @@ -682,7 +739,7 @@ fn ptrToInt(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError | ... | @@ -682,7 +739,7 @@ fn ptrToInt(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError |
| 682 | const operand = try expr(mod, scope, .none, call.params()[0]); | 739 | const operand = try expr(mod, scope, .none, call.params()[0]); |
| 683 | const tree = scope.tree(); | 740 | const tree = scope.tree(); |
| 684 | const src = tree.token_locs[call.builtin_token].start; | 741 | const src = tree.token_locs[call.builtin_token].start; |
| 685 | return mod.addZIRUnOp(scope, src, .ptrtoint, operand); | 742 | return addZIRUnOp(mod, scope, src, .ptrtoint, operand); |
| 686 | } | 743 | } |
| 687 | 744 | ||
| 688 | fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { | 745 | 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 | ... | @@ -695,15 +752,19 @@ fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) I |
| 695 | .none => return try expr(mod, scope, .{ .ty = dest_type }, params[1]), | 752 | .none => return try expr(mod, scope, .{ .ty = dest_type }, params[1]), |
| 696 | .discard => { | 753 | .discard => { |
| 697 | const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); | 754 | const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); |
| 698 | _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result); | 755 | _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); |
| 699 | return result; | 756 | return result; |
| 700 | }, | 757 | }, |
| 758 | .lvalue => { | ||
| 759 | const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); | ||
| 760 | return addZIRUnOp(mod, scope, result.src, .ref, result); | ||
| 761 | }, | ||
| 701 | .ty => |result_ty| { | 762 | .ty => |result_ty| { |
| 702 | const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); | 763 | const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); |
| 703 | return mod.addZIRBinOp(scope, src, .as, result_ty, result); | 764 | return addZIRBinOp(mod, scope, src, .as, result_ty, result); |
| 704 | }, | 765 | }, |
| 705 | .ptr => |result_ptr| { | 766 | .ptr => |result_ptr| { |
| 706 | const casted_result_ptr = try mod.addZIRBinOp(scope, src, .coerce_result_ptr, dest_type, result_ptr); | 767 | const casted_result_ptr = try addZIRBinOp(mod, scope, src, .coerce_result_ptr, dest_type, result_ptr); |
| 707 | return expr(mod, scope, .{ .ptr = casted_result_ptr }, params[1]); | 768 | return expr(mod, scope, .{ .ptr = casted_result_ptr }, params[1]); |
| 708 | }, | 769 | }, |
| 709 | .bitcasted_ptr => |bitcasted_ptr| { | 770 | .bitcasted_ptr => |bitcasted_ptr| { |
| ... | @@ -715,7 +776,7 @@ fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) I | ... | @@ -715,7 +776,7 @@ fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) I |
| 715 | return mod.failTok(scope, call.builtin_token, "TODO implement @as with inferred-type result location pointer", .{}); | 776 | return mod.failTok(scope, call.builtin_token, "TODO implement @as with inferred-type result location pointer", .{}); |
| 716 | }, | 777 | }, |
| 717 | .block_ptr => |block_ptr| { | 778 | .block_ptr => |block_ptr| { |
| 718 | const casted_block_ptr = try mod.addZIRInst(scope, src, zir.Inst.CoerceResultBlockPtr, .{ | 779 | const casted_block_ptr = try addZIRInst(mod, scope, src, zir.Inst.CoerceResultBlockPtr, .{ |
| 719 | .dest_type = dest_type, | 780 | .dest_type = dest_type, |
| 720 | .block = block_ptr, | 781 | .block = block_ptr, |
| 721 | }, .{}); | 782 | }, .{}); |
| ... | @@ -728,7 +789,7 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa | ... | @@ -728,7 +789,7 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa |
| 728 | try ensureBuiltinParamCount(mod, scope, call, 2); | 789 | try ensureBuiltinParamCount(mod, scope, call, 2); |
| 729 | const tree = scope.tree(); | 790 | const tree = scope.tree(); |
| 730 | const src = tree.token_locs[call.builtin_token].start; | 791 | const src = tree.token_locs[call.builtin_token].start; |
| 731 | const type_type = try mod.addZIRInstConst(scope, src, .{ | 792 | const type_type = try addZIRInstConst(mod, scope, src, .{ |
| 732 | .ty = Type.initTag(.type), | 793 | .ty = Type.initTag(.type), |
| 733 | .val = Value.initTag(.type_type), | 794 | .val = Value.initTag(.type_type), |
| 734 | }); | 795 | }); |
| ... | @@ -737,21 +798,26 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa | ... | @@ -737,21 +798,26 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa |
| 737 | switch (rl) { | 798 | switch (rl) { |
| 738 | .none => { | 799 | .none => { |
| 739 | const operand = try expr(mod, scope, .none, params[1]); | 800 | const operand = try expr(mod, scope, .none, params[1]); |
| 740 | return mod.addZIRBinOp(scope, src, .bitcast, dest_type, operand); | 801 | return addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand); |
| 741 | }, | 802 | }, |
| 742 | .discard => { | 803 | .discard => { |
| 743 | const operand = try expr(mod, scope, .none, params[1]); | 804 | const operand = try expr(mod, scope, .none, params[1]); |
| 744 | const result = try mod.addZIRBinOp(scope, src, .bitcast, dest_type, operand); | 805 | const result = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand); |
| 745 | _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result); | 806 | _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); |
| 807 | return result; | ||
| 808 | }, | ||
| 809 | .lvalue => { | ||
| 810 | const operand = try expr(mod, scope, .lvalue, params[1]); | ||
| 811 | const result = try addZIRBinOp(mod, scope, src, .bitcast_lvalue, dest_type, operand); | ||
| 746 | return result; | 812 | return result; |
| 747 | }, | 813 | }, |
| 748 | .ty => |result_ty| { | 814 | .ty => |result_ty| { |
| 749 | const result = try expr(mod, scope, .none, params[1]); | 815 | const result = try expr(mod, scope, .none, params[1]); |
| 750 | const bitcasted = try mod.addZIRBinOp(scope, src, .bitcast, dest_type, result); | 816 | const bitcasted = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, result); |
| 751 | return mod.addZIRBinOp(scope, src, .as, result_ty, bitcasted); | 817 | return addZIRBinOp(mod, scope, src, .as, result_ty, bitcasted); |
| 752 | }, | 818 | }, |
| 753 | .ptr => |result_ptr| { | 819 | .ptr => |result_ptr| { |
| 754 | const casted_result_ptr = try mod.addZIRUnOp(scope, src, .bitcast_result_ptr, result_ptr); | 820 | const casted_result_ptr = try addZIRUnOp(mod, scope, src, .bitcast_result_ptr, result_ptr); |
| 755 | return expr(mod, scope, .{ .bitcasted_ptr = casted_result_ptr.castTag(.bitcast_result_ptr).? }, params[1]); | 821 | return expr(mod, scope, .{ .bitcasted_ptr = casted_result_ptr.castTag(.bitcast_result_ptr).? }, params[1]); |
| 756 | }, | 822 | }, |
| 757 | .bitcasted_ptr => |bitcasted_ptr| { | 823 | .bitcasted_ptr => |bitcasted_ptr| { |
| ... | @@ -799,7 +865,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In | ... | @@ -799,7 +865,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In |
| 799 | const args = try scope.getGenZIR().arena.alloc(*zir.Inst, param_nodes.len); | 865 | const args = try scope.getGenZIR().arena.alloc(*zir.Inst, param_nodes.len); |
| 800 | for (param_nodes) |param_node, i| { | 866 | for (param_nodes) |param_node, i| { |
| 801 | const param_src = tree.token_locs[param_node.firstToken()].start; | 867 | const param_src = tree.token_locs[param_node.firstToken()].start; |
| 802 | const param_type = try mod.addZIRInst(scope, param_src, zir.Inst.ParamType, .{ | 868 | const param_type = try addZIRInst(mod, scope, param_src, zir.Inst.ParamType, .{ |
| 803 | .func = lhs, | 869 | .func = lhs, |
| 804 | .arg_index = i, | 870 | .arg_index = i, |
| 805 | }, .{}); | 871 | }, .{}); |
| ... | @@ -807,7 +873,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In | ... | @@ -807,7 +873,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In |
| 807 | } | 873 | } |
| 808 | 874 | ||
| 809 | const src = tree.token_locs[node.lhs.firstToken()].start; | 875 | const src = tree.token_locs[node.lhs.firstToken()].start; |
| 810 | const result = try mod.addZIRInst(scope, src, zir.Inst.Call, .{ | 876 | const result = try addZIRInst(mod, scope, src, zir.Inst.Call, .{ |
| 811 | .func = lhs, | 877 | .func = lhs, |
| 812 | .args = args, | 878 | .args = args, |
| 813 | }, .{}); | 879 | }, .{}); |
| ... | @@ -818,7 +884,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In | ... | @@ -818,7 +884,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In |
| 818 | fn unreach(mod: *Module, scope: *Scope, unreach_node: *ast.Node.OneToken) InnerError!*zir.Inst { | 884 | fn unreach(mod: *Module, scope: *Scope, unreach_node: *ast.Node.OneToken) InnerError!*zir.Inst { |
| 819 | const tree = scope.tree(); | 885 | const tree = scope.tree(); |
| 820 | const src = tree.token_locs[unreach_node.token].start; | 886 | const src = tree.token_locs[unreach_node.token].start; |
| 821 | return mod.addZIRNoOp(scope, src, .@"unreachable"); | 887 | return addZIRNoOp(mod, scope, src, .@"unreachable"); |
| 822 | } | 888 | } |
| 823 | 889 | ||
| 824 | fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { | 890 | fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { |
| ... | @@ -1000,19 +1066,20 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr | ... | @@ -1000,19 +1066,20 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr |
| 1000 | .none => return result, | 1066 | .none => return result, |
| 1001 | .discard => { | 1067 | .discard => { |
| 1002 | // Emit a compile error for discarding error values. | 1068 | // Emit a compile error for discarding error values. |
| 1003 | _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result); | 1069 | _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); |
| 1004 | return result; | 1070 | return result; |
| 1005 | }, | 1071 | }, |
| 1006 | .ty => |ty_inst| return mod.addZIRBinOp(scope, result.src, .as, ty_inst, result), | 1072 | .lvalue => { |
| 1073 | // We need a pointer but we have a value. | ||
| 1074 | return addZIRUnOp(mod, scope, result.src, .ref, result); | ||
| 1075 | }, | ||
| 1076 | .ty => |ty_inst| return addZIRBinOp(mod, scope, result.src, .as, ty_inst, result), | ||
| 1007 | .ptr => |ptr_inst| { | 1077 | .ptr => |ptr_inst| { |
| 1008 | const casted_result = try mod.addZIRInst(scope, result.src, zir.Inst.CoerceToPtrElem, .{ | 1078 | const casted_result = try addZIRInst(mod, scope, result.src, zir.Inst.CoerceToPtrElem, .{ |
| 1009 | .ptr = ptr_inst, | 1079 | .ptr = ptr_inst, |
| 1010 | .value = result, | 1080 | .value = result, |
| 1011 | }, .{}); | 1081 | }, .{}); |
| 1012 | _ = try mod.addZIRInst(scope, result.src, zir.Inst.Store, .{ | 1082 | _ = try addZIRBinOp(mod, scope, result.src, .store, ptr_inst, casted_result); |
| 1013 | .ptr = ptr_inst, | ||
| 1014 | .value = casted_result, | ||
| 1015 | }, .{}); | ||
| 1016 | return casted_result; | 1083 | return casted_result; |
| 1017 | }, | 1084 | }, |
| 1018 | .bitcasted_ptr => |bitcasted_ptr| { | 1085 | .bitcasted_ptr => |bitcasted_ptr| { |
| ... | @@ -1026,3 +1093,121 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr | ... | @@ -1026,3 +1093,121 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr |
| 1026 | }, | 1093 | }, |
| 1027 | } | 1094 | } |
| 1028 | } | 1095 | } |
| 1096 | |||
| 1097 | pub fn addZIRInstSpecial( | ||
| 1098 | mod: *Module, | ||
| 1099 | scope: *Scope, | ||
| 1100 | src: usize, | ||
| 1101 | comptime T: type, | ||
| 1102 | positionals: std.meta.fieldInfo(T, "positionals").field_type, | ||
| 1103 | kw_args: std.meta.fieldInfo(T, "kw_args").field_type, | ||
| 1104 | ) !*T { | ||
| 1105 | const gen_zir = scope.getGenZIR(); | ||
| 1106 | try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); | ||
| 1107 | const inst = try gen_zir.arena.create(T); | ||
| 1108 | inst.* = .{ | ||
| 1109 | .base = .{ | ||
| 1110 | .tag = T.base_tag, | ||
| 1111 | .src = src, | ||
| 1112 | }, | ||
| 1113 | .positionals = positionals, | ||
| 1114 | .kw_args = kw_args, | ||
| 1115 | }; | ||
| 1116 | gen_zir.instructions.appendAssumeCapacity(&inst.base); | ||
| 1117 | return inst; | ||
| 1118 | } | ||
| 1119 | |||
| 1120 | pub fn addZIRNoOpT(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst.NoOp { | ||
| 1121 | const gen_zir = scope.getGenZIR(); | ||
| 1122 | try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); | ||
| 1123 | const inst = try gen_zir.arena.create(zir.Inst.NoOp); | ||
| 1124 | inst.* = .{ | ||
| 1125 | .base = .{ | ||
| 1126 | .tag = tag, | ||
| 1127 | .src = src, | ||
| 1128 | }, | ||
| 1129 | .positionals = .{}, | ||
| 1130 | .kw_args = .{}, | ||
| 1131 | }; | ||
| 1132 | gen_zir.instructions.appendAssumeCapacity(&inst.base); | ||
| 1133 | return inst; | ||
| 1134 | } | ||
| 1135 | |||
| 1136 | pub fn addZIRNoOp(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst { | ||
| 1137 | const inst = try addZIRNoOpT(mod, scope, src, tag); | ||
| 1138 | return &inst.base; | ||
| 1139 | } | ||
| 1140 | |||
| 1141 | pub fn addZIRUnOp( | ||
| 1142 | mod: *Module, | ||
| 1143 | scope: *Scope, | ||
| 1144 | src: usize, | ||
| 1145 | tag: zir.Inst.Tag, | ||
| 1146 | operand: *zir.Inst, | ||
| 1147 | ) !*zir.Inst { | ||
| 1148 | const gen_zir = scope.getGenZIR(); | ||
| 1149 | try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); | ||
| 1150 | const inst = try gen_zir.arena.create(zir.Inst.UnOp); | ||
| 1151 | inst.* = .{ | ||
| 1152 | .base = .{ | ||
| 1153 | .tag = tag, | ||
| 1154 | .src = src, | ||
| 1155 | }, | ||
| 1156 | .positionals = .{ | ||
| 1157 | .operand = operand, | ||
| 1158 | }, | ||
| 1159 | .kw_args = .{}, | ||
| 1160 | }; | ||
| 1161 | gen_zir.instructions.appendAssumeCapacity(&inst.base); | ||
| 1162 | return &inst.base; | ||
| 1163 | } | ||
| 1164 | |||
| 1165 | pub fn addZIRBinOp( | ||
| 1166 | mod: *Module, | ||
| 1167 | scope: *Scope, | ||
| 1168 | src: usize, | ||
| 1169 | tag: zir.Inst.Tag, | ||
| 1170 | lhs: *zir.Inst, | ||
| 1171 | rhs: *zir.Inst, | ||
| 1172 | ) !*zir.Inst { | ||
| 1173 | const gen_zir = scope.getGenZIR(); | ||
| 1174 | try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); | ||
| 1175 | const inst = try gen_zir.arena.create(zir.Inst.BinOp); | ||
| 1176 | inst.* = .{ | ||
| 1177 | .base = .{ | ||
| 1178 | .tag = tag, | ||
| 1179 | .src = src, | ||
| 1180 | }, | ||
| 1181 | .positionals = .{ | ||
| 1182 | .lhs = lhs, | ||
| 1183 | .rhs = rhs, | ||
| 1184 | }, | ||
| 1185 | .kw_args = .{}, | ||
| 1186 | }; | ||
| 1187 | gen_zir.instructions.appendAssumeCapacity(&inst.base); | ||
| 1188 | return &inst.base; | ||
| 1189 | } | ||
| 1190 | |||
| 1191 | pub fn addZIRInst( | ||
| 1192 | mod: *Module, | ||
| 1193 | scope: *Scope, | ||
| 1194 | src: usize, | ||
| 1195 | comptime T: type, | ||
| 1196 | positionals: std.meta.fieldInfo(T, "positionals").field_type, | ||
| 1197 | kw_args: std.meta.fieldInfo(T, "kw_args").field_type, | ||
| 1198 | ) !*zir.Inst { | ||
| 1199 | const inst_special = try addZIRInstSpecial(mod, scope, src, T, positionals, kw_args); | ||
| 1200 | return &inst_special.base; | ||
| 1201 | } | ||
| 1202 | |||
| 1203 | /// TODO The existence of this function is a workaround for a bug in stage1. | ||
| 1204 | pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst { | ||
| 1205 | const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type; | ||
| 1206 | return addZIRInst(mod, scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{}); | ||
| 1207 | } | ||
| 1208 | |||
| 1209 | /// TODO The existence of this function is a workaround for a bug in stage1. | ||
| 1210 | pub fn addZIRInstBlock(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block { | ||
| 1211 | const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type; | ||
| 1212 | return addZIRInstSpecial(mod, scope, src, zir.Inst.Block, P{ .body = body }, .{}); | ||
| 1213 | } |
src-self-hosted/clang.zig+9| ... | @@ -828,6 +828,14 @@ pub const ZigClangExpr_ConstExprUsage = extern enum { | ... | @@ -828,6 +828,14 @@ pub const ZigClangExpr_ConstExprUsage = extern enum { |
| 828 | EvaluateForMangling, | 828 | EvaluateForMangling, |
| 829 | }; | 829 | }; |
| 830 | 830 | ||
| 831 | pub const ZigClangUnaryExprOrTypeTrait_Kind = extern enum { | ||
| 832 | SizeOf, | ||
| 833 | AlignOf, | ||
| 834 | VecStep, | ||
| 835 | OpenMPRequiredSimdAlign, | ||
| 836 | PreferredAlignOf, | ||
| 837 | }; | ||
| 838 | |||
| 831 | pub extern fn ZigClangSourceManager_getSpellingLoc(self: ?*const struct_ZigClangSourceManager, Loc: struct_ZigClangSourceLocation) struct_ZigClangSourceLocation; | 839 | pub extern fn ZigClangSourceManager_getSpellingLoc(self: ?*const struct_ZigClangSourceManager, Loc: struct_ZigClangSourceLocation) struct_ZigClangSourceLocation; |
| 832 | pub extern fn ZigClangSourceManager_getFilename(self: *const struct_ZigClangSourceManager, SpellingLoc: struct_ZigClangSourceLocation) ?[*:0]const u8; | 840 | pub extern fn ZigClangSourceManager_getFilename(self: *const struct_ZigClangSourceManager, SpellingLoc: struct_ZigClangSourceLocation) ?[*:0]const u8; |
| 833 | pub extern fn ZigClangSourceManager_getSpellingLineNumber(self: ?*const struct_ZigClangSourceManager, Loc: struct_ZigClangSourceLocation) c_uint; | 841 | 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 | ... | @@ -1225,6 +1233,7 @@ pub extern fn ZigClangCallExpr_getArgs(*const ZigClangCallExpr) [*]const *const |
| 1225 | 1233 | ||
| 1226 | pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangQualType; | 1234 | pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangQualType; |
| 1227 | pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangSourceLocation; | 1235 | pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangSourceLocation; |
| 1236 | pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getKind(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangUnaryExprOrTypeTrait_Kind; | ||
| 1228 | 1237 | ||
| 1229 | pub extern fn ZigClangUnaryOperator_getOpcode(*const ZigClangUnaryOperator) ZigClangUO; | 1238 | pub extern fn ZigClangUnaryOperator_getOpcode(*const ZigClangUnaryOperator) ZigClangUO; |
| 1230 | pub extern fn ZigClangUnaryOperator_getType(*const ZigClangUnaryOperator) ZigClangQualType; | 1239 | pub extern fn ZigClangUnaryOperator_getType(*const ZigClangUnaryOperator) ZigClangQualType; |
src-self-hosted/codegen.zig+928-211| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const mem = std.mem; | 2 | const mem = std.mem; |
| 3 | const math = std.math; | ||
| 3 | const assert = std.debug.assert; | 4 | const assert = std.debug.assert; |
| 4 | const ir = @import("ir.zig"); | 5 | const ir = @import("ir.zig"); |
| 5 | const Type = @import("type.zig").Type; | 6 | const Type = @import("type.zig").Type; |
| ... | @@ -11,6 +12,11 @@ const ErrorMsg = Module.ErrorMsg; | ... | @@ -11,6 +12,11 @@ const ErrorMsg = Module.ErrorMsg; |
| 11 | const Target = std.Target; | 12 | const Target = std.Target; |
| 12 | const Allocator = mem.Allocator; | 13 | const Allocator = mem.Allocator; |
| 13 | const trace = @import("tracy.zig").trace; | 14 | const trace = @import("tracy.zig").trace; |
| 15 | const DW = std.dwarf; | ||
| 16 | const leb128 = std.debug.leb; | ||
| 17 | |||
| 18 | // TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. | ||
| 19 | // zig fmt: off | ||
| 14 | 20 | ||
| 15 | /// The codegen-related data that is stored in `ir.Inst.Block` instructions. | 21 | /// The codegen-related data that is stored in `ir.Inst.Block` instructions. |
| 16 | pub const BlockData = struct { | 22 | pub const BlockData = struct { |
| ... | @@ -43,64 +49,66 @@ pub fn generateSymbol( | ... | @@ -43,64 +49,66 @@ pub fn generateSymbol( |
| 43 | src: usize, | 49 | src: usize, |
| 44 | typed_value: TypedValue, | 50 | typed_value: TypedValue, |
| 45 | code: *std.ArrayList(u8), | 51 | code: *std.ArrayList(u8), |
| 52 | dbg_line: *std.ArrayList(u8), | ||
| 46 | ) GenerateSymbolError!Result { | 53 | ) GenerateSymbolError!Result { |
| 47 | const tracy = trace(@src()); | 54 | const tracy = trace(@src()); |
| 48 | defer tracy.end(); | 55 | defer tracy.end(); |
| 49 | 56 | ||
| 50 | switch (typed_value.ty.zigTypeTag()) { | 57 | switch (typed_value.ty.zigTypeTag()) { |
| 51 | .Fn => { | 58 | .Fn => { |
| 52 | switch (bin_file.options.target.cpu.arch) { | 59 | switch (bin_file.base.options.target.cpu.arch) { |
| 53 | .arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code), | 60 | //.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 54 | .armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code), | 61 | //.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 55 | .aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code), | 62 | //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 56 | .aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code), | 63 | //.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 57 | .aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code), | 64 | //.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 58 | .arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code), | 65 | //.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 59 | .avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code), | 66 | //.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 60 | .bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code), | 67 | //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 61 | .bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code), | 68 | //.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 62 | .hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code), | 69 | //.hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 63 | .mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code), | 70 | //.mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 64 | .mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code), | 71 | //.mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 65 | .mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code), | 72 | //.mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 66 | .mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code), | 73 | //.mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 67 | .msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code), | 74 | //.msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 68 | .powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code), | 75 | //.powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 69 | .powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code), | 76 | //.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 70 | .powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code), | 77 | //.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 71 | .r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code), | 78 | //.r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 72 | .amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code), | 79 | //.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 73 | .riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code), | 80 | //.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 74 | .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code), | 81 | .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 75 | .sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code), | 82 | //.sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 76 | .sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code), | 83 | //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 77 | .sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code), | 84 | //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 78 | .s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code), | 85 | //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 79 | .tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code), | 86 | //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 80 | .tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code), | 87 | //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 81 | .thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code), | 88 | //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 82 | .thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code), | 89 | //.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 83 | .i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code), | 90 | //.i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 84 | .x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code), | 91 | .x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 85 | .xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code), | 92 | //.xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 86 | .nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code), | 93 | //.nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 87 | .nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code), | 94 | //.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 88 | .le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code), | 95 | //.le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 89 | .le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code), | 96 | //.le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 90 | .amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code), | 97 | //.amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 91 | .amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code), | 98 | //.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 92 | .hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code), | 99 | //.hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 93 | .hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code), | 100 | //.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 94 | .spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code), | 101 | //.spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 95 | .spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code), | 102 | //.spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 96 | .kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code), | 103 | //.kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 97 | .shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code), | 104 | //.shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 98 | .lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code), | 105 | //.lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 99 | .wasm32 => return Function(.wasm32).generateSymbol(bin_file, src, typed_value, code), | 106 | //.wasm32 => return Function(.wasm32).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 100 | .wasm64 => return Function(.wasm64).generateSymbol(bin_file, src, typed_value, code), | 107 | //.wasm64 => return Function(.wasm64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 101 | .renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code), | 108 | //.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 102 | .renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code), | 109 | //.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 103 | .ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code), | 110 | //.ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code, dbg_line), |
| 111 | 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."), | ||
| 104 | } | 112 | } |
| 105 | }, | 113 | }, |
| 106 | .Array => { | 114 | .Array => { |
| ... | @@ -112,7 +120,7 @@ pub fn generateSymbol( | ... | @@ -112,7 +120,7 @@ pub fn generateSymbol( |
| 112 | switch (try generateSymbol(bin_file, src, .{ | 120 | switch (try generateSymbol(bin_file, src, .{ |
| 113 | .ty = typed_value.ty.elemType(), | 121 | .ty = typed_value.ty.elemType(), |
| 114 | .val = sentinel, | 122 | .val = sentinel, |
| 115 | }, code)) { | 123 | }, code, dbg_line)) { |
| 116 | .appended => return Result{ .appended = {} }, | 124 | .appended => return Result{ .appended = {} }, |
| 117 | .externally_managed => |slice| { | 125 | .externally_managed => |slice| { |
| 118 | code.appendSliceAssumeCapacity(slice); | 126 | code.appendSliceAssumeCapacity(slice); |
| ... | @@ -141,7 +149,7 @@ pub fn generateSymbol( | ... | @@ -141,7 +149,7 @@ pub fn generateSymbol( |
| 141 | // TODO handle the dependency of this symbol on the decl's vaddr. | 149 | // TODO handle the dependency of this symbol on the decl's vaddr. |
| 142 | // If the decl changes vaddr, then this symbol needs to get regenerated. | 150 | // If the decl changes vaddr, then this symbol needs to get regenerated. |
| 143 | const vaddr = bin_file.local_symbols.items[decl.link.local_sym_index].st_value; | 151 | const vaddr = bin_file.local_symbols.items[decl.link.local_sym_index].st_value; |
| 144 | const endian = bin_file.options.target.cpu.arch.endian(); | 152 | const endian = bin_file.base.options.target.cpu.arch.endian(); |
| 145 | switch (bin_file.ptr_width) { | 153 | switch (bin_file.ptr_width) { |
| 146 | .p32 => { | 154 | .p32 => { |
| 147 | try code.resize(4); | 155 | try code.resize(4); |
| ... | @@ -164,7 +172,7 @@ pub fn generateSymbol( | ... | @@ -164,7 +172,7 @@ pub fn generateSymbol( |
| 164 | }; | 172 | }; |
| 165 | }, | 173 | }, |
| 166 | .Int => { | 174 | .Int => { |
| 167 | const info = typed_value.ty.intInfo(bin_file.options.target); | 175 | const info = typed_value.ty.intInfo(bin_file.base.options.target); |
| 168 | if (info.bits == 8 and !info.signed) { | 176 | if (info.bits == 8 and !info.signed) { |
| 169 | const x = typed_value.val.toUnsignedInt(); | 177 | const x = typed_value.val.toUnsignedInt(); |
| 170 | try code.append(@intCast(u8, x)); | 178 | try code.append(@intCast(u8, x)); |
| ... | @@ -204,10 +212,28 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -204,10 +212,28 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 204 | target: *const std.Target, | 212 | target: *const std.Target, |
| 205 | mod_fn: *const Module.Fn, | 213 | mod_fn: *const Module.Fn, |
| 206 | code: *std.ArrayList(u8), | 214 | code: *std.ArrayList(u8), |
| 215 | dbg_line: *std.ArrayList(u8), | ||
| 207 | err_msg: ?*ErrorMsg, | 216 | err_msg: ?*ErrorMsg, |
| 208 | args: []MCValue, | 217 | args: []MCValue, |
| 218 | ret_mcv: MCValue, | ||
| 219 | fn_type: Type, | ||
| 209 | arg_index: usize, | 220 | arg_index: usize, |
| 210 | src: usize, | 221 | src: usize, |
| 222 | stack_align: u32, | ||
| 223 | |||
| 224 | /// Byte offset within the source file. | ||
| 225 | prev_di_src: usize, | ||
| 226 | /// Relative to the beginning of `code`. | ||
| 227 | prev_di_pc: usize, | ||
| 228 | /// Used to find newlines and count line deltas. | ||
| 229 | source: []const u8, | ||
| 230 | /// Byte offset within the source file of the ending curly. | ||
| 231 | rbrace_src: usize, | ||
| 232 | |||
| 233 | /// The value is an offset into the `Function` `code` from the beginning. | ||
| 234 | /// To perform the reloc, write 32-bit signed little-endian integer | ||
| 235 | /// which is a relative jump, based on the address following the reloc. | ||
| 236 | exitlude_jump_relocs: std.ArrayListUnmanaged(usize) = .{}, | ||
| 211 | 237 | ||
| 212 | /// Whenever there is a runtime branch, we push a Branch onto this stack, | 238 | /// Whenever there is a runtime branch, we push a Branch onto this stack, |
| 213 | /// and pop it off when the runtime branch joins. This provides an "overlay" | 239 | /// 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 { | ... | @@ -225,22 +251,32 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 225 | unreach, | 251 | unreach, |
| 226 | /// No more references to this value remain. | 252 | /// No more references to this value remain. |
| 227 | dead, | 253 | dead, |
| 254 | /// The value is undefined. | ||
| 255 | undef, | ||
| 228 | /// A pointer-sized integer that fits in a register. | 256 | /// A pointer-sized integer that fits in a register. |
| 257 | /// If the type is a pointer, this is the pointer address in virtual address space. | ||
| 229 | immediate: u64, | 258 | immediate: u64, |
| 230 | /// The constant was emitted into the code, at this offset. | 259 | /// The constant was emitted into the code, at this offset. |
| 260 | /// If the type is a pointer, it means the pointer address is embedded in the code. | ||
| 231 | embedded_in_code: usize, | 261 | embedded_in_code: usize, |
| 262 | /// The value is a pointer to a constant which was emitted into the code, at this offset. | ||
| 263 | ptr_embedded_in_code: usize, | ||
| 232 | /// The value is in a target-specific register. | 264 | /// The value is in a target-specific register. |
| 233 | register: Register, | 265 | register: Register, |
| 234 | /// The value is in memory at a hard-coded address. | 266 | /// The value is in memory at a hard-coded address. |
| 267 | /// If the type is a pointer, it means the pointer address is at this memory location. | ||
| 235 | memory: u64, | 268 | memory: u64, |
| 236 | /// The value is one of the stack variables. | 269 | /// The value is one of the stack variables. |
| 237 | stack_offset: u64, | 270 | /// If the type is a pointer, it means the pointer address is in the stack at this offset. |
| 271 | stack_offset: u32, | ||
| 272 | /// The value is a pointer to one of the stack variables (payload is stack offset). | ||
| 273 | ptr_stack_offset: u32, | ||
| 238 | /// The value is in the compare flags assuming an unsigned operation, | 274 | /// The value is in the compare flags assuming an unsigned operation, |
| 239 | /// with this operator applied on top of it. | 275 | /// with this operator applied on top of it. |
| 240 | compare_flags_unsigned: std.math.CompareOperator, | 276 | compare_flags_unsigned: math.CompareOperator, |
| 241 | /// The value is in the compare flags assuming a signed operation, | 277 | /// The value is in the compare flags assuming a signed operation, |
| 242 | /// with this operator applied on top of it. | 278 | /// with this operator applied on top of it. |
| 243 | compare_flags_signed: std.math.CompareOperator, | 279 | compare_flags_signed: math.CompareOperator, |
| 244 | 280 | ||
| 245 | fn isMemory(mcv: MCValue) bool { | 281 | fn isMemory(mcv: MCValue) bool { |
| 246 | return switch (mcv) { | 282 | return switch (mcv) { |
| ... | @@ -267,6 +303,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -267,6 +303,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 267 | .memory, | 303 | .memory, |
| 268 | .compare_flags_unsigned, | 304 | .compare_flags_unsigned, |
| 269 | .compare_flags_signed, | 305 | .compare_flags_signed, |
| 306 | .ptr_stack_offset, | ||
| 307 | .ptr_embedded_in_code, | ||
| 308 | .undef, | ||
| 270 | => false, | 309 | => false, |
| 271 | 310 | ||
| 272 | .register, | 311 | .register, |
| ... | @@ -279,10 +318,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -279,10 +318,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 279 | const Branch = struct { | 318 | const Branch = struct { |
| 280 | inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, | 319 | inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, |
| 281 | registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{}, | 320 | registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{}, |
| 282 | free_registers: FreeRegInt = std.math.maxInt(FreeRegInt), | 321 | free_registers: FreeRegInt = math.maxInt(FreeRegInt), |
| 283 | 322 | ||
| 284 | /// Maps offset to what is stored there. | 323 | /// Maps offset to what is stored there. |
| 285 | stack: std.AutoHashMapUnmanaged(usize, StackAllocation) = .{}, | 324 | stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{}, |
| 286 | /// Offset from the stack base, representing the end of the stack frame. | 325 | /// Offset from the stack base, representing the end of the stack frame. |
| 287 | max_end_stack: u32 = 0, | 326 | max_end_stack: u32 = 0, |
| 288 | /// Represents the current end stack offset. If there is no existing slot | 327 | /// 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 { | ... | @@ -292,7 +331,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 292 | fn markRegUsed(self: *Branch, reg: Register) void { | 331 | fn markRegUsed(self: *Branch, reg: Register) void { |
| 293 | if (FreeRegInt == u0) return; | 332 | if (FreeRegInt == u0) return; |
| 294 | const index = reg.allocIndex() orelse return; | 333 | const index = reg.allocIndex() orelse return; |
| 295 | const ShiftInt = std.math.Log2Int(FreeRegInt); | 334 | const ShiftInt = math.Log2Int(FreeRegInt); |
| 296 | const shift = @intCast(ShiftInt, index); | 335 | const shift = @intCast(ShiftInt, index); |
| 297 | self.free_registers &= ~(@as(FreeRegInt, 1) << shift); | 336 | self.free_registers &= ~(@as(FreeRegInt, 1) << shift); |
| 298 | } | 337 | } |
| ... | @@ -300,11 +339,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -300,11 +339,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 300 | fn markRegFree(self: *Branch, reg: Register) void { | 339 | fn markRegFree(self: *Branch, reg: Register) void { |
| 301 | if (FreeRegInt == u0) return; | 340 | if (FreeRegInt == u0) return; |
| 302 | const index = reg.allocIndex() orelse return; | 341 | const index = reg.allocIndex() orelse return; |
| 303 | const ShiftInt = std.math.Log2Int(FreeRegInt); | 342 | const ShiftInt = math.Log2Int(FreeRegInt); |
| 304 | const shift = @intCast(ShiftInt, index); | 343 | const shift = @intCast(ShiftInt, index); |
| 305 | self.free_registers |= @as(FreeRegInt, 1) << shift; | 344 | self.free_registers |= @as(FreeRegInt, 1) << shift; |
| 306 | } | 345 | } |
| 307 | 346 | ||
| 347 | /// Before calling, must ensureCapacity + 1 on branch.registers. | ||
| 348 | /// Returns `null` if all registers are allocated. | ||
| 349 | fn allocReg(self: *Branch, inst: *ir.Inst) ?Register { | ||
| 350 | const free_index = @ctz(FreeRegInt, self.free_registers); | ||
| 351 | if (free_index >= callee_preserved_regs.len) { | ||
| 352 | return null; | ||
| 353 | } | ||
| 354 | self.free_registers &= ~(@as(FreeRegInt, 1) << free_index); | ||
| 355 | const reg = callee_preserved_regs[free_index]; | ||
| 356 | self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); | ||
| 357 | return reg; | ||
| 358 | } | ||
| 359 | |||
| 308 | fn deinit(self: *Branch, gpa: *Allocator) void { | 360 | fn deinit(self: *Branch, gpa: *Allocator) void { |
| 309 | self.inst_table.deinit(gpa); | 361 | self.inst_table.deinit(gpa); |
| 310 | self.registers.deinit(gpa); | 362 | self.registers.deinit(gpa); |
| ... | @@ -329,15 +381,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -329,15 +381,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 329 | src: usize, | 381 | src: usize, |
| 330 | typed_value: TypedValue, | 382 | typed_value: TypedValue, |
| 331 | code: *std.ArrayList(u8), | 383 | code: *std.ArrayList(u8), |
| 384 | dbg_line: *std.ArrayList(u8), | ||
| 332 | ) GenerateSymbolError!Result { | 385 | ) GenerateSymbolError!Result { |
| 333 | const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; | 386 | const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; |
| 334 | 387 | ||
| 335 | const fn_type = module_fn.owner_decl.typed_value.most_recent.typed_value.ty; | 388 | const fn_type = module_fn.owner_decl.typed_value.most_recent.typed_value.ty; |
| 336 | const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen()); | ||
| 337 | defer bin_file.allocator.free(param_types); | ||
| 338 | fn_type.fnParamTypes(param_types); | ||
| 339 | var mc_args = try bin_file.allocator.alloc(MCValue, param_types.len); | ||
| 340 | defer bin_file.allocator.free(mc_args); | ||
| 341 | 389 | ||
| 342 | var branch_stack = std.ArrayList(Branch).init(bin_file.allocator); | 390 | var branch_stack = std.ArrayList(Branch).init(bin_file.allocator); |
| 343 | defer { | 391 | defer { |
| ... | @@ -348,24 +396,54 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -348,24 +396,54 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 348 | const branch = try branch_stack.addOne(); | 396 | const branch = try branch_stack.addOne(); |
| 349 | branch.* = .{}; | 397 | branch.* = .{}; |
| 350 | 398 | ||
| 399 | const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: { | ||
| 400 | if (module_fn.owner_decl.scope.cast(Module.Scope.File)) |scope_file| { | ||
| 401 | const tree = scope_file.contents.tree; | ||
| 402 | const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?; | ||
| 403 | const block = fn_proto.body().?.castTag(.Block).?; | ||
| 404 | const lbrace_src = tree.token_locs[block.lbrace].start; | ||
| 405 | const rbrace_src = tree.token_locs[block.rbrace].start; | ||
| 406 | break :blk .{ .lbrace_src = lbrace_src, .rbrace_src = rbrace_src, .source = tree.source }; | ||
| 407 | } else if (module_fn.owner_decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| { | ||
| 408 | const byte_off = zir_module.contents.module.decls[module_fn.owner_decl.src_index].inst.src; | ||
| 409 | break :blk .{ .lbrace_src = byte_off, .rbrace_src = byte_off, .source = zir_module.source.bytes }; | ||
| 410 | } else { | ||
| 411 | unreachable; | ||
| 412 | } | ||
| 413 | }; | ||
| 414 | |||
| 351 | var function = Self{ | 415 | var function = Self{ |
| 352 | .gpa = bin_file.allocator, | 416 | .gpa = bin_file.allocator, |
| 353 | .target = &bin_file.options.target, | 417 | .target = &bin_file.base.options.target, |
| 354 | .bin_file = bin_file, | 418 | .bin_file = bin_file, |
| 355 | .mod_fn = module_fn, | 419 | .mod_fn = module_fn, |
| 356 | .code = code, | 420 | .code = code, |
| 421 | .dbg_line = dbg_line, | ||
| 357 | .err_msg = null, | 422 | .err_msg = null, |
| 358 | .args = mc_args, | 423 | .args = undefined, // populated after `resolveCallingConventionValues` |
| 424 | .ret_mcv = undefined, // populated after `resolveCallingConventionValues` | ||
| 425 | .fn_type = fn_type, | ||
| 359 | .arg_index = 0, | 426 | .arg_index = 0, |
| 360 | .branch_stack = &branch_stack, | 427 | .branch_stack = &branch_stack, |
| 361 | .src = src, | 428 | .src = src, |
| 429 | .stack_align = undefined, | ||
| 430 | .prev_di_pc = 0, | ||
| 431 | .prev_di_src = src_data.lbrace_src, | ||
| 432 | .rbrace_src = src_data.rbrace_src, | ||
| 433 | .source = src_data.source, | ||
| 362 | }; | 434 | }; |
| 435 | defer function.exitlude_jump_relocs.deinit(bin_file.allocator); | ||
| 363 | 436 | ||
| 364 | const cc = fn_type.fnCallingConvention(); | 437 | var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) { |
| 365 | branch.max_end_stack = function.resolveParameters(src, cc, param_types, mc_args) catch |err| switch (err) { | ||
| 366 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, | 438 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, |
| 367 | else => |e| return e, | 439 | else => |e| return e, |
| 368 | }; | 440 | }; |
| 441 | defer call_info.deinit(&function); | ||
| 442 | |||
| 443 | function.args = call_info.args; | ||
| 444 | function.ret_mcv = call_info.return_value; | ||
| 445 | function.stack_align = call_info.stack_align; | ||
| 446 | branch.max_end_stack = call_info.stack_byte_count; | ||
| 369 | 447 | ||
| 370 | function.gen() catch |err| switch (err) { | 448 | function.gen() catch |err| switch (err) { |
| 371 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, | 449 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, |
| ... | @@ -380,28 +458,81 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -380,28 +458,81 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 380 | } | 458 | } |
| 381 | 459 | ||
| 382 | fn gen(self: *Self) !void { | 460 | fn gen(self: *Self) !void { |
| 383 | try self.code.ensureCapacity(self.code.items.len + 11); | 461 | switch (arch) { |
| 384 | 462 | .x86_64 => { | |
| 385 | // push rbp | 463 | try self.code.ensureCapacity(self.code.items.len + 11); |
| 386 | // mov rbp, rsp | 464 | |
| 387 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x55, 0x48, 0x89, 0xe5 }); | 465 | const cc = self.fn_type.fnCallingConvention(); |
| 388 | 466 | if (cc != .Naked) { | |
| 389 | // sub rsp, x | 467 | // We want to subtract the aligned stack frame size from rsp here, but we don't |
| 390 | const stack_end = self.branch_stack.items[0].max_end_stack; | 468 | // yet know how big it will be, so we leave room for a 4-byte stack size. |
| 391 | if (stack_end > std.math.maxInt(i32)) { | 469 | // TODO During semantic analysis, check if there are no function calls. If there |
| 392 | return self.fail(self.src, "too much stack used in call parameters", .{}); | 470 | // are none, here we can omit the part where we subtract and then add rsp. |
| 393 | } else if (stack_end > std.math.maxInt(i8)) { | 471 | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 394 | // 48 83 ec xx sub rsp,0x10 | 472 | 0x55, // push rbp |
| 395 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xec }); | 473 | 0x48, 0x89, 0xe5, // mov rbp, rsp |
| 396 | const x = @intCast(u32, stack_end); | 474 | 0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc) |
| 397 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); | 475 | }); |
| 398 | } else if (stack_end != 0) { | 476 | const reloc_index = self.code.items.len; |
| 399 | // 48 81 ec xx xx xx xx sub rsp,0x80 | 477 | self.code.items.len += 4; |
| 400 | const x = @intCast(u8, stack_end); | 478 | |
| 401 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xec, x }); | 479 | try self.dbgSetPrologueEnd(); |
| 402 | } | 480 | try self.genBody(self.mod_fn.analysis.success); |
| 481 | |||
| 482 | const stack_end = self.branch_stack.items[0].max_end_stack; | ||
| 483 | if (stack_end > math.maxInt(i32)) | ||
| 484 | return self.fail(self.src, "too much stack used in call parameters", .{}); | ||
| 485 | const aligned_stack_end = mem.alignForward(stack_end, self.stack_align); | ||
| 486 | mem.writeIntLittle(u32, self.code.items[reloc_index..][0..4], @intCast(u32, aligned_stack_end)); | ||
| 487 | |||
| 488 | if (self.code.items.len >= math.maxInt(i32)) { | ||
| 489 | return self.fail(self.src, "unable to perform relocation: jump too far", .{}); | ||
| 490 | } | ||
| 491 | for (self.exitlude_jump_relocs.items) |jmp_reloc| { | ||
| 492 | const amt = self.code.items.len - (jmp_reloc + 4); | ||
| 493 | // If it wouldn't jump at all, elide it. | ||
| 494 | if (amt == 0) { | ||
| 495 | self.code.items.len -= 5; | ||
| 496 | continue; | ||
| 497 | } | ||
| 498 | const s32_amt = @intCast(i32, amt); | ||
| 499 | mem.writeIntLittle(i32, self.code.items[jmp_reloc..][0..4], s32_amt); | ||
| 500 | } | ||
| 501 | |||
| 502 | // Important to be after the possible self.code.items.len -= 5 above. | ||
| 503 | try self.dbgSetEpilogueBegin(); | ||
| 504 | |||
| 505 | try self.code.ensureCapacity(self.code.items.len + 9); | ||
| 506 | // add rsp, x | ||
| 507 | if (aligned_stack_end > math.maxInt(i8)) { | ||
| 508 | // example: 48 81 c4 ff ff ff 7f add rsp,0x7fffffff | ||
| 509 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xc4 }); | ||
| 510 | const x = @intCast(u32, aligned_stack_end); | ||
| 511 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); | ||
| 512 | } else if (aligned_stack_end != 0) { | ||
| 513 | // example: 48 83 c4 7f add rsp,0x7f | ||
| 514 | const x = @intCast(u8, aligned_stack_end); | ||
| 515 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xc4, x }); | ||
| 516 | } | ||
| 403 | 517 | ||
| 404 | try self.genBody(self.mod_fn.analysis.success); | 518 | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 519 | 0x5d, // pop rbp | ||
| 520 | 0xc3, // ret | ||
| 521 | }); | ||
| 522 | } else { | ||
| 523 | try self.dbgSetPrologueEnd(); | ||
| 524 | try self.genBody(self.mod_fn.analysis.success); | ||
| 525 | try self.dbgSetEpilogueBegin(); | ||
| 526 | } | ||
| 527 | }, | ||
| 528 | else => { | ||
| 529 | try self.dbgSetPrologueEnd(); | ||
| 530 | try self.genBody(self.mod_fn.analysis.success); | ||
| 531 | try self.dbgSetEpilogueBegin(); | ||
| 532 | }, | ||
| 533 | } | ||
| 534 | // Drop them off at the rbrace. | ||
| 535 | try self.dbgAdvancePCAndLine(self.rbrace_src); | ||
| 405 | } | 536 | } |
| 406 | 537 | ||
| 407 | fn genBody(self: *Self, body: ir.Body) InnerError!void { | 538 | fn genBody(self: *Self, body: ir.Body) InnerError!void { |
| ... | @@ -418,6 +549,38 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -418,6 +549,38 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 418 | } | 549 | } |
| 419 | } | 550 | } |
| 420 | 551 | ||
| 552 | fn dbgSetPrologueEnd(self: *Self) InnerError!void { | ||
| 553 | try self.dbg_line.append(DW.LNS_set_prologue_end); | ||
| 554 | try self.dbgAdvancePCAndLine(self.prev_di_src); | ||
| 555 | } | ||
| 556 | |||
| 557 | fn dbgSetEpilogueBegin(self: *Self) InnerError!void { | ||
| 558 | try self.dbg_line.append(DW.LNS_set_epilogue_begin); | ||
| 559 | try self.dbgAdvancePCAndLine(self.prev_di_src); | ||
| 560 | } | ||
| 561 | |||
| 562 | fn dbgAdvancePCAndLine(self: *Self, src: usize) InnerError!void { | ||
| 563 | // TODO Look into improving the performance here by adding a token-index-to-line | ||
| 564 | // lookup table, and changing ir.Inst from storing byte offset to token. Currently | ||
| 565 | // this involves scanning over the source code for newlines | ||
| 566 | // (but only from the previous byte offset to the new one). | ||
| 567 | const delta_line = std.zig.lineDelta(self.source, self.prev_di_src, src); | ||
| 568 | const delta_pc = self.code.items.len - self.prev_di_pc; | ||
| 569 | self.prev_di_src = src; | ||
| 570 | self.prev_di_pc = self.code.items.len; | ||
| 571 | // TODO Look into using the DWARF special opcodes to compress this data. It lets you emit | ||
| 572 | // single-byte opcodes that add different numbers to both the PC and the line number | ||
| 573 | // at the same time. | ||
| 574 | try self.dbg_line.ensureCapacity(self.dbg_line.items.len + 11); | ||
| 575 | self.dbg_line.appendAssumeCapacity(DW.LNS_advance_pc); | ||
| 576 | leb128.writeULEB128(self.dbg_line.writer(), delta_pc) catch unreachable; | ||
| 577 | if (delta_line != 0) { | ||
| 578 | self.dbg_line.appendAssumeCapacity(DW.LNS_advance_line); | ||
| 579 | leb128.writeILEB128(self.dbg_line.writer(), delta_line) catch unreachable; | ||
| 580 | } | ||
| 581 | self.dbg_line.appendAssumeCapacity(DW.LNS_copy); | ||
| 582 | } | ||
| 583 | |||
| 421 | fn processDeath(self: *Self, inst: *ir.Inst) void { | 584 | fn processDeath(self: *Self, inst: *ir.Inst) void { |
| 422 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | 585 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 423 | const entry = branch.inst_table.getEntry(inst) orelse return; | 586 | const entry = branch.inst_table.getEntry(inst) orelse return; |
| ... | @@ -425,8 +588,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -425,8 +588,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 425 | entry.value = .dead; | 588 | entry.value = .dead; |
| 426 | switch (prev_value) { | 589 | switch (prev_value) { |
| 427 | .register => |reg| { | 590 | .register => |reg| { |
| 428 | _ = branch.registers.remove(reg); | 591 | const canon_reg = toCanonicalReg(reg); |
| 429 | branch.markRegFree(reg); | 592 | _ = branch.registers.remove(canon_reg); |
| 593 | branch.markRegFree(canon_reg); | ||
| 430 | }, | 594 | }, |
| 431 | else => {}, // TODO process stack allocation death | 595 | else => {}, // TODO process stack allocation death |
| 432 | } | 596 | } |
| ... | @@ -435,6 +599,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -435,6 +599,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 435 | fn genFuncInst(self: *Self, inst: *ir.Inst) !MCValue { | 599 | fn genFuncInst(self: *Self, inst: *ir.Inst) !MCValue { |
| 436 | switch (inst.tag) { | 600 | switch (inst.tag) { |
| 437 | .add => return self.genAdd(inst.castTag(.add).?), | 601 | .add => return self.genAdd(inst.castTag(.add).?), |
| 602 | .alloc => return self.genAlloc(inst.castTag(.alloc).?), | ||
| 438 | .arg => return self.genArg(inst.castTag(.arg).?), | 603 | .arg => return self.genArg(inst.castTag(.arg).?), |
| 439 | .assembly => return self.genAsm(inst.castTag(.assembly).?), | 604 | .assembly => return self.genAsm(inst.castTag(.assembly).?), |
| 440 | .bitcast => return self.genBitCast(inst.castTag(.bitcast).?), | 605 | .bitcast => return self.genBitCast(inst.castTag(.bitcast).?), |
| ... | @@ -451,19 +616,91 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -451,19 +616,91 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 451 | .cmp_neq => return self.genCmp(inst.castTag(.cmp_neq).?, .neq), | 616 | .cmp_neq => return self.genCmp(inst.castTag(.cmp_neq).?, .neq), |
| 452 | .condbr => return self.genCondBr(inst.castTag(.condbr).?), | 617 | .condbr => return self.genCondBr(inst.castTag(.condbr).?), |
| 453 | .constant => unreachable, // excluded from function bodies | 618 | .constant => unreachable, // excluded from function bodies |
| 619 | .dbg_stmt => return self.genDbgStmt(inst.castTag(.dbg_stmt).?), | ||
| 620 | .floatcast => return self.genFloatCast(inst.castTag(.floatcast).?), | ||
| 621 | .intcast => return self.genIntCast(inst.castTag(.intcast).?), | ||
| 454 | .isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?), | 622 | .isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?), |
| 455 | .isnull => return self.genIsNull(inst.castTag(.isnull).?), | 623 | .isnull => return self.genIsNull(inst.castTag(.isnull).?), |
| 624 | .load => return self.genLoad(inst.castTag(.load).?), | ||
| 625 | .not => return self.genNot(inst.castTag(.not).?), | ||
| 456 | .ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?), | 626 | .ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?), |
| 627 | .ref => return self.genRef(inst.castTag(.ref).?), | ||
| 457 | .ret => return self.genRet(inst.castTag(.ret).?), | 628 | .ret => return self.genRet(inst.castTag(.ret).?), |
| 458 | .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?), | 629 | .retvoid => return self.genRetVoid(inst.castTag(.retvoid).?), |
| 630 | .store => return self.genStore(inst.castTag(.store).?), | ||
| 459 | .sub => return self.genSub(inst.castTag(.sub).?), | 631 | .sub => return self.genSub(inst.castTag(.sub).?), |
| 460 | .unreach => return MCValue{ .unreach = {} }, | 632 | .unreach => return MCValue{ .unreach = {} }, |
| 461 | .not => return self.genNot(inst.castTag(.not).?), | ||
| 462 | .floatcast => return self.genFloatCast(inst.castTag(.floatcast).?), | ||
| 463 | .intcast => return self.genIntCast(inst.castTag(.intcast).?), | ||
| 464 | } | 633 | } |
| 465 | } | 634 | } |
| 466 | 635 | ||
| 636 | fn allocMem(self: *Self, inst: *ir.Inst, abi_size: u32, abi_align: u32) !u32 { | ||
| 637 | if (abi_align > self.stack_align) | ||
| 638 | self.stack_align = abi_align; | ||
| 639 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | ||
| 640 | // TODO find a free slot instead of always appending | ||
| 641 | const offset = mem.alignForwardGeneric(u32, branch.next_stack_offset, abi_align); | ||
| 642 | branch.next_stack_offset = offset + abi_size; | ||
| 643 | if (branch.next_stack_offset > branch.max_end_stack) | ||
| 644 | branch.max_end_stack = branch.next_stack_offset; | ||
| 645 | try branch.stack.putNoClobber(self.gpa, offset, .{ | ||
| 646 | .inst = inst, | ||
| 647 | .size = abi_size, | ||
| 648 | }); | ||
| 649 | return offset; | ||
| 650 | } | ||
| 651 | |||
| 652 | /// Use a pointer instruction as the basis for allocating stack memory. | ||
| 653 | fn allocMemPtr(self: *Self, inst: *ir.Inst) !u32 { | ||
| 654 | const elem_ty = inst.ty.elemType(); | ||
| 655 | const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { | ||
| 656 | return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty}); | ||
| 657 | }; | ||
| 658 | // TODO swap this for inst.ty.ptrAlign | ||
| 659 | const abi_align = elem_ty.abiAlignment(self.target.*); | ||
| 660 | return self.allocMem(inst, abi_size, abi_align); | ||
| 661 | } | ||
| 662 | |||
| 663 | fn allocRegOrMem(self: *Self, inst: *ir.Inst) !MCValue { | ||
| 664 | const elem_ty = inst.ty; | ||
| 665 | const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { | ||
| 666 | return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty}); | ||
| 667 | }; | ||
| 668 | const abi_align = elem_ty.abiAlignment(self.target.*); | ||
| 669 | if (abi_align > self.stack_align) | ||
| 670 | self.stack_align = abi_align; | ||
| 671 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | ||
| 672 | |||
| 673 | // Make sure the type can fit in a register before we try to allocate one. | ||
| 674 | const ptr_bits = arch.ptrBitWidth(); | ||
| 675 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); | ||
| 676 | if (abi_size <= ptr_bytes) { | ||
| 677 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); | ||
| 678 | if (branch.allocReg(inst)) |reg| { | ||
| 679 | return MCValue{ .register = registerAlias(reg, abi_size) }; | ||
| 680 | } | ||
| 681 | } | ||
| 682 | const stack_offset = try self.allocMem(inst, abi_size, abi_align); | ||
| 683 | return MCValue{ .stack_offset = stack_offset }; | ||
| 684 | } | ||
| 685 | |||
| 686 | /// Does not "move" the instruction. | ||
| 687 | fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue { | ||
| 688 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | ||
| 689 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); | ||
| 690 | |||
| 691 | const reg = branch.allocReg(inst) orelse | ||
| 692 | return self.fail(inst.src, "TODO implement spilling register to stack", .{}); | ||
| 693 | const old_mcv = branch.inst_table.get(inst).?; | ||
| 694 | const new_mcv: MCValue = .{ .register = reg }; | ||
| 695 | try self.genSetReg(inst.src, reg, old_mcv); | ||
| 696 | return new_mcv; | ||
| 697 | } | ||
| 698 | |||
| 699 | fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue { | ||
| 700 | const stack_offset = try self.allocMemPtr(&inst.base); | ||
| 701 | return MCValue{ .ptr_stack_offset = stack_offset }; | ||
| 702 | } | ||
| 703 | |||
| 467 | fn genFloatCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue { | 704 | fn genFloatCast(self: *Self, inst: *ir.Inst.UnOp) !MCValue { |
| 468 | // No side effects, so if it's unreferenced, do nothing. | 705 | // No side effects, so if it's unreferenced, do nothing. |
| 469 | if (inst.base.isUnused()) | 706 | if (inst.base.isUnused()) |
| ... | @@ -542,6 +779,87 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -542,6 +779,87 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 542 | } | 779 | } |
| 543 | } | 780 | } |
| 544 | 781 | ||
| 782 | fn genLoad(self: *Self, inst: *ir.Inst.UnOp) !MCValue { | ||
| 783 | const elem_ty = inst.base.ty; | ||
| 784 | if (!elem_ty.hasCodeGenBits()) | ||
| 785 | return MCValue.none; | ||
| 786 | const ptr = try self.resolveInst(inst.operand); | ||
| 787 | const is_volatile = inst.operand.ty.isVolatilePtr(); | ||
| 788 | if (inst.base.isUnused() and !is_volatile) | ||
| 789 | return MCValue.dead; | ||
| 790 | const dst_mcv: MCValue = blk: { | ||
| 791 | if (inst.base.operandDies(0) and ptr.isMutable()) { | ||
| 792 | // The MCValue that holds the pointer can be re-used as the value. | ||
| 793 | // TODO track this in the register/stack allocation metadata. | ||
| 794 | break :blk ptr; | ||
| 795 | } else { | ||
| 796 | break :blk try self.allocRegOrMem(&inst.base); | ||
| 797 | } | ||
| 798 | }; | ||
| 799 | switch (ptr) { | ||
| 800 | .none => unreachable, | ||
| 801 | .undef => unreachable, | ||
| 802 | .unreach => unreachable, | ||
| 803 | .dead => unreachable, | ||
| 804 | .compare_flags_unsigned => unreachable, | ||
| 805 | .compare_flags_signed => unreachable, | ||
| 806 | .immediate => |imm| try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .memory = imm }), | ||
| 807 | .ptr_stack_offset => |off| try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .stack_offset = off }), | ||
| 808 | .ptr_embedded_in_code => |off| { | ||
| 809 | try self.setRegOrMem(inst.base.src, elem_ty, dst_mcv, .{ .embedded_in_code = off }); | ||
| 810 | }, | ||
| 811 | .embedded_in_code => { | ||
| 812 | return self.fail(inst.base.src, "TODO implement loading from MCValue.embedded_in_code", .{}); | ||
| 813 | }, | ||
| 814 | .register => { | ||
| 815 | return self.fail(inst.base.src, "TODO implement loading from MCValue.register", .{}); | ||
| 816 | }, | ||
| 817 | .memory => { | ||
| 818 | return self.fail(inst.base.src, "TODO implement loading from MCValue.memory", .{}); | ||
| 819 | }, | ||
| 820 | .stack_offset => { | ||
| 821 | return self.fail(inst.base.src, "TODO implement loading from MCValue.stack_offset", .{}); | ||
| 822 | }, | ||
| 823 | } | ||
| 824 | return dst_mcv; | ||
| 825 | } | ||
| 826 | |||
| 827 | fn genStore(self: *Self, inst: *ir.Inst.BinOp) !MCValue { | ||
| 828 | const ptr = try self.resolveInst(inst.lhs); | ||
| 829 | const value = try self.resolveInst(inst.rhs); | ||
| 830 | const elem_ty = inst.rhs.ty; | ||
| 831 | switch (ptr) { | ||
| 832 | .none => unreachable, | ||
| 833 | .undef => unreachable, | ||
| 834 | .unreach => unreachable, | ||
| 835 | .dead => unreachable, | ||
| 836 | .compare_flags_unsigned => unreachable, | ||
| 837 | .compare_flags_signed => unreachable, | ||
| 838 | .immediate => |imm| { | ||
| 839 | try self.setRegOrMem(inst.base.src, elem_ty, .{ .memory = imm }, value); | ||
| 840 | }, | ||
| 841 | .ptr_stack_offset => |off| { | ||
| 842 | try self.genSetStack(inst.base.src, elem_ty, off, value); | ||
| 843 | }, | ||
| 844 | .ptr_embedded_in_code => |off| { | ||
| 845 | try self.setRegOrMem(inst.base.src, elem_ty, .{ .embedded_in_code = off }, value); | ||
| 846 | }, | ||
| 847 | .embedded_in_code => { | ||
| 848 | return self.fail(inst.base.src, "TODO implement storing to MCValue.embedded_in_code", .{}); | ||
| 849 | }, | ||
| 850 | .register => { | ||
| 851 | return self.fail(inst.base.src, "TODO implement storing to MCValue.register", .{}); | ||
| 852 | }, | ||
| 853 | .memory => { | ||
| 854 | return self.fail(inst.base.src, "TODO implement storing to MCValue.memory", .{}); | ||
| 855 | }, | ||
| 856 | .stack_offset => { | ||
| 857 | return self.fail(inst.base.src, "TODO implement storing to MCValue.stack_offset", .{}); | ||
| 858 | }, | ||
| 859 | } | ||
| 860 | return .none; | ||
| 861 | } | ||
| 862 | |||
| 545 | fn genSub(self: *Self, inst: *ir.Inst.BinOp) !MCValue { | 863 | fn genSub(self: *Self, inst: *ir.Inst.BinOp) !MCValue { |
| 546 | // No side effects, so if it's unreferenced, do nothing. | 864 | // No side effects, so if it's unreferenced, do nothing. |
| 547 | if (inst.base.isUnused()) | 865 | if (inst.base.isUnused()) |
| ... | @@ -609,7 +927,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -609,7 +927,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 609 | // and as a register. | 927 | // and as a register. |
| 610 | switch (src_mcv) { | 928 | switch (src_mcv) { |
| 611 | .immediate => |imm| { | 929 | .immediate => |imm| { |
| 612 | if (imm > std.math.maxInt(u31)) { | 930 | if (imm > math.maxInt(u31)) { |
| 613 | src_mcv = try self.copyToNewRegister(src_inst); | 931 | src_mcv = try self.copyToNewRegister(src_inst); |
| 614 | } | 932 | } |
| 615 | }, | 933 | }, |
| ... | @@ -624,13 +942,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -624,13 +942,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 624 | fn genX8664BinMathCode(self: *Self, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void { | 942 | fn genX8664BinMathCode(self: *Self, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void { |
| 625 | switch (dst_mcv) { | 943 | switch (dst_mcv) { |
| 626 | .none => unreachable, | 944 | .none => unreachable, |
| 945 | .undef => unreachable, | ||
| 627 | .dead, .unreach, .immediate => unreachable, | 946 | .dead, .unreach, .immediate => unreachable, |
| 628 | .compare_flags_unsigned => unreachable, | 947 | .compare_flags_unsigned => unreachable, |
| 629 | .compare_flags_signed => unreachable, | 948 | .compare_flags_signed => unreachable, |
| 949 | .ptr_stack_offset => unreachable, | ||
| 950 | .ptr_embedded_in_code => unreachable, | ||
| 630 | .register => |dst_reg| { | 951 | .register => |dst_reg| { |
| 631 | switch (src_mcv) { | 952 | switch (src_mcv) { |
| 632 | .none => unreachable, | 953 | .none => unreachable, |
| 954 | .undef => try self.genSetReg(src, dst_reg, .undef), | ||
| 633 | .dead, .unreach => unreachable, | 955 | .dead, .unreach => unreachable, |
| 956 | .ptr_stack_offset => unreachable, | ||
| 957 | .ptr_embedded_in_code => unreachable, | ||
| 634 | .register => |src_reg| { | 958 | .register => |src_reg| { |
| 635 | self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); | 959 | self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); |
| 636 | self.code.appendSliceAssumeCapacity(&[_]u8{ mr + 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) }); | 960 | 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 { | ... | @@ -638,7 +962,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 638 | .immediate => |imm| { | 962 | .immediate => |imm| { |
| 639 | const imm32 = @intCast(u31, imm); // This case must be handled before calling genX8664BinMathCode. | 963 | const imm32 = @intCast(u31, imm); // This case must be handled before calling genX8664BinMathCode. |
| 640 | // 81 /opx id | 964 | // 81 /opx id |
| 641 | if (imm32 <= std.math.maxInt(u7)) { | 965 | if (imm32 <= math.maxInt(u7)) { |
| 642 | self.rex(.{ .b = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); | 966 | self.rex(.{ .b = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); |
| 643 | self.code.appendSliceAssumeCapacity(&[_]u8{ | 967 | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 644 | 0x83, | 968 | 0x83, |
| ... | @@ -699,26 +1023,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -699,26 +1023,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 699 | .i386, .x86_64 => { | 1023 | .i386, .x86_64 => { |
| 700 | try self.code.append(0xcc); // int3 | 1024 | try self.code.append(0xcc); // int3 |
| 701 | }, | 1025 | }, |
| 1026 | .riscv64 => { | ||
| 1027 | const full = @bitCast(u32, instructions.CallBreak{ | ||
| 1028 | .mode = @enumToInt(instructions.CallBreak.Mode.ebreak), | ||
| 1029 | }); | ||
| 1030 | |||
| 1031 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), full); | ||
| 1032 | }, | ||
| 702 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), | 1033 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), |
| 703 | } | 1034 | } |
| 704 | return .none; | 1035 | return .none; |
| 705 | } | 1036 | } |
| 706 | 1037 | ||
| 707 | fn genCall(self: *Self, inst: *ir.Inst.Call) !MCValue { | 1038 | fn genCall(self: *Self, inst: *ir.Inst.Call) !MCValue { |
| 708 | const fn_ty = inst.func.ty; | 1039 | var info = try self.resolveCallingConventionValues(inst.base.src, inst.func.ty); |
| 709 | const cc = fn_ty.fnCallingConvention(); | 1040 | defer info.deinit(self); |
| 710 | const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen()); | ||
| 711 | defer self.gpa.free(param_types); | ||
| 712 | fn_ty.fnParamTypes(param_types); | ||
| 713 | var mc_args = try self.gpa.alloc(MCValue, param_types.len); | ||
| 714 | defer self.gpa.free(mc_args); | ||
| 715 | const stack_byte_count = try self.resolveParameters(inst.base.src, cc, param_types, mc_args); | ||
| 716 | 1041 | ||
| 717 | switch (arch) { | 1042 | switch (arch) { |
| 718 | .x86_64 => { | 1043 | .x86_64 => { |
| 719 | for (mc_args) |mc_arg, arg_i| { | 1044 | for (info.args) |mc_arg, arg_i| { |
| 720 | const arg = inst.args[arg_i]; | 1045 | const arg = inst.args[arg_i]; |
| 721 | const arg_mcv = try self.resolveInst(inst.args[arg_i]); | 1046 | const arg_mcv = try self.resolveInst(inst.args[arg_i]); |
| 1047 | // Here we do not use setRegOrMem even though the logic is similar, because | ||
| 1048 | // the function call will move the stack pointer, so the offsets are different. | ||
| 722 | switch (mc_arg) { | 1049 | switch (mc_arg) { |
| 723 | .none => continue, | 1050 | .none => continue, |
| 724 | .register => |reg| { | 1051 | .register => |reg| { |
| ... | @@ -730,6 +1057,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -730,6 +1057,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 730 | // mov qword ptr [rsp + stack_offset], x | 1057 | // mov qword ptr [rsp + stack_offset], x |
| 731 | return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); | 1058 | return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); |
| 732 | }, | 1059 | }, |
| 1060 | .ptr_stack_offset => { | ||
| 1061 | return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{}); | ||
| 1062 | }, | ||
| 1063 | .ptr_embedded_in_code => { | ||
| 1064 | return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); | ||
| 1065 | }, | ||
| 1066 | .undef => unreachable, | ||
| 733 | .immediate => unreachable, | 1067 | .immediate => unreachable, |
| 734 | .unreach => unreachable, | 1068 | .unreach => unreachable, |
| 735 | .dead => unreachable, | 1069 | .dead => unreachable, |
| ... | @@ -758,30 +1092,86 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -758,30 +1092,86 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 758 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | 1092 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); |
| 759 | } | 1093 | } |
| 760 | }, | 1094 | }, |
| 1095 | .riscv64 => { | ||
| 1096 | if (info.args.len > 0) return self.fail(inst.base.src, "TODO implement fn args for {}", .{self.target.cpu.arch}); | ||
| 1097 | |||
| 1098 | if (inst.func.cast(ir.Inst.Constant)) |func_inst| { | ||
| 1099 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { | ||
| 1100 | const func = func_val.func; | ||
| 1101 | const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?]; | ||
| 1102 | const ptr_bits = self.target.cpu.arch.ptrBitWidth(); | ||
| 1103 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); | ||
| 1104 | const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes); | ||
| 1105 | |||
| 1106 | try self.genSetReg(inst.base.src, .ra, .{ .memory = got_addr }); | ||
| 1107 | const jalr = instructions.Jalr{ | ||
| 1108 | .rd = Register.ra.id(), | ||
| 1109 | .rs1 = Register.ra.id(), | ||
| 1110 | .offset = 0, | ||
| 1111 | }; | ||
| 1112 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), @bitCast(u32, jalr)); | ||
| 1113 | } else { | ||
| 1114 | return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); | ||
| 1115 | } | ||
| 1116 | } else { | ||
| 1117 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | ||
| 1118 | } | ||
| 1119 | }, | ||
| 761 | else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), | 1120 | else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), |
| 762 | } | 1121 | } |
| 763 | 1122 | ||
| 764 | const return_type = fn_ty.fnReturnType(); | 1123 | return info.return_value; |
| 765 | switch (return_type.zigTypeTag()) { | 1124 | } |
| 766 | .Void => return MCValue{ .none = {} }, | 1125 | |
| 767 | .NoReturn => return MCValue{ .unreach = {} }, | 1126 | fn genRef(self: *Self, inst: *ir.Inst.UnOp) !MCValue { |
| 768 | else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}), | 1127 | const operand = try self.resolveInst(inst.operand); |
| 1128 | switch (operand) { | ||
| 1129 | .unreach => unreachable, | ||
| 1130 | .dead => unreachable, | ||
| 1131 | .none => return .none, | ||
| 1132 | |||
| 1133 | .immediate, | ||
| 1134 | .register, | ||
| 1135 | .ptr_stack_offset, | ||
| 1136 | .ptr_embedded_in_code, | ||
| 1137 | .compare_flags_unsigned, | ||
| 1138 | .compare_flags_signed, | ||
| 1139 | => { | ||
| 1140 | const stack_offset = try self.allocMemPtr(&inst.base); | ||
| 1141 | try self.genSetStack(inst.base.src, inst.operand.ty, stack_offset, operand); | ||
| 1142 | return MCValue{ .ptr_stack_offset = stack_offset }; | ||
| 1143 | }, | ||
| 1144 | |||
| 1145 | .stack_offset => |offset| return MCValue{ .ptr_stack_offset = offset }, | ||
| 1146 | .embedded_in_code => |offset| return MCValue{ .ptr_embedded_in_code = offset }, | ||
| 1147 | .memory => |vaddr| return MCValue{ .immediate = vaddr }, | ||
| 1148 | |||
| 1149 | .undef => return self.fail(inst.base.src, "TODO implement ref on an undefined value", .{}), | ||
| 769 | } | 1150 | } |
| 770 | } | 1151 | } |
| 771 | 1152 | ||
| 772 | fn ret(self: *Self, src: usize, mcv: MCValue) !MCValue { | 1153 | fn ret(self: *Self, src: usize, mcv: MCValue) !MCValue { |
| 773 | if (mcv != .none) { | 1154 | const ret_ty = self.fn_type.fnReturnType(); |
| 774 | return self.fail(src, "TODO implement return with non-void operand", .{}); | 1155 | try self.setRegOrMem(src, ret_ty, self.ret_mcv, mcv); |
| 775 | } | ||
| 776 | switch (arch) { | 1156 | switch (arch) { |
| 777 | .i386 => { | 1157 | .i386 => { |
| 778 | try self.code.append(0xc3); // ret | 1158 | try self.code.append(0xc3); // ret |
| 779 | }, | 1159 | }, |
| 780 | .x86_64 => { | 1160 | .x86_64 => { |
| 781 | try self.code.appendSlice(&[_]u8{ | 1161 | // TODO when implementing defer, this will need to jump to the appropriate defer expression. |
| 782 | 0x5d, // pop rbp | 1162 | // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction |
| 783 | 0xc3, // ret | 1163 | // which is available if the jump is 127 bytes or less forward. |
| 784 | }); | 1164 | try self.code.resize(self.code.items.len + 5); |
| 1165 | self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 | ||
| 1166 | try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4); | ||
| 1167 | }, | ||
| 1168 | .riscv64 => { | ||
| 1169 | const jalr = instructions.Jalr{ | ||
| 1170 | .rd = Register.zero.id(), | ||
| 1171 | .rs1 = Register.ra.id(), | ||
| 1172 | .offset = 0, | ||
| 1173 | }; | ||
| 1174 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), @bitCast(u32, jalr)); | ||
| 785 | }, | 1175 | }, |
| 786 | else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), | 1176 | else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), |
| 787 | } | 1177 | } |
| ... | @@ -797,7 +1187,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -797,7 +1187,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 797 | return self.ret(inst.base.src, .none); | 1187 | return self.ret(inst.base.src, .none); |
| 798 | } | 1188 | } |
| 799 | 1189 | ||
| 800 | fn genCmp(self: *Self, inst: *ir.Inst.BinOp, op: std.math.CompareOperator) !MCValue { | 1190 | fn genCmp(self: *Self, inst: *ir.Inst.BinOp, op: math.CompareOperator) !MCValue { |
| 801 | // No side effects, so if it's unreferenced, do nothing. | 1191 | // No side effects, so if it's unreferenced, do nothing. |
| 802 | if (inst.base.isUnused()) | 1192 | if (inst.base.isUnused()) |
| 803 | return MCValue.dead; | 1193 | return MCValue.dead; |
| ... | @@ -830,6 +1220,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -830,6 +1220,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 830 | } | 1220 | } |
| 831 | } | 1221 | } |
| 832 | 1222 | ||
| 1223 | fn genDbgStmt(self: *Self, inst: *ir.Inst.NoOp) !MCValue { | ||
| 1224 | try self.dbgAdvancePCAndLine(inst.base.src); | ||
| 1225 | return MCValue.none; | ||
| 1226 | } | ||
| 1227 | |||
| 833 | fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue { | 1228 | fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue { |
| 834 | switch (arch) { | 1229 | switch (arch) { |
| 835 | .x86_64 => { | 1230 | .x86_64 => { |
| ... | @@ -865,7 +1260,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -865,7 +1260,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 865 | // test reg, 1 | 1260 | // test reg, 1 |
| 866 | // TODO detect al, ax, eax | 1261 | // TODO detect al, ax, eax |
| 867 | try self.code.ensureCapacity(self.code.items.len + 4); | 1262 | try self.code.ensureCapacity(self.code.items.len + 4); |
| 868 | self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 }); | 1263 | // TODO audit this codegen: we force w = true here to make |
| 1264 | // the value affect the big register | ||
| 1265 | self.rex(.{ .b = reg.isExtended(), .w = true }); | ||
| 869 | self.code.appendSliceAssumeCapacity(&[_]u8{ | 1266 | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 870 | 0xf6, | 1267 | 0xf6, |
| 871 | @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()), | 1268 | @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()), |
| ... | @@ -921,7 +1318,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -921,7 +1318,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 921 | switch (reloc) { | 1318 | switch (reloc) { |
| 922 | .rel32 => |pos| { | 1319 | .rel32 => |pos| { |
| 923 | const amt = self.code.items.len - (pos + 4); | 1320 | const amt = self.code.items.len - (pos + 4); |
| 924 | const s32_amt = std.math.cast(i32, amt) catch | 1321 | // If it wouldn't jump at all, elide it. |
| 1322 | if (amt == 0) { | ||
| 1323 | self.code.items.len -= 5; | ||
| 1324 | return; | ||
| 1325 | } | ||
| 1326 | const s32_amt = math.cast(i32, amt) catch | ||
| 925 | return self.fail(src, "unable to perform relocation: jump too far", .{}); | 1327 | return self.fail(src, "unable to perform relocation: jump too far", .{}); |
| 926 | mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt); | 1328 | mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt); |
| 927 | }, | 1329 | }, |
| ... | @@ -963,36 +1365,72 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -963,36 +1365,72 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 963 | fn genAsm(self: *Self, inst: *ir.Inst.Assembly) !MCValue { | 1365 | fn genAsm(self: *Self, inst: *ir.Inst.Assembly) !MCValue { |
| 964 | if (!inst.is_volatile and inst.base.isUnused()) | 1366 | if (!inst.is_volatile and inst.base.isUnused()) |
| 965 | return MCValue.dead; | 1367 | return MCValue.dead; |
| 966 | if (arch != .x86_64 and arch != .i386) { | 1368 | switch (arch) { |
| 967 | return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{}); | 1369 | .riscv64 => { |
| 968 | } | 1370 | for (inst.inputs) |input, i| { |
| 969 | for (inst.inputs) |input, i| { | 1371 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { |
| 970 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { | 1372 | return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); |
| 971 | return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); | 1373 | } |
| 972 | } | 1374 | const reg_name = input[1 .. input.len - 1]; |
| 973 | const reg_name = input[1 .. input.len - 1]; | 1375 | const reg = parseRegName(reg_name) orelse |
| 974 | const reg = parseRegName(reg_name) orelse | 1376 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); |
| 975 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | 1377 | const arg = try self.resolveInst(inst.args[i]); |
| 976 | const arg = try self.resolveInst(inst.args[i]); | 1378 | try self.genSetReg(inst.base.src, reg, arg); |
| 977 | try self.genSetReg(inst.base.src, reg, arg); | 1379 | } |
| 978 | } | ||
| 979 | 1380 | ||
| 980 | if (mem.eql(u8, inst.asm_source, "syscall")) { | 1381 | if (mem.eql(u8, inst.asm_source, "ecall")) { |
| 981 | try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 }); | 1382 | const full = @bitCast(u32, instructions.CallBreak{ |
| 982 | } else { | 1383 | .mode = @enumToInt(instructions.CallBreak.Mode.ecall), |
| 983 | return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{}); | 1384 | }); |
| 984 | } | ||
| 985 | 1385 | ||
| 986 | if (inst.output) |output| { | 1386 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), full); |
| 987 | if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { | 1387 | } else { |
| 988 | return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); | 1388 | return self.fail(inst.base.src, "TODO implement support for more riscv64 assembly instructions", .{}); |
| 989 | } | 1389 | } |
| 990 | const reg_name = output[2 .. output.len - 1]; | 1390 | |
| 991 | const reg = parseRegName(reg_name) orelse | 1391 | if (inst.output) |output| { |
| 992 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | 1392 | if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { |
| 993 | return MCValue{ .register = reg }; | 1393 | return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); |
| 994 | } else { | 1394 | } |
| 995 | return MCValue.none; | 1395 | const reg_name = output[2 .. output.len - 1]; |
| 1396 | const reg = parseRegName(reg_name) orelse | ||
| 1397 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | ||
| 1398 | return MCValue{ .register = reg }; | ||
| 1399 | } else { | ||
| 1400 | return MCValue.none; | ||
| 1401 | } | ||
| 1402 | }, | ||
| 1403 | .x86_64, .i386 => { | ||
| 1404 | for (inst.inputs) |input, i| { | ||
| 1405 | if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { | ||
| 1406 | return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); | ||
| 1407 | } | ||
| 1408 | const reg_name = input[1 .. input.len - 1]; | ||
| 1409 | const reg = parseRegName(reg_name) orelse | ||
| 1410 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | ||
| 1411 | const arg = try self.resolveInst(inst.args[i]); | ||
| 1412 | try self.genSetReg(inst.base.src, reg, arg); | ||
| 1413 | } | ||
| 1414 | |||
| 1415 | if (mem.eql(u8, inst.asm_source, "syscall")) { | ||
| 1416 | try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 }); | ||
| 1417 | } else { | ||
| 1418 | return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{}); | ||
| 1419 | } | ||
| 1420 | |||
| 1421 | if (inst.output) |output| { | ||
| 1422 | if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { | ||
| 1423 | return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); | ||
| 1424 | } | ||
| 1425 | const reg_name = output[2 .. output.len - 1]; | ||
| 1426 | const reg = parseRegName(reg_name) orelse | ||
| 1427 | return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); | ||
| 1428 | return MCValue{ .register = reg }; | ||
| 1429 | } else { | ||
| 1430 | return MCValue.none; | ||
| 1431 | } | ||
| 1432 | }, | ||
| 1433 | else => return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{}), | ||
| 996 | } | 1434 | } |
| 997 | } | 1435 | } |
| 998 | 1436 | ||
| ... | @@ -1024,15 +1462,211 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1024,15 +1462,211 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1024 | } | 1462 | } |
| 1025 | } | 1463 | } |
| 1026 | 1464 | ||
| 1027 | fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) error{ CodegenFail, OutOfMemory }!void { | 1465 | /// Sets the value without any modifications to register allocation metadata or stack allocation metadata. |
| 1466 | fn setRegOrMem(self: *Self, src: usize, ty: Type, loc: MCValue, val: MCValue) !void { | ||
| 1467 | switch (loc) { | ||
| 1468 | .none => return, | ||
| 1469 | .register => |reg| return self.genSetReg(src, reg, val), | ||
| 1470 | .stack_offset => |off| return self.genSetStack(src, ty, off, val), | ||
| 1471 | .memory => { | ||
| 1472 | return self.fail(src, "TODO implement setRegOrMem for memory", .{}); | ||
| 1473 | }, | ||
| 1474 | else => unreachable, | ||
| 1475 | } | ||
| 1476 | } | ||
| 1477 | |||
| 1478 | fn genSetStack(self: *Self, src: usize, ty: Type, stack_offset: u32, mcv: MCValue) InnerError!void { | ||
| 1028 | switch (arch) { | 1479 | switch (arch) { |
| 1029 | .x86_64 => switch (mcv) { | 1480 | .x86_64 => switch (mcv) { |
| 1030 | .dead => unreachable, | 1481 | .dead => unreachable, |
| 1031 | .none => unreachable, | 1482 | .ptr_stack_offset => unreachable, |
| 1032 | .unreach => unreachable, | 1483 | .ptr_embedded_in_code => unreachable, |
| 1484 | .unreach, .none => return, // Nothing to do. | ||
| 1485 | .undef => { | ||
| 1486 | if (!self.wantSafety()) | ||
| 1487 | return; // The already existing value will do just fine. | ||
| 1488 | // TODO Upgrade this to a memset call when we have that available. | ||
| 1489 | switch (ty.abiSize(self.target.*)) { | ||
| 1490 | 1 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaa }), | ||
| 1491 | 2 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaa }), | ||
| 1492 | 4 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa }), | ||
| 1493 | 8 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), | ||
| 1494 | else => return self.fail(src, "TODO implement memset", .{}), | ||
| 1495 | } | ||
| 1496 | }, | ||
| 1497 | .compare_flags_unsigned => |op| { | ||
| 1498 | return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{}); | ||
| 1499 | }, | ||
| 1500 | .compare_flags_signed => |op| { | ||
| 1501 | return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{}); | ||
| 1502 | }, | ||
| 1503 | .immediate => |x_big| { | ||
| 1504 | const abi_size = ty.abiSize(self.target.*); | ||
| 1505 | const adj_off = stack_offset + abi_size; | ||
| 1506 | if (adj_off > 128) { | ||
| 1507 | return self.fail(src, "TODO implement set stack variable with large stack offset", .{}); | ||
| 1508 | } | ||
| 1509 | try self.code.ensureCapacity(self.code.items.len + 8); | ||
| 1510 | switch (abi_size) { | ||
| 1511 | 1 => { | ||
| 1512 | return self.fail(src, "TODO implement set abi_size=1 stack variable with immediate", .{}); | ||
| 1513 | }, | ||
| 1514 | 2 => { | ||
| 1515 | return self.fail(src, "TODO implement set abi_size=2 stack variable with immediate", .{}); | ||
| 1516 | }, | ||
| 1517 | 4 => { | ||
| 1518 | const x = @intCast(u32, x_big); | ||
| 1519 | // We have a positive stack offset value but we want a twos complement negative | ||
| 1520 | // offset from rbp, which is at the top of the stack frame. | ||
| 1521 | const negative_offset = @intCast(i8, -@intCast(i32, adj_off)); | ||
| 1522 | const twos_comp = @bitCast(u8, negative_offset); | ||
| 1523 | // mov DWORD PTR [rbp+offset], immediate | ||
| 1524 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp }); | ||
| 1525 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); | ||
| 1526 | }, | ||
| 1527 | 8 => { | ||
| 1528 | return self.fail(src, "TODO implement set abi_size=8 stack variable with immediate", .{}); | ||
| 1529 | }, | ||
| 1530 | else => { | ||
| 1531 | return self.fail(src, "TODO implement set abi_size=large stack variable with immediate", .{}); | ||
| 1532 | }, | ||
| 1533 | } | ||
| 1534 | if (x_big <= math.maxInt(u32)) {} else { | ||
| 1535 | return self.fail(src, "TODO implement set stack variable with large immediate", .{}); | ||
| 1536 | } | ||
| 1537 | }, | ||
| 1538 | .embedded_in_code => |code_offset| { | ||
| 1539 | return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{}); | ||
| 1540 | }, | ||
| 1541 | .register => |reg| { | ||
| 1542 | const abi_size = ty.abiSize(self.target.*); | ||
| 1543 | const adj_off = stack_offset + abi_size; | ||
| 1544 | try self.code.ensureCapacity(self.code.items.len + 7); | ||
| 1545 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); | ||
| 1546 | const reg_id: u8 = @truncate(u3, reg.id()); | ||
| 1547 | if (adj_off <= 128) { | ||
| 1548 | // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx | ||
| 1549 | const RM = @as(u8, 0b01_000_101) | (reg_id << 3); | ||
| 1550 | const negative_offset = @intCast(i8, -@intCast(i32, adj_off)); | ||
| 1551 | const twos_comp = @bitCast(u8, negative_offset); | ||
| 1552 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM, twos_comp }); | ||
| 1553 | } else if (adj_off <= 2147483648) { | ||
| 1554 | // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx | ||
| 1555 | const RM = @as(u8, 0b10_000_101) | (reg_id << 3); | ||
| 1556 | const negative_offset = @intCast(i32, -@intCast(i33, adj_off)); | ||
| 1557 | const twos_comp = @bitCast(u32, negative_offset); | ||
| 1558 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM }); | ||
| 1559 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp); | ||
| 1560 | } else { | ||
| 1561 | return self.fail(src, "stack offset too large", .{}); | ||
| 1562 | } | ||
| 1563 | }, | ||
| 1564 | .memory => |vaddr| { | ||
| 1565 | return self.fail(src, "TODO implement set stack variable from memory vaddr", .{}); | ||
| 1566 | }, | ||
| 1567 | .stack_offset => |off| { | ||
| 1568 | if (stack_offset == off) | ||
| 1569 | return; // Copy stack variable to itself; nothing to do. | ||
| 1570 | return self.fail(src, "TODO implement copy stack variable to stack variable", .{}); | ||
| 1571 | }, | ||
| 1572 | }, | ||
| 1573 | else => return self.fail(src, "TODO implement getSetStack for {}", .{self.target.cpu.arch}), | ||
| 1574 | } | ||
| 1575 | } | ||
| 1576 | |||
| 1577 | fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) InnerError!void { | ||
| 1578 | switch (arch) { | ||
| 1579 | .riscv64 => switch (mcv) { | ||
| 1580 | .dead => unreachable, | ||
| 1581 | .ptr_stack_offset => unreachable, | ||
| 1582 | .ptr_embedded_in_code => unreachable, | ||
| 1583 | .unreach, .none => return, // Nothing to do. | ||
| 1584 | .undef => { | ||
| 1585 | if (!self.wantSafety()) | ||
| 1586 | return; // The already existing value will do just fine. | ||
| 1587 | // Write the debug undefined value. | ||
| 1588 | return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }); | ||
| 1589 | }, | ||
| 1590 | .immediate => |unsigned_x| { | ||
| 1591 | const x = @bitCast(i64, unsigned_x); | ||
| 1592 | if (math.minInt(i12) <= x and x <= math.maxInt(i12)) { | ||
| 1593 | const instruction = @bitCast(u32, instructions.Addi{ | ||
| 1594 | .mode = @enumToInt(instructions.Addi.Mode.addi), | ||
| 1595 | .imm = @truncate(i12, x), | ||
| 1596 | .rs1 = Register.zero.id(), | ||
| 1597 | .rd = reg.id(), | ||
| 1598 | }); | ||
| 1599 | |||
| 1600 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), instruction); | ||
| 1601 | return; | ||
| 1602 | } | ||
| 1603 | if (math.minInt(i32) <= x and x <= math.maxInt(i32)) { | ||
| 1604 | const split = @bitCast(packed struct { | ||
| 1605 | low12: i12, | ||
| 1606 | up20: i20, | ||
| 1607 | }, @truncate(i32, x)); | ||
| 1608 | if (split.low12 < 0) return self.fail(src, "TODO support riscv64 genSetReg i32 immediates with 12th bit set to 1", .{}); | ||
| 1609 | |||
| 1610 | const lui = @bitCast(u32, instructions.Lui{ | ||
| 1611 | .imm = split.up20, | ||
| 1612 | .rd = reg.id(), | ||
| 1613 | }); | ||
| 1614 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), lui); | ||
| 1615 | |||
| 1616 | const addi = @bitCast(u32, instructions.Addi{ | ||
| 1617 | .mode = @enumToInt(instructions.Addi.Mode.addi), | ||
| 1618 | .imm = @truncate(i12, split.low12), | ||
| 1619 | .rs1 = reg.id(), | ||
| 1620 | .rd = reg.id(), | ||
| 1621 | }); | ||
| 1622 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), addi); | ||
| 1623 | return; | ||
| 1624 | } | ||
| 1625 | // li rd, immediate | ||
| 1626 | // "Myriad sequences" | ||
| 1627 | return self.fail(src, "TODO genSetReg 33-64 bit immediates for riscv64", .{}); // glhf | ||
| 1628 | }, | ||
| 1629 | .memory => |addr| { | ||
| 1630 | // The value is in memory at a hard-coded address. | ||
| 1631 | // If the type is a pointer, it means the pointer address is at this memory location. | ||
| 1632 | try self.genSetReg(src, reg, .{ .immediate = addr }); | ||
| 1633 | |||
| 1634 | const ld = @bitCast(u32, instructions.Load{ | ||
| 1635 | .mode = @enumToInt(instructions.Load.Mode.ld), | ||
| 1636 | .rs1 = reg.id(), | ||
| 1637 | .rd = reg.id(), | ||
| 1638 | .offset = 0, | ||
| 1639 | }); | ||
| 1640 | |||
| 1641 | mem.writeIntLittle(u32, try self.code.addManyAsArray(4), ld); | ||
| 1642 | // LOAD imm=[i12 offset = 0], rs1 = | ||
| 1643 | |||
| 1644 | // return self.fail("TODO implement genSetReg memory for riscv64"); | ||
| 1645 | }, | ||
| 1646 | else => return self.fail(src, "TODO implement getSetReg for riscv64 {}", .{mcv}), | ||
| 1647 | }, | ||
| 1648 | .x86_64 => switch (mcv) { | ||
| 1649 | .dead => unreachable, | ||
| 1650 | .ptr_stack_offset => unreachable, | ||
| 1651 | .ptr_embedded_in_code => unreachable, | ||
| 1652 | .unreach, .none => return, // Nothing to do. | ||
| 1653 | .undef => { | ||
| 1654 | if (!self.wantSafety()) | ||
| 1655 | return; // The already existing value will do just fine. | ||
| 1656 | // Write the debug undefined value. | ||
| 1657 | switch (reg.size()) { | ||
| 1658 | 8 => return self.genSetReg(src, reg, .{ .immediate = 0xaa }), | ||
| 1659 | 16 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaa }), | ||
| 1660 | 32 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }), | ||
| 1661 | 64 => return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), | ||
| 1662 | else => unreachable, | ||
| 1663 | } | ||
| 1664 | }, | ||
| 1033 | .compare_flags_unsigned => |op| { | 1665 | .compare_flags_unsigned => |op| { |
| 1034 | try self.code.ensureCapacity(self.code.items.len + 3); | 1666 | try self.code.ensureCapacity(self.code.items.len + 3); |
| 1035 | self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 }); | 1667 | // TODO audit this codegen: we force w = true here to make |
| 1668 | // the value affect the big register | ||
| 1669 | self.rex(.{ .b = reg.isExtended(), .w = true }); | ||
| 1036 | const opcode: u8 = switch (op) { | 1670 | const opcode: u8 = switch (op) { |
| 1037 | .gte => 0x93, | 1671 | .gte => 0x93, |
| 1038 | .gt => 0x97, | 1672 | .gt => 0x97, |
| ... | @@ -1048,9 +1682,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1048,9 +1682,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1048 | return self.fail(src, "TODO set register with compare flags value (signed)", .{}); | 1682 | return self.fail(src, "TODO set register with compare flags value (signed)", .{}); |
| 1049 | }, | 1683 | }, |
| 1050 | .immediate => |x| { | 1684 | .immediate => |x| { |
| 1051 | if (reg.size() != 64) { | ||
| 1052 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | ||
| 1053 | } | ||
| 1054 | // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit | 1685 | // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit |
| 1055 | // register is the fastest way to zero a register. | 1686 | // register is the fastest way to zero a register. |
| 1056 | if (x == 0) { | 1687 | if (x == 0) { |
| ... | @@ -1071,7 +1702,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1071,7 +1702,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1071 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x31, 0xC0 | id << 3 | id }); | 1702 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x31, 0xC0 | id << 3 | id }); |
| 1072 | return; | 1703 | return; |
| 1073 | } | 1704 | } |
| 1074 | if (x <= std.math.maxInt(u32)) { | 1705 | if (x <= math.maxInt(u32)) { |
| 1075 | // Next best case: if we set the lower four bytes, the upper four will be zeroed. | 1706 | // Next best case: if we set the lower four bytes, the upper four will be zeroed. |
| 1076 | // | 1707 | // |
| 1077 | // The encoding for `mov IMM32 -> REG` is (0xB8 + R) IMM. | 1708 | // The encoding for `mov IMM32 -> REG` is (0xB8 + R) IMM. |
| ... | @@ -1103,16 +1734,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1103,16 +1734,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1103 | // | 1734 | // |
| 1104 | // In this case, the encoding of the REX byte is 0b0100100B | 1735 | // In this case, the encoding of the REX byte is 0b0100100B |
| 1105 | try self.code.ensureCapacity(self.code.items.len + 10); | 1736 | try self.code.ensureCapacity(self.code.items.len + 10); |
| 1106 | self.rex(.{ .w = true, .b = reg.isExtended() }); | 1737 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); |
| 1107 | self.code.items.len += 9; | 1738 | self.code.items.len += 9; |
| 1108 | self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111); | 1739 | self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111); |
| 1109 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; | 1740 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; |
| 1110 | mem.writeIntLittle(u64, imm_ptr, x); | 1741 | mem.writeIntLittle(u64, imm_ptr, x); |
| 1111 | }, | 1742 | }, |
| 1112 | .embedded_in_code => |code_offset| { | 1743 | .embedded_in_code => |code_offset| { |
| 1113 | if (reg.size() != 64) { | ||
| 1114 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | ||
| 1115 | } | ||
| 1116 | // We need the offset from RIP in a signed i32 twos complement. | 1744 | // We need the offset from RIP in a signed i32 twos complement. |
| 1117 | // The instruction is 7 bytes long and RIP points to the next instruction. | 1745 | // The instruction is 7 bytes long and RIP points to the next instruction. |
| 1118 | try self.code.ensureCapacity(self.code.items.len + 7); | 1746 | try self.code.ensureCapacity(self.code.items.len + 7); |
| ... | @@ -1120,7 +1748,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1120,7 +1748,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1120 | // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three | 1748 | // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three |
| 1121 | // bits as five. | 1749 | // bits as five. |
| 1122 | // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id. | 1750 | // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id. |
| 1123 | self.rex(.{ .w = true, .b = reg.isExtended() }); | 1751 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); |
| 1124 | self.code.items.len += 6; | 1752 | self.code.items.len += 6; |
| 1125 | const rip = self.code.items.len; | 1753 | const rip = self.code.items.len; |
| 1126 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); | 1754 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); |
| ... | @@ -1131,9 +1759,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1131,9 +1759,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1131 | mem.writeIntLittle(i32, imm_ptr, offset); | 1759 | mem.writeIntLittle(i32, imm_ptr, offset); |
| 1132 | }, | 1760 | }, |
| 1133 | .register => |src_reg| { | 1761 | .register => |src_reg| { |
| 1134 | if (reg.size() != 64) { | 1762 | // If the registers are the same, nothing to do. |
| 1135 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | 1763 | if (src_reg.id() == reg.id()) |
| 1136 | } | 1764 | return; |
| 1765 | |||
| 1137 | // This is a variant of 8B /r. Since we're using 64-bit moves, we require a REX. | 1766 | // This is a variant of 8B /r. Since we're using 64-bit moves, we require a REX. |
| 1138 | // This is thus three bytes: REX 0x8B R/M. | 1767 | // This is thus three bytes: REX 0x8B R/M. |
| 1139 | // If the destination is extended, the R field must be 1. | 1768 | // If the destination is extended, the R field must be 1. |
| ... | @@ -1141,15 +1770,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1141,15 +1770,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1141 | // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle | 1770 | // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle |
| 1142 | // three bits) contain the destination, and the R/M field (the lower three bits) contain the source. | 1771 | // three bits) contain the destination, and the R/M field (the lower three bits) contain the source. |
| 1143 | try self.code.ensureCapacity(self.code.items.len + 3); | 1772 | try self.code.ensureCapacity(self.code.items.len + 3); |
| 1144 | self.rex(.{ .w = true, .r = reg.isExtended(), .b = src_reg.isExtended() }); | 1773 | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended(), .b = src_reg.isExtended() }); |
| 1145 | const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111); | 1774 | const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111); |
| 1146 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R }); | 1775 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R }); |
| 1147 | }, | 1776 | }, |
| 1148 | .memory => |x| { | 1777 | .memory => |x| { |
| 1149 | if (reg.size() != 64) { | 1778 | if (x <= math.maxInt(u32)) { |
| 1150 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | ||
| 1151 | } | ||
| 1152 | if (x <= std.math.maxInt(u32)) { | ||
| 1153 | // Moving from memory to a register is a variant of `8B /r`. | 1779 | // Moving from memory to a register is a variant of `8B /r`. |
| 1154 | // Since we're using 64-bit moves, we require a REX. | 1780 | // Since we're using 64-bit moves, we require a REX. |
| 1155 | // This variant also requires a SIB, as it would otherwise be RIP-relative. | 1781 | // 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 { | ... | @@ -1158,7 +1784,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1158 | // 0b00RRR100, where RRR is the lower three bits of the register ID. | 1784 | // 0b00RRR100, where RRR is the lower three bits of the register ID. |
| 1159 | // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32. | 1785 | // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32. |
| 1160 | try self.code.ensureCapacity(self.code.items.len + 8); | 1786 | try self.code.ensureCapacity(self.code.items.len + 8); |
| 1161 | self.rex(.{ .w = true, .b = reg.isExtended() }); | 1787 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); |
| 1162 | self.code.appendSliceAssumeCapacity(&[_]u8{ | 1788 | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 1163 | 0x8B, | 1789 | 0x8B, |
| 1164 | 0x04 | (@as(u8, reg.id() & 0b111) << 3), // R | 1790 | 0x04 | (@as(u8, reg.id() & 0b111) << 3), // R |
| ... | @@ -1186,7 +1812,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1186,7 +1812,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1186 | // is no way to possibly encode it. This means that RSP, RBP, R12, and R13 cannot be used with | 1812 | // is no way to possibly encode it. This means that RSP, RBP, R12, and R13 cannot be used with |
| 1187 | // this instruction. | 1813 | // this instruction. |
| 1188 | const id3 = @truncate(u3, reg.id()); | 1814 | const id3 = @truncate(u3, reg.id()); |
| 1189 | std.debug.assert(id3 != 4 and id3 != 5); | 1815 | assert(id3 != 4 and id3 != 5); |
| 1190 | 1816 | ||
| 1191 | // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue. | 1817 | // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue. |
| 1192 | try self.genSetReg(src, reg, MCValue{ .immediate = x }); | 1818 | try self.genSetReg(src, reg, MCValue{ .immediate = x }); |
| ... | @@ -1201,17 +1827,37 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1201,17 +1827,37 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1201 | // | 1827 | // |
| 1202 | // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both* | 1828 | // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both* |
| 1203 | // register operands need to be marked as extended. | 1829 | // register operands need to be marked as extended. |
| 1204 | self.rex(.{ .w = true, .b = reg.isExtended(), .r = reg.isExtended() }); | 1830 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), .r = reg.isExtended() }); |
| 1205 | const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); | 1831 | const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); |
| 1206 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM }); | 1832 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM }); |
| 1207 | } | 1833 | } |
| 1208 | } | 1834 | } |
| 1209 | }, | 1835 | }, |
| 1210 | .stack_offset => |off| { | 1836 | .stack_offset => |unadjusted_off| { |
| 1211 | return self.fail(src, "TODO implement genSetReg for stack variables", .{}); | 1837 | try self.code.ensureCapacity(self.code.items.len + 7); |
| 1838 | const size_bytes = @divExact(reg.size(), 8); | ||
| 1839 | const off = unadjusted_off + size_bytes; | ||
| 1840 | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() }); | ||
| 1841 | const reg_id: u8 = @truncate(u3, reg.id()); | ||
| 1842 | if (off <= 128) { | ||
| 1843 | // Example: 48 8b 4d 7f mov rcx,QWORD PTR [rbp+0x7f] | ||
| 1844 | const RM = @as(u8, 0b01_000_101) | (reg_id << 3); | ||
| 1845 | const negative_offset = @intCast(i8, -@intCast(i32, off)); | ||
| 1846 | const twos_comp = @bitCast(u8, negative_offset); | ||
| 1847 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8b, RM, twos_comp }); | ||
| 1848 | } else if (off <= 2147483648) { | ||
| 1849 | // Example: 48 8b 8d 80 00 00 00 mov rcx,QWORD PTR [rbp+0x80] | ||
| 1850 | const RM = @as(u8, 0b10_000_101) | (reg_id << 3); | ||
| 1851 | const negative_offset = @intCast(i32, -@intCast(i33, off)); | ||
| 1852 | const twos_comp = @bitCast(u32, negative_offset); | ||
| 1853 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8b, RM }); | ||
| 1854 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp); | ||
| 1855 | } else { | ||
| 1856 | return self.fail(src, "stack offset too large", .{}); | ||
| 1857 | } | ||
| 1212 | }, | 1858 | }, |
| 1213 | }, | 1859 | }, |
| 1214 | else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}), | 1860 | else => return self.fail(src, "TODO implement getSetReg for {}", .{self.target.cpu.arch}), |
| 1215 | } | 1861 | } |
| 1216 | } | 1862 | } |
| 1217 | 1863 | ||
| ... | @@ -1247,24 +1893,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1247,24 +1893,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1247 | } | 1893 | } |
| 1248 | } | 1894 | } |
| 1249 | 1895 | ||
| 1250 | /// Does not "move" the instruction. | ||
| 1251 | fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue { | ||
| 1252 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | ||
| 1253 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); | ||
| 1254 | try branch.inst_table.ensureCapacity(self.gpa, branch.inst_table.items().len + 1); | ||
| 1255 | |||
| 1256 | const free_index = @ctz(FreeRegInt, branch.free_registers); | ||
| 1257 | if (free_index >= callee_preserved_regs.len) | ||
| 1258 | return self.fail(inst.src, "TODO implement spilling register to stack", .{}); | ||
| 1259 | branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index); | ||
| 1260 | const reg = callee_preserved_regs[free_index]; | ||
| 1261 | branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); | ||
| 1262 | const old_mcv = branch.inst_table.get(inst).?; | ||
| 1263 | const new_mcv: MCValue = .{ .register = reg }; | ||
| 1264 | try self.genSetReg(inst.src, reg, old_mcv); | ||
| 1265 | return new_mcv; | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | /// If the MCValue is an immediate, and it does not fit within this type, | 1896 | /// If the MCValue is an immediate, and it does not fit within this type, |
| 1269 | /// we put it in a register. | 1897 | /// we put it in a register. |
| 1270 | /// A potential opportunity for future optimization here would be keeping track | 1898 | /// A potential opportunity for future optimization here would be keeping track |
| ... | @@ -1282,7 +1910,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1282,7 +1910,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1282 | .is_signed = false, | 1910 | .is_signed = false, |
| 1283 | }, | 1911 | }, |
| 1284 | }); | 1912 | }); |
| 1285 | if (imm >= std.math.maxInt(U)) { | 1913 | if (imm >= math.maxInt(U)) { |
| 1286 | return self.copyToNewRegister(inst); | 1914 | return self.copyToNewRegister(inst); |
| 1287 | } | 1915 | } |
| 1288 | }, | 1916 | }, |
| ... | @@ -1292,6 +1920,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1292,6 +1920,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1292 | } | 1920 | } |
| 1293 | 1921 | ||
| 1294 | fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) !MCValue { | 1922 | fn genTypedValue(self: *Self, src: usize, typed_value: TypedValue) !MCValue { |
| 1923 | if (typed_value.val.isUndef()) | ||
| 1924 | return MCValue.undef; | ||
| 1295 | const ptr_bits = self.target.cpu.arch.ptrBitWidth(); | 1925 | const ptr_bits = self.target.cpu.arch.ptrBitWidth(); |
| 1296 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); | 1926 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 1297 | switch (typed_value.ty.zigTypeTag()) { | 1927 | switch (typed_value.ty.zigTypeTag()) { |
| ... | @@ -1320,19 +1950,44 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1320,19 +1950,44 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1320 | } | 1950 | } |
| 1321 | } | 1951 | } |
| 1322 | 1952 | ||
| 1323 | fn resolveParameters( | 1953 | const CallMCValues = struct { |
| 1324 | self: *Self, | 1954 | args: []MCValue, |
| 1325 | src: usize, | 1955 | return_value: MCValue, |
| 1326 | cc: std.builtin.CallingConvention, | 1956 | stack_byte_count: u32, |
| 1327 | param_types: []const Type, | 1957 | stack_align: u32, |
| 1328 | results: []MCValue, | 1958 | |
| 1329 | ) !u32 { | 1959 | fn deinit(self: *CallMCValues, func: *Self) void { |
| 1960 | func.gpa.free(self.args); | ||
| 1961 | self.* = undefined; | ||
| 1962 | } | ||
| 1963 | }; | ||
| 1964 | |||
| 1965 | /// Caller must call `CallMCValues.deinit`. | ||
| 1966 | fn resolveCallingConventionValues(self: *Self, src: usize, fn_ty: Type) !CallMCValues { | ||
| 1967 | const cc = fn_ty.fnCallingConvention(); | ||
| 1968 | const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen()); | ||
| 1969 | defer self.gpa.free(param_types); | ||
| 1970 | fn_ty.fnParamTypes(param_types); | ||
| 1971 | var result: CallMCValues = .{ | ||
| 1972 | .args = try self.gpa.alloc(MCValue, param_types.len), | ||
| 1973 | // These undefined values must be populated before returning from this function. | ||
| 1974 | .return_value = undefined, | ||
| 1975 | .stack_byte_count = undefined, | ||
| 1976 | .stack_align = undefined, | ||
| 1977 | }; | ||
| 1978 | errdefer self.gpa.free(result.args); | ||
| 1979 | |||
| 1980 | const ret_ty = fn_ty.fnReturnType(); | ||
| 1981 | |||
| 1330 | switch (arch) { | 1982 | switch (arch) { |
| 1331 | .x86_64 => { | 1983 | .x86_64 => { |
| 1332 | switch (cc) { | 1984 | switch (cc) { |
| 1333 | .Naked => { | 1985 | .Naked => { |
| 1334 | assert(results.len == 0); | 1986 | assert(result.args.len == 0); |
| 1335 | return 0; | 1987 | result.return_value = .{ .unreach = {} }; |
| 1988 | result.stack_byte_count = 0; | ||
| 1989 | result.stack_align = 1; | ||
| 1990 | return result; | ||
| 1336 | }, | 1991 | }, |
| 1337 | .Unspecified, .C => { | 1992 | .Unspecified, .C => { |
| 1338 | var next_int_reg: usize = 0; | 1993 | var next_int_reg: usize = 0; |
| ... | @@ -1341,24 +1996,59 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1341,24 +1996,59 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1341 | for (param_types) |ty, i| { | 1996 | for (param_types) |ty, i| { |
| 1342 | switch (ty.zigTypeTag()) { | 1997 | switch (ty.zigTypeTag()) { |
| 1343 | .Bool, .Int => { | 1998 | .Bool, .Int => { |
| 1999 | const param_size = @intCast(u32, ty.abiSize(self.target.*)); | ||
| 1344 | if (next_int_reg >= c_abi_int_param_regs.len) { | 2000 | if (next_int_reg >= c_abi_int_param_regs.len) { |
| 1345 | results[i] = .{ .stack_offset = next_stack_offset }; | 2001 | result.args[i] = .{ .stack_offset = next_stack_offset }; |
| 1346 | next_stack_offset += @intCast(u32, ty.abiSize(self.target.*)); | 2002 | next_stack_offset += param_size; |
| 1347 | } else { | 2003 | } else { |
| 1348 | results[i] = .{ .register = c_abi_int_param_regs[next_int_reg] }; | 2004 | const aliased_reg = registerAlias( |
| 2005 | c_abi_int_param_regs[next_int_reg], | ||
| 2006 | param_size, | ||
| 2007 | ); | ||
| 2008 | result.args[i] = .{ .register = aliased_reg }; | ||
| 1349 | next_int_reg += 1; | 2009 | next_int_reg += 1; |
| 1350 | } | 2010 | } |
| 1351 | }, | 2011 | }, |
| 1352 | else => return self.fail(src, "TODO implement function parameters of type {}", .{@tagName(ty.zigTypeTag())}), | 2012 | else => return self.fail(src, "TODO implement function parameters of type {}", .{@tagName(ty.zigTypeTag())}), |
| 1353 | } | 2013 | } |
| 1354 | } | 2014 | } |
| 1355 | return next_stack_offset; | 2015 | result.stack_byte_count = next_stack_offset; |
| 2016 | result.stack_align = 16; | ||
| 1356 | }, | 2017 | }, |
| 1357 | else => return self.fail(src, "TODO implement function parameters for {}", .{cc}), | 2018 | else => return self.fail(src, "TODO implement function parameters for {}", .{cc}), |
| 1358 | } | 2019 | } |
| 1359 | }, | 2020 | }, |
| 1360 | else => return self.fail(src, "TODO implement C ABI support for {}", .{self.target.cpu.arch}), | 2021 | else => if (param_types.len != 0) |
| 2022 | return self.fail(src, "TODO implement codegen parameters for {}", .{self.target.cpu.arch}), | ||
| 1361 | } | 2023 | } |
| 2024 | |||
| 2025 | if (ret_ty.zigTypeTag() == .NoReturn) { | ||
| 2026 | result.return_value = .{ .unreach = {} }; | ||
| 2027 | } else if (!ret_ty.hasCodeGenBits()) { | ||
| 2028 | result.return_value = .{ .none = {} }; | ||
| 2029 | } else switch (arch) { | ||
| 2030 | .x86_64 => switch (cc) { | ||
| 2031 | .Naked => unreachable, | ||
| 2032 | .Unspecified, .C => { | ||
| 2033 | const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); | ||
| 2034 | const aliased_reg = registerAlias(c_abi_int_return_regs[0], ret_ty_size); | ||
| 2035 | result.return_value = .{ .register = aliased_reg }; | ||
| 2036 | }, | ||
| 2037 | else => return self.fail(src, "TODO implement function return values for {}", .{cc}), | ||
| 2038 | }, | ||
| 2039 | else => return self.fail(src, "TODO implement codegen return values for {}", .{self.target.cpu.arch}), | ||
| 2040 | } | ||
| 2041 | return result; | ||
| 2042 | } | ||
| 2043 | |||
| 2044 | /// TODO support scope overrides. Also note this logic is duplicated with `Module.wantSafety`. | ||
| 2045 | fn wantSafety(self: *Self) bool { | ||
| 2046 | return switch (self.bin_file.base.options.optimize_mode) { | ||
| 2047 | .Debug => true, | ||
| 2048 | .ReleaseSafe => true, | ||
| 2049 | .ReleaseFast => false, | ||
| 2050 | .ReleaseSmall => false, | ||
| 2051 | }; | ||
| 1362 | } | 2052 | } |
| 1363 | 2053 | ||
| 1364 | fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } { | 2054 | 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 { | ... | @@ -1371,6 +2061,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1371 | usingnamespace switch (arch) { | 2061 | usingnamespace switch (arch) { |
| 1372 | .i386 => @import("codegen/x86.zig"), | 2062 | .i386 => @import("codegen/x86.zig"), |
| 1373 | .x86_64 => @import("codegen/x86_64.zig"), | 2063 | .x86_64 => @import("codegen/x86_64.zig"), |
| 2064 | .riscv64 => @import("codegen/riscv64.zig"), | ||
| 1374 | else => struct { | 2065 | else => struct { |
| 1375 | pub const Register = enum { | 2066 | pub const Register = enum { |
| 1376 | dummy, | 2067 | dummy, |
| ... | @@ -1387,7 +2078,33 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1387,7 +2078,33 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1387 | const FreeRegInt = @Type(.{ .Int = .{ .is_signed = false, .bits = callee_preserved_regs.len } }); | 2078 | const FreeRegInt = @Type(.{ .Int = .{ .is_signed = false, .bits = callee_preserved_regs.len } }); |
| 1388 | 2079 | ||
| 1389 | fn parseRegName(name: []const u8) ?Register { | 2080 | fn parseRegName(name: []const u8) ?Register { |
| 2081 | if (@hasDecl(Register, "parseRegName")) { | ||
| 2082 | return Register.parseRegName(name); | ||
| 2083 | } | ||
| 1390 | return std.meta.stringToEnum(Register, name); | 2084 | return std.meta.stringToEnum(Register, name); |
| 1391 | } | 2085 | } |
| 2086 | |||
| 2087 | fn registerAlias(reg: Register, size_bytes: u32) Register { | ||
| 2088 | switch (arch) { | ||
| 2089 | // For x86_64 we have to pick a smaller register alias depending on abi size. | ||
| 2090 | .x86_64 => switch (size_bytes) { | ||
| 2091 | 1 => return reg.to8(), | ||
| 2092 | 2 => return reg.to16(), | ||
| 2093 | 4 => return reg.to32(), | ||
| 2094 | 8 => return reg.to64(), | ||
| 2095 | else => unreachable, | ||
| 2096 | }, | ||
| 2097 | else => return reg, | ||
| 2098 | } | ||
| 2099 | } | ||
| 2100 | |||
| 2101 | /// For most architectures this does nothing. For x86_64 it resolves any aliased registers | ||
| 2102 | /// to the 64-bit wide ones. | ||
| 2103 | fn toCanonicalReg(reg: Register) Register { | ||
| 2104 | return switch (arch) { | ||
| 2105 | .x86_64 => reg.to64(), | ||
| 2106 | else => reg, | ||
| 2107 | }; | ||
| 2108 | } | ||
| 1392 | }; | 2109 | }; |
| 1393 | } | 2110 | } |
src-self-hosted/codegen/c.zig+11-5| ... | @@ -89,17 +89,17 @@ fn genFn(file: *C, decl: *Decl) !void { | ... | @@ -89,17 +89,17 @@ fn genFn(file: *C, decl: *Decl) !void { |
| 89 | const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; | 89 | const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; |
| 90 | const instructions = func.analysis.success.instructions; | 90 | const instructions = func.analysis.success.instructions; |
| 91 | if (instructions.len > 0) { | 91 | if (instructions.len > 0) { |
| 92 | try writer.writeAll("\n"); | ||
| 92 | for (instructions) |inst| { | 93 | for (instructions) |inst| { |
| 93 | try writer.writeAll("\n "); | ||
| 94 | switch (inst.tag) { | 94 | switch (inst.tag) { |
| 95 | .assembly => try genAsm(file, inst.castTag(.assembly).?, decl), | 95 | .assembly => try genAsm(file, inst.castTag(.assembly).?, decl), |
| 96 | .call => try genCall(file, inst.castTag(.call).?, decl), | 96 | .call => try genCall(file, inst.castTag(.call).?, decl), |
| 97 | .ret => try genRet(file, inst.castTag(.ret).?, decl, tv.ty.fnReturnType()), | 97 | .ret => try genRet(file, inst.castTag(.ret).?, decl, tv.ty.fnReturnType()), |
| 98 | .retvoid => try file.main.writer().print("return;", .{}), | 98 | .retvoid => try file.main.writer().print(" return;\n", .{}), |
| 99 | .dbg_stmt => try genDbgStmt(file, inst.castTag(.dbg_stmt).?, decl), | ||
| 99 | else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}), | 100 | else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}), |
| 100 | } | 101 | } |
| 101 | } | 102 | } |
| 102 | try writer.writeAll("\n"); | ||
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | try writer.writeAll("}\n\n"); | 105 | try writer.writeAll("}\n\n"); |
| ... | @@ -112,6 +112,7 @@ fn genRet(file: *C, inst: *Inst.UnOp, decl: *Decl, expected_return_type: Type) ! | ... | @@ -112,6 +112,7 @@ fn genRet(file: *C, inst: *Inst.UnOp, decl: *Decl, expected_return_type: Type) ! |
| 112 | fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { | 112 | fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { |
| 113 | const writer = file.main.writer(); | 113 | const writer = file.main.writer(); |
| 114 | const header = file.header.writer(); | 114 | const header = file.header.writer(); |
| 115 | try writer.writeAll(" "); | ||
| 115 | if (inst.func.castTag(.constant)) |func_inst| { | 116 | if (inst.func.castTag(.constant)) |func_inst| { |
| 116 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { | 117 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { |
| 117 | const target = func_val.func.owner_decl; | 118 | const target = func_val.func.owner_decl; |
| ... | @@ -126,7 +127,7 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { | ... | @@ -126,7 +127,7 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { |
| 126 | try renderFunctionSignature(file, header, target); | 127 | try renderFunctionSignature(file, header, target); |
| 127 | try header.writeAll(";\n"); | 128 | try header.writeAll(";\n"); |
| 128 | } | 129 | } |
| 129 | try writer.print("{}();", .{tname}); | 130 | try writer.print("{}();\n", .{tname}); |
| 130 | } else { | 131 | } else { |
| 131 | return file.fail(decl.src(), "TODO non-function call target?", .{}); | 132 | return file.fail(decl.src(), "TODO non-function call target?", .{}); |
| 132 | } | 133 | } |
| ... | @@ -138,8 +139,13 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { | ... | @@ -138,8 +139,13 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { |
| 138 | } | 139 | } |
| 139 | } | 140 | } |
| 140 | 141 | ||
| 142 | fn genDbgStmt(file: *C, inst: *Inst.NoOp, decl: *Decl) !void { | ||
| 143 | // TODO emit #line directive here with line number and filename | ||
| 144 | } | ||
| 145 | |||
| 141 | fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void { | 146 | fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void { |
| 142 | const writer = file.main.writer(); | 147 | const writer = file.main.writer(); |
| 148 | try writer.writeAll(" "); | ||
| 143 | for (as.inputs) |i, index| { | 149 | for (as.inputs) |i, index| { |
| 144 | if (i[0] == '{' and i[i.len - 1] == '}') { | 150 | if (i[0] == '{' and i[i.len - 1] == '}') { |
| 145 | const reg = i[1 .. i.len - 1]; | 151 | const reg = i[1 .. i.len - 1]; |
| ... | @@ -187,5 +193,5 @@ fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void { | ... | @@ -187,5 +193,5 @@ fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void { |
| 187 | } | 193 | } |
| 188 | } | 194 | } |
| 189 | } | 195 | } |
| 190 | try writer.writeAll(");"); | 196 | try writer.writeAll(");\n"); |
| 191 | } | 197 | } |
src-self-hosted/codegen/riscv64.zig created+92| ... | @@ -0,0 +1,92 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub const instructions = struct { | ||
| 4 | pub const CallBreak = packed struct { | ||
| 5 | pub const Mode = packed enum(u12) { ecall, ebreak }; | ||
| 6 | opcode: u7 = 0b1110011, | ||
| 7 | unused1: u5 = 0, | ||
| 8 | unused2: u3 = 0, | ||
| 9 | unused3: u5 = 0, | ||
| 10 | mode: u12, //: Mode | ||
| 11 | }; | ||
| 12 | // I-type | ||
| 13 | pub const Addi = packed struct { | ||
| 14 | pub const Mode = packed enum(u3) { addi = 0b000, slti = 0b010, sltiu = 0b011, xori = 0b100, ori = 0b110, andi = 0b111 }; | ||
| 15 | opcode: u7 = 0b0010011, | ||
| 16 | rd: u5, | ||
| 17 | mode: u3, //: Mode | ||
| 18 | rs1: u5, | ||
| 19 | imm: i12, | ||
| 20 | }; | ||
| 21 | pub const Lui = packed struct { | ||
| 22 | opcode: u7 = 0b0110111, | ||
| 23 | rd: u5, | ||
| 24 | imm: i20, | ||
| 25 | }; | ||
| 26 | // I_type | ||
| 27 | pub const Load = packed struct { | ||
| 28 | pub const Mode = packed enum(u3) { ld = 0b011, lwu = 0b110 }; | ||
| 29 | opcode: u7 = 0b0000011, | ||
| 30 | rd: u5, | ||
| 31 | mode: u3, //: Mode | ||
| 32 | rs1: u5, | ||
| 33 | offset: i12, | ||
| 34 | }; | ||
| 35 | // I-type | ||
| 36 | pub const Jalr = packed struct { | ||
| 37 | opcode: u7 = 0b1100111, | ||
| 38 | rd: u5, | ||
| 39 | mode: u3 = 0, | ||
| 40 | rs1: u5, | ||
| 41 | offset: i12, | ||
| 42 | }; | ||
| 43 | }; | ||
| 44 | |||
| 45 | // zig fmt: off | ||
| 46 | pub const RawRegister = enum(u8) { | ||
| 47 | x0, x1, x2, x3, x4, x5, x6, x7, | ||
| 48 | x8, x9, x10, x11, x12, x13, x14, x15, | ||
| 49 | x16, x17, x18, x19, x20, x21, x22, x23, | ||
| 50 | x24, x25, x26, x27, x28, x29, x30, x31, | ||
| 51 | }; | ||
| 52 | |||
| 53 | pub const Register = enum(u8) { | ||
| 54 | // 64 bit registers | ||
| 55 | zero, // zero | ||
| 56 | ra, // return address. caller saved | ||
| 57 | sp, // stack pointer. callee saved. | ||
| 58 | gp, // global pointer | ||
| 59 | tp, // thread pointer | ||
| 60 | t0, t1, t2, // temporaries. caller saved. | ||
| 61 | s0, // s0/fp, callee saved. | ||
| 62 | s1, // callee saved. | ||
| 63 | a0, a1, // fn args/return values. caller saved. | ||
| 64 | a2, a3, a4, a5, a6, a7, // fn args. caller saved. | ||
| 65 | s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, // saved registers. callee saved. | ||
| 66 | t3, t4, t5, t6, // caller saved | ||
| 67 | |||
| 68 | pub fn parseRegName(name: []const u8) ?Register { | ||
| 69 | if(std.meta.stringToEnum(Register, name)) |reg| return reg; | ||
| 70 | if(std.meta.stringToEnum(RawRegister, name)) |rawreg| return @intToEnum(Register, @enumToInt(rawreg)); | ||
| 71 | return null; | ||
| 72 | } | ||
| 73 | |||
| 74 | /// Returns the register's id. | ||
| 75 | pub fn id(self: @This()) u5 { | ||
| 76 | return @truncate(u5, @enumToInt(self)); | ||
| 77 | } | ||
| 78 | |||
| 79 | /// Returns the index into `callee_preserved_regs`. | ||
| 80 | pub fn allocIndex(self: Register) ?u4 { | ||
| 81 | inline for(callee_preserved_regs) |cpreg, i| { | ||
| 82 | if(self == cpreg) return i; | ||
| 83 | } | ||
| 84 | return null; | ||
| 85 | } | ||
| 86 | }; | ||
| 87 | |||
| 88 | // zig fmt: on | ||
| 89 | |||
| 90 | pub const callee_preserved_regs = [_]Register{ | ||
| 91 | .s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11, | ||
| 92 | }; | ||
src-self-hosted/codegen/x86_64.zig+21| ... | @@ -81,6 +81,26 @@ pub const Register = enum(u8) { | ... | @@ -81,6 +81,26 @@ pub const Register = enum(u8) { |
| 81 | else => null, | 81 | else => null, |
| 82 | }; | 82 | }; |
| 83 | } | 83 | } |
| 84 | |||
| 85 | /// Convert from any register to its 64 bit alias. | ||
| 86 | pub fn to64(self: Register) Register { | ||
| 87 | return @intToEnum(Register, self.id()); | ||
| 88 | } | ||
| 89 | |||
| 90 | /// Convert from any register to its 32 bit alias. | ||
| 91 | pub fn to32(self: Register) Register { | ||
| 92 | return @intToEnum(Register, @as(u8, self.id()) + 16); | ||
| 93 | } | ||
| 94 | |||
| 95 | /// Convert from any register to its 16 bit alias. | ||
| 96 | pub fn to16(self: Register) Register { | ||
| 97 | return @intToEnum(Register, @as(u8, self.id()) + 32); | ||
| 98 | } | ||
| 99 | |||
| 100 | /// Convert from any register to its 8 bit alias. | ||
| 101 | pub fn to8(self: Register) Register { | ||
| 102 | return @intToEnum(Register, @as(u8, self.id()) + 48); | ||
| 103 | } | ||
| 84 | }; | 104 | }; |
| 85 | 105 | ||
| 86 | // zig fmt: on | 106 | // zig fmt: on |
| ... | @@ -88,3 +108,4 @@ pub const Register = enum(u8) { | ... | @@ -88,3 +108,4 @@ pub const Register = enum(u8) { |
| 88 | /// These registers belong to the called function. | 108 | /// These registers belong to the called function. |
| 89 | pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 }; | 109 | pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 }; |
| 90 | pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; | 110 | pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; |
| 111 | pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx }; |
src-self-hosted/ir.zig+14-5| ... | @@ -4,6 +4,7 @@ const Type = @import("type.zig").Type; | ... | @@ -4,6 +4,7 @@ const Type = @import("type.zig").Type; |
| 4 | const Module = @import("Module.zig"); | 4 | const Module = @import("Module.zig"); |
| 5 | const assert = std.debug.assert; | 5 | const assert = std.debug.assert; |
| 6 | const codegen = @import("codegen.zig"); | 6 | const codegen = @import("codegen.zig"); |
| 7 | const ast = std.zig.ast; | ||
| 7 | 8 | ||
| 8 | /// These are in-memory, analyzed instructions. See `zir.Inst` for the representation | 9 | /// These are in-memory, analyzed instructions. See `zir.Inst` for the representation |
| 9 | /// of instructions that correspond to the ZIR text format. | 10 | /// of instructions that correspond to the ZIR text format. |
| ... | @@ -47,6 +48,7 @@ pub const Inst = struct { | ... | @@ -47,6 +48,7 @@ pub const Inst = struct { |
| 47 | 48 | ||
| 48 | pub const Tag = enum { | 49 | pub const Tag = enum { |
| 49 | add, | 50 | add, |
| 51 | alloc, | ||
| 50 | arg, | 52 | arg, |
| 51 | assembly, | 53 | assembly, |
| 52 | bitcast, | 54 | bitcast, |
| ... | @@ -63,28 +65,34 @@ pub const Inst = struct { | ... | @@ -63,28 +65,34 @@ pub const Inst = struct { |
| 63 | cmp_neq, | 65 | cmp_neq, |
| 64 | condbr, | 66 | condbr, |
| 65 | constant, | 67 | constant, |
| 68 | dbg_stmt, | ||
| 66 | isnonnull, | 69 | isnonnull, |
| 67 | isnull, | 70 | isnull, |
| 71 | /// Read a value from a pointer. | ||
| 72 | load, | ||
| 68 | ptrtoint, | 73 | ptrtoint, |
| 74 | ref, | ||
| 69 | ret, | 75 | ret, |
| 70 | retvoid, | 76 | retvoid, |
| 77 | /// Write a value to a pointer. LHS is pointer, RHS is value. | ||
| 78 | store, | ||
| 71 | sub, | 79 | sub, |
| 72 | unreach, | 80 | unreach, |
| 73 | not, | 81 | not, |
| 74 | floatcast, | 82 | floatcast, |
| 75 | intcast, | 83 | intcast, |
| 76 | 84 | ||
| 77 | /// There is one-to-one correspondence between tag and type for now, | ||
| 78 | /// but this will not always be the case. For example, binary operations | ||
| 79 | /// such as + and - will have different tags but the same type. | ||
| 80 | pub fn Type(tag: Tag) type { | 85 | pub fn Type(tag: Tag) type { |
| 81 | return switch (tag) { | 86 | return switch (tag) { |
| 87 | .alloc, | ||
| 82 | .retvoid, | 88 | .retvoid, |
| 83 | .unreach, | 89 | .unreach, |
| 84 | .arg, | 90 | .arg, |
| 85 | .breakpoint, | 91 | .breakpoint, |
| 92 | .dbg_stmt, | ||
| 86 | => NoOp, | 93 | => NoOp, |
| 87 | 94 | ||
| 95 | .ref, | ||
| 88 | .ret, | 96 | .ret, |
| 89 | .bitcast, | 97 | .bitcast, |
| 90 | .not, | 98 | .not, |
| ... | @@ -93,6 +101,7 @@ pub const Inst = struct { | ... | @@ -93,6 +101,7 @@ pub const Inst = struct { |
| 93 | .ptrtoint, | 101 | .ptrtoint, |
| 94 | .floatcast, | 102 | .floatcast, |
| 95 | .intcast, | 103 | .intcast, |
| 104 | .load, | ||
| 96 | => UnOp, | 105 | => UnOp, |
| 97 | 106 | ||
| 98 | .add, | 107 | .add, |
| ... | @@ -103,6 +112,7 @@ pub const Inst = struct { | ... | @@ -103,6 +112,7 @@ pub const Inst = struct { |
| 103 | .cmp_gte, | 112 | .cmp_gte, |
| 104 | .cmp_gt, | 113 | .cmp_gt, |
| 105 | .cmp_neq, | 114 | .cmp_neq, |
| 115 | .store, | ||
| 106 | => BinOp, | 116 | => BinOp, |
| 107 | 117 | ||
| 108 | .assembly => Assembly, | 118 | .assembly => Assembly, |
| ... | @@ -157,8 +167,7 @@ pub const Inst = struct { | ... | @@ -157,8 +167,7 @@ pub const Inst = struct { |
| 157 | 167 | ||
| 158 | /// Returns `null` if runtime-known. | 168 | /// Returns `null` if runtime-known. |
| 159 | pub fn value(base: *Inst) ?Value { | 169 | pub fn value(base: *Inst) ?Value { |
| 160 | if (base.ty.onePossibleValue()) | 170 | if (base.ty.onePossibleValue()) |opv| return opv; |
| 161 | return Value.initTag(.the_one_possible_value); | ||
| 162 | 171 | ||
| 163 | const inst = base.cast(Constant) orelse return null; | 172 | const inst = base.cast(Constant) orelse return null; |
| 164 | return inst.val; | 173 | return inst.val; |
src-self-hosted/link.zig+1063-280| ... | @@ -8,6 +8,15 @@ const fs = std.fs; | ... | @@ -8,6 +8,15 @@ const fs = std.fs; |
| 8 | const elf = std.elf; | 8 | const elf = std.elf; |
| 9 | const codegen = @import("codegen.zig"); | 9 | const codegen = @import("codegen.zig"); |
| 10 | const c_codegen = @import("codegen/c.zig"); | 10 | const c_codegen = @import("codegen/c.zig"); |
| 11 | const log = std.log; | ||
| 12 | const DW = std.dwarf; | ||
| 13 | const trace = @import("tracy.zig").trace; | ||
| 14 | const leb128 = std.debug.leb; | ||
| 15 | const Package = @import("Package.zig"); | ||
| 16 | const Value = @import("value.zig").Value; | ||
| 17 | |||
| 18 | // TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. | ||
| 19 | // zig fmt: off | ||
| 11 | 20 | ||
| 12 | const default_entry_addr = 0x8000000; | 21 | const default_entry_addr = 0x8000000; |
| 13 | 22 | ||
| ... | @@ -16,6 +25,9 @@ pub const Options = struct { | ... | @@ -16,6 +25,9 @@ pub const Options = struct { |
| 16 | output_mode: std.builtin.OutputMode, | 25 | output_mode: std.builtin.OutputMode, |
| 17 | link_mode: std.builtin.LinkMode, | 26 | link_mode: std.builtin.LinkMode, |
| 18 | object_format: std.builtin.ObjectFormat, | 27 | object_format: std.builtin.ObjectFormat, |
| 28 | optimize_mode: std.builtin.Mode, | ||
| 29 | root_name: []const u8, | ||
| 30 | root_pkg: *const Package, | ||
| 19 | /// Used for calculating how much space to reserve for symbols in case the binary file | 31 | /// Used for calculating how much space to reserve for symbols in case the binary file |
| 20 | /// does not already have a symbol table. | 32 | /// does not already have a symbol table. |
| 21 | symbol_count_hint: u64 = 32, | 33 | symbol_count_hint: u64 = 32, |
| ... | @@ -24,96 +36,27 @@ pub const Options = struct { | ... | @@ -24,96 +36,27 @@ pub const Options = struct { |
| 24 | program_code_size_hint: u64 = 256 * 1024, | 36 | program_code_size_hint: u64 = 256 * 1024, |
| 25 | }; | 37 | }; |
| 26 | 38 | ||
| 27 | /// Attempts incremental linking, if the file already exists. | 39 | pub const File = struct { |
| 28 | /// If incremental linking fails, falls back to truncating the file and rewriting it. | 40 | tag: Tag, |
| 29 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. | ||
| 30 | /// This operation is not atomic. | ||
| 31 | pub fn openBinFilePath( | ||
| 32 | allocator: *Allocator, | ||
| 33 | dir: fs.Dir, | ||
| 34 | sub_path: []const u8, | ||
| 35 | options: Options, | 41 | options: Options, |
| 36 | ) !*File { | ||
| 37 | const cbe = options.object_format == .c; | ||
| 38 | const file = try dir.createFile(sub_path, .{ .truncate = cbe, .read = true, .mode = determineMode(options) }); | ||
| 39 | errdefer file.close(); | ||
| 40 | |||
| 41 | if (cbe) { | ||
| 42 | var bin_file = try allocator.create(File.C); | ||
| 43 | errdefer allocator.destroy(bin_file); | ||
| 44 | bin_file.* = try openCFile(allocator, file, options); | ||
| 45 | return &bin_file.base; | ||
| 46 | } else { | ||
| 47 | var bin_file = try allocator.create(File.Elf); | ||
| 48 | errdefer allocator.destroy(bin_file); | ||
| 49 | bin_file.* = try openBinFile(allocator, file, options); | ||
| 50 | bin_file.owns_file_handle = true; | ||
| 51 | return &bin_file.base; | ||
| 52 | } | ||
| 53 | } | ||
| 54 | 42 | ||
| 55 | /// Atomically overwrites the old file, if present. | 43 | /// Attempts incremental linking, if the file already exists. If |
| 56 | pub fn writeFilePath( | 44 | /// incremental linking fails, falls back to truncating the file and |
| 57 | allocator: *Allocator, | 45 | /// rewriting it. A malicious file is detected as incremental link failure |
| 58 | dir: fs.Dir, | 46 | /// and does not cause Illegal Behavior. This operation is not atomic. |
| 59 | sub_path: []const u8, | 47 | pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File { |
| 60 | module: Module, | 48 | switch (options.object_format) { |
| 61 | errors: *std.ArrayList(Module.ErrorMsg), | 49 | .unknown => unreachable, |
| 62 | ) !void { | 50 | .coff => return error.TODOImplementCoff, |
| 63 | const options: Options = .{ | 51 | .elf => return Elf.openPath(allocator, dir, sub_path, options), |
| 64 | .target = module.target, | 52 | .macho => return error.TODOImplementMacho, |
| 65 | .output_mode = module.output_mode, | 53 | .wasm => return error.TODOImplementWasm, |
| 66 | .link_mode = module.link_mode, | 54 | .c => return C.openPath(allocator, dir, sub_path, options), |
| 67 | .object_format = module.object_format, | 55 | .hex => return error.TODOImplementHex, |
| 68 | .symbol_count_hint = module.decls.items.len, | 56 | .raw => return error.TODOImplementRaw, |
| 69 | }; | 57 | } |
| 70 | const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(options) }); | ||
| 71 | defer af.deinit(); | ||
| 72 | |||
| 73 | const elf_file = try createElfFile(allocator, af.file, options); | ||
| 74 | for (module.decls.items) |decl| { | ||
| 75 | try elf_file.updateDecl(module, decl, errors); | ||
| 76 | } | ||
| 77 | try elf_file.flush(); | ||
| 78 | if (elf_file.error_flags.no_entry_point_found) { | ||
| 79 | try errors.ensureCapacity(errors.items.len + 1); | ||
| 80 | errors.appendAssumeCapacity(.{ | ||
| 81 | .byte_offset = 0, | ||
| 82 | .msg = try std.fmt.allocPrint(errors.allocator, "no entry point found", .{}), | ||
| 83 | }); | ||
| 84 | } | 58 | } |
| 85 | try af.finish(); | ||
| 86 | return result; | ||
| 87 | } | ||
| 88 | 59 | ||
| 89 | fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C { | ||
| 90 | return File.C{ | ||
| 91 | .allocator = allocator, | ||
| 92 | .file = file, | ||
| 93 | .options = options, | ||
| 94 | .main = std.ArrayList(u8).init(allocator), | ||
| 95 | .header = std.ArrayList(u8).init(allocator), | ||
| 96 | .constants = std.ArrayList(u8).init(allocator), | ||
| 97 | .called = std.StringHashMap(void).init(allocator), | ||
| 98 | }; | ||
| 99 | } | ||
| 100 | |||
| 101 | /// Attempts incremental linking, if the file already exists. | ||
| 102 | /// If incremental linking fails, falls back to truncating the file and rewriting it. | ||
| 103 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | ||
| 104 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. | ||
| 105 | /// This operation is not atomic. | ||
| 106 | pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !File.Elf { | ||
| 107 | return openBinFileInner(allocator, file, options) catch |err| switch (err) { | ||
| 108 | error.IncrFailed => { | ||
| 109 | return createElfFile(allocator, file, options); | ||
| 110 | }, | ||
| 111 | else => |e| return e, | ||
| 112 | }; | ||
| 113 | } | ||
| 114 | |||
| 115 | pub const File = struct { | ||
| 116 | tag: Tag, | ||
| 117 | pub fn cast(base: *File, comptime T: type) ?*T { | 60 | pub fn cast(base: *File, comptime T: type) ?*T { |
| 118 | if (base.tag != T.base_tag) | 61 | if (base.tag != T.base_tag) |
| 119 | return null; | 62 | return null; |
| ... | @@ -123,86 +66,82 @@ pub const File = struct { | ... | @@ -123,86 +66,82 @@ pub const File = struct { |
| 123 | 66 | ||
| 124 | pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void { | 67 | pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void { |
| 125 | switch (base.tag) { | 68 | switch (base.tag) { |
| 126 | .Elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path), | 69 | .elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path), |
| 127 | .C => {}, | 70 | .c => {}, |
| 128 | else => unreachable, | ||
| 129 | } | 71 | } |
| 130 | } | 72 | } |
| 131 | 73 | ||
| 132 | pub fn makeExecutable(base: *File) !void { | 74 | pub fn makeExecutable(base: *File) !void { |
| 133 | switch (base.tag) { | 75 | switch (base.tag) { |
| 134 | .Elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(), | 76 | .elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(), |
| 135 | else => unreachable, | 77 | .c => unreachable, |
| 136 | } | 78 | } |
| 137 | } | 79 | } |
| 138 | 80 | ||
| 139 | pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void { | 81 | pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void { |
| 140 | switch (base.tag) { | 82 | switch (base.tag) { |
| 141 | .Elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), | 83 | .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), |
| 142 | .C => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), | 84 | .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), |
| 143 | else => unreachable, | 85 | } |
| 86 | } | ||
| 87 | |||
| 88 | pub fn updateDeclLineNumber(base: *File, module: *Module, decl: *Module.Decl) !void { | ||
| 89 | switch (base.tag) { | ||
| 90 | .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), | ||
| 91 | .c => {}, | ||
| 144 | } | 92 | } |
| 145 | } | 93 | } |
| 146 | 94 | ||
| 147 | pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void { | 95 | pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void { |
| 148 | switch (base.tag) { | 96 | switch (base.tag) { |
| 149 | .Elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), | 97 | .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), |
| 150 | .C => {}, | 98 | .c => {}, |
| 151 | else => unreachable, | ||
| 152 | } | 99 | } |
| 153 | } | 100 | } |
| 154 | 101 | ||
| 155 | pub fn deinit(base: *File) void { | 102 | pub fn deinit(base: *File) void { |
| 156 | switch (base.tag) { | 103 | switch (base.tag) { |
| 157 | .Elf => @fieldParentPtr(Elf, "base", base).deinit(), | 104 | .elf => @fieldParentPtr(Elf, "base", base).deinit(), |
| 158 | .C => @fieldParentPtr(C, "base", base).deinit(), | 105 | .c => @fieldParentPtr(C, "base", base).deinit(), |
| 159 | else => unreachable, | ||
| 160 | } | 106 | } |
| 161 | } | 107 | } |
| 162 | 108 | ||
| 163 | pub fn destroy(base: *File) void { | 109 | pub fn destroy(base: *File) void { |
| 164 | switch (base.tag) { | 110 | switch (base.tag) { |
| 165 | .Elf => { | 111 | .elf => { |
| 166 | const parent = @fieldParentPtr(Elf, "base", base); | 112 | const parent = @fieldParentPtr(Elf, "base", base); |
| 167 | parent.deinit(); | 113 | parent.deinit(); |
| 168 | parent.allocator.destroy(parent); | 114 | parent.allocator.destroy(parent); |
| 169 | }, | 115 | }, |
| 170 | .C => { | 116 | .c => { |
| 171 | const parent = @fieldParentPtr(C, "base", base); | 117 | const parent = @fieldParentPtr(C, "base", base); |
| 172 | parent.deinit(); | 118 | parent.deinit(); |
| 173 | parent.allocator.destroy(parent); | 119 | parent.allocator.destroy(parent); |
| 174 | }, | 120 | }, |
| 175 | else => unreachable, | ||
| 176 | } | 121 | } |
| 177 | } | 122 | } |
| 178 | 123 | ||
| 179 | pub fn flush(base: *File) !void { | 124 | pub fn flush(base: *File) !void { |
| 125 | const tracy = trace(@src()); | ||
| 126 | defer tracy.end(); | ||
| 127 | |||
| 180 | try switch (base.tag) { | 128 | try switch (base.tag) { |
| 181 | .Elf => @fieldParentPtr(Elf, "base", base).flush(), | 129 | .elf => @fieldParentPtr(Elf, "base", base).flush(), |
| 182 | .C => @fieldParentPtr(C, "base", base).flush(), | 130 | .c => @fieldParentPtr(C, "base", base).flush(), |
| 183 | else => unreachable, | ||
| 184 | }; | 131 | }; |
| 185 | } | 132 | } |
| 186 | 133 | ||
| 187 | pub fn freeDecl(base: *File, decl: *Module.Decl) void { | 134 | pub fn freeDecl(base: *File, decl: *Module.Decl) void { |
| 188 | switch (base.tag) { | 135 | switch (base.tag) { |
| 189 | .Elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), | 136 | .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), |
| 190 | else => unreachable, | 137 | .c => unreachable, |
| 191 | } | 138 | } |
| 192 | } | 139 | } |
| 193 | 140 | ||
| 194 | pub fn errorFlags(base: *File) ErrorFlags { | 141 | pub fn errorFlags(base: *File) ErrorFlags { |
| 195 | return switch (base.tag) { | 142 | return switch (base.tag) { |
| 196 | .Elf => @fieldParentPtr(Elf, "base", base).error_flags, | 143 | .elf => @fieldParentPtr(Elf, "base", base).error_flags, |
| 197 | .C => return .{ .no_entry_point_found = false }, | 144 | .c => return .{ .no_entry_point_found = false }, |
| 198 | else => unreachable, | ||
| 199 | }; | ||
| 200 | } | ||
| 201 | |||
| 202 | pub fn options(base: *File) Options { | ||
| 203 | return switch (base.tag) { | ||
| 204 | .Elf => @fieldParentPtr(Elf, "base", base).options, | ||
| 205 | .C => @fieldParentPtr(C, "base", base).options, | ||
| 206 | }; | 145 | }; |
| 207 | } | 146 | } |
| 208 | 147 | ||
| ... | @@ -214,14 +153,14 @@ pub const File = struct { | ... | @@ -214,14 +153,14 @@ pub const File = struct { |
| 214 | exports: []const *Module.Export, | 153 | exports: []const *Module.Export, |
| 215 | ) !void { | 154 | ) !void { |
| 216 | switch (base.tag) { | 155 | switch (base.tag) { |
| 217 | .Elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), | 156 | .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), |
| 218 | .C => return {}, | 157 | .c => return {}, |
| 219 | } | 158 | } |
| 220 | } | 159 | } |
| 221 | 160 | ||
| 222 | pub const Tag = enum { | 161 | pub const Tag = enum { |
| 223 | Elf, | 162 | elf, |
| 224 | C, | 163 | c, |
| 225 | }; | 164 | }; |
| 226 | 165 | ||
| 227 | pub const ErrorFlags = struct { | 166 | pub const ErrorFlags = struct { |
| ... | @@ -229,24 +168,49 @@ pub const File = struct { | ... | @@ -229,24 +168,49 @@ pub const File = struct { |
| 229 | }; | 168 | }; |
| 230 | 169 | ||
| 231 | pub const C = struct { | 170 | pub const C = struct { |
| 232 | pub const base_tag: Tag = .C; | 171 | pub const base_tag: Tag = .c; |
| 233 | base: File = File{ .tag = base_tag }, | 172 | |
| 173 | base: File, | ||
| 234 | 174 | ||
| 235 | allocator: *Allocator, | 175 | allocator: *Allocator, |
| 236 | header: std.ArrayList(u8), | 176 | header: std.ArrayList(u8), |
| 237 | constants: std.ArrayList(u8), | 177 | constants: std.ArrayList(u8), |
| 238 | main: std.ArrayList(u8), | 178 | main: std.ArrayList(u8), |
| 239 | file: ?fs.File, | 179 | file: ?fs.File, |
| 240 | options: Options, | ||
| 241 | called: std.StringHashMap(void), | 180 | called: std.StringHashMap(void), |
| 242 | need_stddef: bool = false, | 181 | need_stddef: bool = false, |
| 243 | need_stdint: bool = false, | 182 | need_stdint: bool = false, |
| 244 | need_noreturn: bool = false, | 183 | need_noreturn: bool = false, |
| 245 | error_msg: *Module.ErrorMsg = undefined, | 184 | error_msg: *Module.ErrorMsg = undefined, |
| 246 | 185 | ||
| 186 | pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File { | ||
| 187 | assert(options.object_format == .c); | ||
| 188 | |||
| 189 | const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true, .mode = determineMode(options) }); | ||
| 190 | errdefer file.close(); | ||
| 191 | |||
| 192 | var c_file = try allocator.create(C); | ||
| 193 | errdefer allocator.destroy(c_file); | ||
| 194 | |||
| 195 | c_file.* = File.C{ | ||
| 196 | .base = .{ | ||
| 197 | .tag = .c, | ||
| 198 | .options = options, | ||
| 199 | }, | ||
| 200 | .allocator = allocator, | ||
| 201 | .file = file, | ||
| 202 | .main = std.ArrayList(u8).init(allocator), | ||
| 203 | .header = std.ArrayList(u8).init(allocator), | ||
| 204 | .constants = std.ArrayList(u8).init(allocator), | ||
| 205 | .called = std.StringHashMap(void).init(allocator), | ||
| 206 | }; | ||
| 207 | |||
| 208 | return &c_file.base; | ||
| 209 | } | ||
| 210 | |||
| 247 | pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) !void { | 211 | pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) !void { |
| 248 | self.error_msg = try Module.ErrorMsg.create(self.allocator, src, format, args); | 212 | self.error_msg = try Module.ErrorMsg.create(self.allocator, src, format, args); |
| 249 | return error.CGenFailure; | 213 | return error.AnalysisFail; |
| 250 | } | 214 | } |
| 251 | 215 | ||
| 252 | pub fn deinit(self: *File.C) void { | 216 | pub fn deinit(self: *File.C) void { |
| ... | @@ -260,7 +224,7 @@ pub const File = struct { | ... | @@ -260,7 +224,7 @@ pub const File = struct { |
| 260 | 224 | ||
| 261 | pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { | 225 | pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { |
| 262 | c_codegen.generate(self, decl) catch |err| { | 226 | c_codegen.generate(self, decl) catch |err| { |
| 263 | if (err == error.CGenFailure) { | 227 | if (err == error.AnalysisFail) { |
| 264 | try module.failed_decls.put(module.gpa, decl, self.error_msg); | 228 | try module.failed_decls.put(module.gpa, decl, self.error_msg); |
| 265 | } | 229 | } |
| 266 | return err; | 230 | return err; |
| ... | @@ -301,13 +265,13 @@ pub const File = struct { | ... | @@ -301,13 +265,13 @@ pub const File = struct { |
| 301 | }; | 265 | }; |
| 302 | 266 | ||
| 303 | pub const Elf = struct { | 267 | pub const Elf = struct { |
| 304 | pub const base_tag: Tag = .Elf; | 268 | pub const base_tag: Tag = .elf; |
| 305 | base: File = File{ .tag = base_tag }, | 269 | |
| 270 | base: File, | ||
| 306 | 271 | ||
| 307 | allocator: *Allocator, | 272 | allocator: *Allocator, |
| 308 | file: ?fs.File, | 273 | file: ?fs.File, |
| 309 | owns_file_handle: bool, | 274 | owns_file_handle: bool, |
| 310 | options: Options, | ||
| 311 | ptr_width: enum { p32, p64 }, | 275 | ptr_width: enum { p32, p64 }, |
| 312 | 276 | ||
| 313 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. | 277 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. |
| ... | @@ -326,12 +290,20 @@ pub const File = struct { | ... | @@ -326,12 +290,20 @@ pub const File = struct { |
| 326 | phdr_got_index: ?u16 = null, | 290 | phdr_got_index: ?u16 = null, |
| 327 | entry_addr: ?u64 = null, | 291 | entry_addr: ?u64 = null, |
| 328 | 292 | ||
| 293 | debug_strtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){}, | ||
| 329 | shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){}, | 294 | shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){}, |
| 330 | shstrtab_index: ?u16 = null, | 295 | shstrtab_index: ?u16 = null, |
| 331 | 296 | ||
| 332 | text_section_index: ?u16 = null, | 297 | text_section_index: ?u16 = null, |
| 333 | symtab_section_index: ?u16 = null, | 298 | symtab_section_index: ?u16 = null, |
| 334 | got_section_index: ?u16 = null, | 299 | got_section_index: ?u16 = null, |
| 300 | debug_info_section_index: ?u16 = null, | ||
| 301 | debug_abbrev_section_index: ?u16 = null, | ||
| 302 | debug_str_section_index: ?u16 = null, | ||
| 303 | debug_aranges_section_index: ?u16 = null, | ||
| 304 | debug_line_section_index: ?u16 = null, | ||
| 305 | |||
| 306 | debug_abbrev_table_offset: ?u64 = null, | ||
| 335 | 307 | ||
| 336 | /// The same order as in the file. ELF requires global symbols to all be after the | 308 | /// The same order as in the file. ELF requires global symbols to all be after the |
| 337 | /// local symbols, they cannot be mixed. So we must buffer all the global symbols and | 309 | /// local symbols, they cannot be mixed. So we must buffer all the global symbols and |
| ... | @@ -352,7 +324,12 @@ pub const File = struct { | ... | @@ -352,7 +324,12 @@ pub const File = struct { |
| 352 | phdr_table_dirty: bool = false, | 324 | phdr_table_dirty: bool = false, |
| 353 | shdr_table_dirty: bool = false, | 325 | shdr_table_dirty: bool = false, |
| 354 | shstrtab_dirty: bool = false, | 326 | shstrtab_dirty: bool = false, |
| 327 | debug_strtab_dirty: bool = false, | ||
| 355 | offset_table_count_dirty: bool = false, | 328 | offset_table_count_dirty: bool = false, |
| 329 | debug_info_section_dirty: bool = false, | ||
| 330 | debug_abbrev_section_dirty: bool = false, | ||
| 331 | debug_aranges_section_dirty: bool = false, | ||
| 332 | debug_line_header_dirty: bool = false, | ||
| 356 | 333 | ||
| 357 | error_flags: ErrorFlags = ErrorFlags{}, | 334 | error_flags: ErrorFlags = ErrorFlags{}, |
| 358 | 335 | ||
| ... | @@ -374,6 +351,12 @@ pub const File = struct { | ... | @@ -374,6 +351,12 @@ pub const File = struct { |
| 374 | text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){}, | 351 | text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){}, |
| 375 | last_text_block: ?*TextBlock = null, | 352 | last_text_block: ?*TextBlock = null, |
| 376 | 353 | ||
| 354 | /// A list of `SrcFn` whose Line Number Programs have surplus capacity. | ||
| 355 | /// This is the same concept as `text_block_free_list`; see those doc comments. | ||
| 356 | dbg_line_fn_free_list: std.AutoHashMapUnmanaged(*SrcFn, void) = .{}, | ||
| 357 | dbg_line_fn_first: ?*SrcFn = null, | ||
| 358 | dbg_line_fn_last: ?*SrcFn = null, | ||
| 359 | |||
| 377 | /// `alloc_num / alloc_den` is the factor of padding when allocating. | 360 | /// `alloc_num / alloc_den` is the factor of padding when allocating. |
| 378 | const alloc_num = 4; | 361 | const alloc_num = 4; |
| 379 | const alloc_den = 3; | 362 | const alloc_den = 3; |
| ... | @@ -437,16 +420,139 @@ pub const File = struct { | ... | @@ -437,16 +420,139 @@ pub const File = struct { |
| 437 | sym_index: ?u32 = null, | 420 | sym_index: ?u32 = null, |
| 438 | }; | 421 | }; |
| 439 | 422 | ||
| 423 | pub const SrcFn = struct { | ||
| 424 | /// Offset from the beginning of the Debug Line Program header that contains this function. | ||
| 425 | off: u32, | ||
| 426 | /// Size of the line number program component belonging to this function, not | ||
| 427 | /// including padding. | ||
| 428 | len: u32, | ||
| 429 | |||
| 430 | /// Points to the previous and next neighbors, based on the offset from .debug_line. | ||
| 431 | /// This can be used to find, for example, the capacity of this `SrcFn`. | ||
| 432 | prev: ?*SrcFn, | ||
| 433 | next: ?*SrcFn, | ||
| 434 | |||
| 435 | pub const empty: SrcFn = .{ | ||
| 436 | .off = 0, | ||
| 437 | .len = 0, | ||
| 438 | .prev = null, | ||
| 439 | .next = null, | ||
| 440 | }; | ||
| 441 | }; | ||
| 442 | |||
| 443 | pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File { | ||
| 444 | assert(options.object_format == .elf); | ||
| 445 | |||
| 446 | const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) }); | ||
| 447 | errdefer file.close(); | ||
| 448 | |||
| 449 | var elf_file = try allocator.create(Elf); | ||
| 450 | errdefer allocator.destroy(elf_file); | ||
| 451 | |||
| 452 | elf_file.* = openFile(allocator, file, options) catch |err| switch (err) { | ||
| 453 | error.IncrFailed => try createFile(allocator, file, options), | ||
| 454 | else => |e| return e, | ||
| 455 | }; | ||
| 456 | |||
| 457 | elf_file.owns_file_handle = true; | ||
| 458 | return &elf_file.base; | ||
| 459 | } | ||
| 460 | |||
| 461 | /// Returns error.IncrFailed if incremental update could not be performed. | ||
| 462 | fn openFile(allocator: *Allocator, file: fs.File, options: Options) !Elf { | ||
| 463 | switch (options.output_mode) { | ||
| 464 | .Exe => {}, | ||
| 465 | .Obj => {}, | ||
| 466 | .Lib => return error.IncrFailed, | ||
| 467 | } | ||
| 468 | var self: Elf = .{ | ||
| 469 | .base = .{ | ||
| 470 | .tag = .elf, | ||
| 471 | .options = options, | ||
| 472 | }, | ||
| 473 | .allocator = allocator, | ||
| 474 | .file = file, | ||
| 475 | .owns_file_handle = false, | ||
| 476 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { | ||
| 477 | 32 => .p32, | ||
| 478 | 64 => .p64, | ||
| 479 | else => return error.UnsupportedELFArchitecture, | ||
| 480 | }, | ||
| 481 | }; | ||
| 482 | errdefer self.deinit(); | ||
| 483 | |||
| 484 | // TODO implement reading the elf file | ||
| 485 | return error.IncrFailed; | ||
| 486 | //try self.populateMissingMetadata(); | ||
| 487 | //return self; | ||
| 488 | } | ||
| 489 | |||
| 490 | /// Truncates the existing file contents and overwrites the contents. | ||
| 491 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | ||
| 492 | fn createFile(allocator: *Allocator, file: fs.File, options: Options) !Elf { | ||
| 493 | switch (options.output_mode) { | ||
| 494 | .Exe => {}, | ||
| 495 | .Obj => {}, | ||
| 496 | .Lib => return error.TODOImplementWritingLibFiles, | ||
| 497 | } | ||
| 498 | var self: Elf = .{ | ||
| 499 | .base = .{ | ||
| 500 | .tag = .elf, | ||
| 501 | .options = options, | ||
| 502 | }, | ||
| 503 | .allocator = allocator, | ||
| 504 | .file = file, | ||
| 505 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { | ||
| 506 | 32 => .p32, | ||
| 507 | 64 => .p64, | ||
| 508 | else => return error.UnsupportedELFArchitecture, | ||
| 509 | }, | ||
| 510 | .shdr_table_dirty = true, | ||
| 511 | .owns_file_handle = false, | ||
| 512 | }; | ||
| 513 | errdefer self.deinit(); | ||
| 514 | |||
| 515 | // Index 0 is always a null symbol. | ||
| 516 | try self.local_symbols.append(allocator, .{ | ||
| 517 | .st_name = 0, | ||
| 518 | .st_info = 0, | ||
| 519 | .st_other = 0, | ||
| 520 | .st_shndx = 0, | ||
| 521 | .st_value = 0, | ||
| 522 | .st_size = 0, | ||
| 523 | }); | ||
| 524 | |||
| 525 | // There must always be a null section in index 0 | ||
| 526 | try self.sections.append(allocator, .{ | ||
| 527 | .sh_name = 0, | ||
| 528 | .sh_type = elf.SHT_NULL, | ||
| 529 | .sh_flags = 0, | ||
| 530 | .sh_addr = 0, | ||
| 531 | .sh_offset = 0, | ||
| 532 | .sh_size = 0, | ||
| 533 | .sh_link = 0, | ||
| 534 | .sh_info = 0, | ||
| 535 | .sh_addralign = 0, | ||
| 536 | .sh_entsize = 0, | ||
| 537 | }); | ||
| 538 | |||
| 539 | try self.populateMissingMetadata(); | ||
| 540 | |||
| 541 | return self; | ||
| 542 | } | ||
| 543 | |||
| 440 | pub fn deinit(self: *Elf) void { | 544 | pub fn deinit(self: *Elf) void { |
| 441 | self.sections.deinit(self.allocator); | 545 | self.sections.deinit(self.allocator); |
| 442 | self.program_headers.deinit(self.allocator); | 546 | self.program_headers.deinit(self.allocator); |
| 443 | self.shstrtab.deinit(self.allocator); | 547 | self.shstrtab.deinit(self.allocator); |
| 548 | self.debug_strtab.deinit(self.allocator); | ||
| 444 | self.local_symbols.deinit(self.allocator); | 549 | self.local_symbols.deinit(self.allocator); |
| 445 | self.global_symbols.deinit(self.allocator); | 550 | self.global_symbols.deinit(self.allocator); |
| 446 | self.global_symbol_free_list.deinit(self.allocator); | 551 | self.global_symbol_free_list.deinit(self.allocator); |
| 447 | self.local_symbol_free_list.deinit(self.allocator); | 552 | self.local_symbol_free_list.deinit(self.allocator); |
| 448 | self.offset_table_free_list.deinit(self.allocator); | 553 | self.offset_table_free_list.deinit(self.allocator); |
| 449 | self.text_block_free_list.deinit(self.allocator); | 554 | self.text_block_free_list.deinit(self.allocator); |
| 555 | self.dbg_line_fn_free_list.deinit(self.allocator); | ||
| 450 | self.offset_table.deinit(self.allocator); | 556 | self.offset_table.deinit(self.allocator); |
| 451 | if (self.owns_file_handle) { | 557 | if (self.owns_file_handle) { |
| 452 | if (self.file) |f| f.close(); | 558 | if (self.file) |f| f.close(); |
| ... | @@ -467,13 +573,21 @@ pub const File = struct { | ... | @@ -467,13 +573,21 @@ pub const File = struct { |
| 467 | self.file = try dir.createFile(sub_path, .{ | 573 | self.file = try dir.createFile(sub_path, .{ |
| 468 | .truncate = false, | 574 | .truncate = false, |
| 469 | .read = true, | 575 | .read = true, |
| 470 | .mode = determineMode(self.options), | 576 | .mode = determineMode(self.base.options), |
| 471 | }); | 577 | }); |
| 472 | } | 578 | } |
| 473 | 579 | ||
| 580 | fn getDebugLineProgramOff(self: Elf) u32 { | ||
| 581 | return self.dbg_line_fn_first.?.off; | ||
| 582 | } | ||
| 583 | |||
| 584 | fn getDebugLineProgramEnd(self: Elf) u32 { | ||
| 585 | return self.dbg_line_fn_last.?.off + self.dbg_line_fn_last.?.len; | ||
| 586 | } | ||
| 587 | |||
| 474 | /// Returns end pos of collision, if any. | 588 | /// Returns end pos of collision, if any. |
| 475 | fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { | 589 | fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { |
| 476 | const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32; | 590 | const small_ptr = self.base.options.target.cpu.arch.ptrBitWidth() == 32; |
| 477 | const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); | 591 | const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); |
| 478 | if (start < ehdr_size) | 592 | if (start < ehdr_size) |
| 479 | return ehdr_size; | 593 | return ehdr_size; |
| ... | @@ -518,6 +632,8 @@ pub const File = struct { | ... | @@ -518,6 +632,8 @@ pub const File = struct { |
| 518 | } | 632 | } |
| 519 | 633 | ||
| 520 | fn allocatedSize(self: *Elf, start: u64) u64 { | 634 | fn allocatedSize(self: *Elf, start: u64) u64 { |
| 635 | if (start == 0) | ||
| 636 | return 0; | ||
| 521 | var min_pos: u64 = std.math.maxInt(u64); | 637 | var min_pos: u64 = std.math.maxInt(u64); |
| 522 | if (self.shdr_table_offset) |off| { | 638 | if (self.shdr_table_offset) |off| { |
| 523 | if (off > start and off < min_pos) min_pos = off; | 639 | if (off > start and off < min_pos) min_pos = off; |
| ... | @@ -544,6 +660,7 @@ pub const File = struct { | ... | @@ -544,6 +660,7 @@ pub const File = struct { |
| 544 | return start; | 660 | return start; |
| 545 | } | 661 | } |
| 546 | 662 | ||
| 663 | /// TODO Improve this to use a table. | ||
| 547 | fn makeString(self: *Elf, bytes: []const u8) !u32 { | 664 | fn makeString(self: *Elf, bytes: []const u8) !u32 { |
| 548 | try self.shstrtab.ensureCapacity(self.allocator, self.shstrtab.items.len + bytes.len + 1); | 665 | try self.shstrtab.ensureCapacity(self.allocator, self.shstrtab.items.len + bytes.len + 1); |
| 549 | const result = self.shstrtab.items.len; | 666 | const result = self.shstrtab.items.len; |
| ... | @@ -552,6 +669,15 @@ pub const File = struct { | ... | @@ -552,6 +669,15 @@ pub const File = struct { |
| 552 | return @intCast(u32, result); | 669 | return @intCast(u32, result); |
| 553 | } | 670 | } |
| 554 | 671 | ||
| 672 | /// TODO Improve this to use a table. | ||
| 673 | fn makeDebugString(self: *Elf, bytes: []const u8) !u32 { | ||
| 674 | try self.debug_strtab.ensureCapacity(self.allocator, self.debug_strtab.items.len + bytes.len + 1); | ||
| 675 | const result = self.debug_strtab.items.len; | ||
| 676 | self.debug_strtab.appendSliceAssumeCapacity(bytes); | ||
| 677 | self.debug_strtab.appendAssumeCapacity(0); | ||
| 678 | return @intCast(u32, result); | ||
| 679 | } | ||
| 680 | |||
| 555 | fn getString(self: *Elf, str_off: u32) []const u8 { | 681 | fn getString(self: *Elf, str_off: u32) []const u8 { |
| 556 | assert(str_off < self.shstrtab.items.len); | 682 | assert(str_off < self.shstrtab.items.len); |
| 557 | return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off)); | 683 | return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off)); |
| ... | @@ -570,16 +696,13 @@ pub const File = struct { | ... | @@ -570,16 +696,13 @@ pub const File = struct { |
| 570 | .p32 => true, | 696 | .p32 => true, |
| 571 | .p64 => false, | 697 | .p64 => false, |
| 572 | }; | 698 | }; |
| 573 | const ptr_size: u8 = switch (self.ptr_width) { | 699 | const ptr_size: u8 = self.ptrWidthBytes(); |
| 574 | .p32 => 4, | ||
| 575 | .p64 => 8, | ||
| 576 | }; | ||
| 577 | if (self.phdr_load_re_index == null) { | 700 | if (self.phdr_load_re_index == null) { |
| 578 | self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len); | 701 | self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len); |
| 579 | const file_size = self.options.program_code_size_hint; | 702 | const file_size = self.base.options.program_code_size_hint; |
| 580 | const p_align = 0x1000; | 703 | const p_align = 0x1000; |
| 581 | const off = self.findFreeSpace(file_size, p_align); | 704 | const off = self.findFreeSpace(file_size, p_align); |
| 582 | std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | 705 | log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); |
| 583 | try self.program_headers.append(self.allocator, .{ | 706 | try self.program_headers.append(self.allocator, .{ |
| 584 | .p_type = elf.PT_LOAD, | 707 | .p_type = elf.PT_LOAD, |
| 585 | .p_offset = off, | 708 | .p_offset = off, |
| ... | @@ -595,12 +718,12 @@ pub const File = struct { | ... | @@ -595,12 +718,12 @@ pub const File = struct { |
| 595 | } | 718 | } |
| 596 | if (self.phdr_got_index == null) { | 719 | if (self.phdr_got_index == null) { |
| 597 | self.phdr_got_index = @intCast(u16, self.program_headers.items.len); | 720 | self.phdr_got_index = @intCast(u16, self.program_headers.items.len); |
| 598 | const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint; | 721 | const file_size = @as(u64, ptr_size) * self.base.options.symbol_count_hint; |
| 599 | // We really only need ptr alignment but since we are using PROGBITS, linux requires | 722 | // We really only need ptr alignment but since we are using PROGBITS, linux requires |
| 600 | // page align. | 723 | // page align. |
| 601 | const p_align = 0x1000; | 724 | const p_align = 0x1000; |
| 602 | const off = self.findFreeSpace(file_size, p_align); | 725 | const off = self.findFreeSpace(file_size, p_align); |
| 603 | std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | 726 | log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); |
| 604 | // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. | 727 | // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. |
| 605 | // we'll need to re-use that function anyway, in case the GOT grows and overlaps something | 728 | // we'll need to re-use that function anyway, in case the GOT grows and overlaps something |
| 606 | // else in virtual memory. | 729 | // else in virtual memory. |
| ... | @@ -622,7 +745,7 @@ pub const File = struct { | ... | @@ -622,7 +745,7 @@ pub const File = struct { |
| 622 | assert(self.shstrtab.items.len == 0); | 745 | assert(self.shstrtab.items.len == 0); |
| 623 | try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0 | 746 | try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0 |
| 624 | const off = self.findFreeSpace(self.shstrtab.items.len, 1); | 747 | const off = self.findFreeSpace(self.shstrtab.items.len, 1); |
| 625 | std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); | 748 | log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); |
| 626 | try self.sections.append(self.allocator, .{ | 749 | try self.sections.append(self.allocator, .{ |
| 627 | .sh_name = try self.makeString(".shstrtab"), | 750 | .sh_name = try self.makeString(".shstrtab"), |
| 628 | .sh_type = elf.SHT_STRTAB, | 751 | .sh_type = elf.SHT_STRTAB, |
| ... | @@ -678,9 +801,9 @@ pub const File = struct { | ... | @@ -678,9 +801,9 @@ pub const File = struct { |
| 678 | self.symtab_section_index = @intCast(u16, self.sections.items.len); | 801 | self.symtab_section_index = @intCast(u16, self.sections.items.len); |
| 679 | const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); | 802 | const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); |
| 680 | const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); | 803 | const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); |
| 681 | const file_size = self.options.symbol_count_hint * each_size; | 804 | const file_size = self.base.options.symbol_count_hint * each_size; |
| 682 | const off = self.findFreeSpace(file_size, min_align); | 805 | const off = self.findFreeSpace(file_size, min_align); |
| 683 | std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | 806 | log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); |
| 684 | 807 | ||
| 685 | try self.sections.append(self.allocator, .{ | 808 | try self.sections.append(self.allocator, .{ |
| 686 | .sh_name = try self.makeString(".symtab"), | 809 | .sh_name = try self.makeString(".symtab"), |
| ... | @@ -698,6 +821,124 @@ pub const File = struct { | ... | @@ -698,6 +821,124 @@ pub const File = struct { |
| 698 | self.shdr_table_dirty = true; | 821 | self.shdr_table_dirty = true; |
| 699 | try self.writeSymbol(0); | 822 | try self.writeSymbol(0); |
| 700 | } | 823 | } |
| 824 | if (self.debug_str_section_index == null) { | ||
| 825 | self.debug_str_section_index = @intCast(u16, self.sections.items.len); | ||
| 826 | assert(self.debug_strtab.items.len == 0); | ||
| 827 | try self.sections.append(self.allocator, .{ | ||
| 828 | .sh_name = try self.makeString(".debug_str"), | ||
| 829 | .sh_type = elf.SHT_PROGBITS, | ||
| 830 | .sh_flags = elf.SHF_MERGE | elf.SHF_STRINGS, | ||
| 831 | .sh_addr = 0, | ||
| 832 | .sh_offset = 0, | ||
| 833 | .sh_size = self.debug_strtab.items.len, | ||
| 834 | .sh_link = 0, | ||
| 835 | .sh_info = 0, | ||
| 836 | .sh_addralign = 1, | ||
| 837 | .sh_entsize = 1, | ||
| 838 | }); | ||
| 839 | self.debug_strtab_dirty = true; | ||
| 840 | self.shdr_table_dirty = true; | ||
| 841 | } | ||
| 842 | if (self.debug_info_section_index == null) { | ||
| 843 | self.debug_info_section_index = @intCast(u16, self.sections.items.len); | ||
| 844 | |||
| 845 | const file_size_hint = 200; | ||
| 846 | const p_align = 1; | ||
| 847 | const off = self.findFreeSpace(file_size_hint, p_align); | ||
| 848 | log.debug(.link, "found .debug_info free space 0x{x} to 0x{x}\n", .{ | ||
| 849 | off, | ||
| 850 | off + file_size_hint, | ||
| 851 | }); | ||
| 852 | try self.sections.append(self.allocator, .{ | ||
| 853 | .sh_name = try self.makeString(".debug_info"), | ||
| 854 | .sh_type = elf.SHT_PROGBITS, | ||
| 855 | .sh_flags = 0, | ||
| 856 | .sh_addr = 0, | ||
| 857 | .sh_offset = off, | ||
| 858 | .sh_size = file_size_hint, | ||
| 859 | .sh_link = 0, | ||
| 860 | .sh_info = 0, | ||
| 861 | .sh_addralign = p_align, | ||
| 862 | .sh_entsize = 0, | ||
| 863 | }); | ||
| 864 | self.shdr_table_dirty = true; | ||
| 865 | self.debug_info_section_dirty = true; | ||
| 866 | } | ||
| 867 | if (self.debug_abbrev_section_index == null) { | ||
| 868 | self.debug_abbrev_section_index = @intCast(u16, self.sections.items.len); | ||
| 869 | |||
| 870 | const file_size_hint = 128; | ||
| 871 | const p_align = 1; | ||
| 872 | const off = self.findFreeSpace(file_size_hint, p_align); | ||
| 873 | log.debug(.link, "found .debug_abbrev free space 0x{x} to 0x{x}\n", .{ | ||
| 874 | off, | ||
| 875 | off + file_size_hint, | ||
| 876 | }); | ||
| 877 | try self.sections.append(self.allocator, .{ | ||
| 878 | .sh_name = try self.makeString(".debug_abbrev"), | ||
| 879 | .sh_type = elf.SHT_PROGBITS, | ||
| 880 | .sh_flags = 0, | ||
| 881 | .sh_addr = 0, | ||
| 882 | .sh_offset = off, | ||
| 883 | .sh_size = file_size_hint, | ||
| 884 | .sh_link = 0, | ||
| 885 | .sh_info = 0, | ||
| 886 | .sh_addralign = p_align, | ||
| 887 | .sh_entsize = 0, | ||
| 888 | }); | ||
| 889 | self.shdr_table_dirty = true; | ||
| 890 | self.debug_abbrev_section_dirty = true; | ||
| 891 | } | ||
| 892 | if (self.debug_aranges_section_index == null) { | ||
| 893 | self.debug_aranges_section_index = @intCast(u16, self.sections.items.len); | ||
| 894 | |||
| 895 | const file_size_hint = 160; | ||
| 896 | const p_align = 16; | ||
| 897 | const off = self.findFreeSpace(file_size_hint, p_align); | ||
| 898 | log.debug(.link, "found .debug_aranges free space 0x{x} to 0x{x}\n", .{ | ||
| 899 | off, | ||
| 900 | off + file_size_hint, | ||
| 901 | }); | ||
| 902 | try self.sections.append(self.allocator, .{ | ||
| 903 | .sh_name = try self.makeString(".debug_aranges"), | ||
| 904 | .sh_type = elf.SHT_PROGBITS, | ||
| 905 | .sh_flags = 0, | ||
| 906 | .sh_addr = 0, | ||
| 907 | .sh_offset = off, | ||
| 908 | .sh_size = file_size_hint, | ||
| 909 | .sh_link = 0, | ||
| 910 | .sh_info = 0, | ||
| 911 | .sh_addralign = p_align, | ||
| 912 | .sh_entsize = 0, | ||
| 913 | }); | ||
| 914 | self.shdr_table_dirty = true; | ||
| 915 | self.debug_aranges_section_dirty = true; | ||
| 916 | } | ||
| 917 | if (self.debug_line_section_index == null) { | ||
| 918 | self.debug_line_section_index = @intCast(u16, self.sections.items.len); | ||
| 919 | |||
| 920 | const file_size_hint = 250; | ||
| 921 | const p_align = 1; | ||
| 922 | const off = self.findFreeSpace(file_size_hint, p_align); | ||
| 923 | log.debug(.link, "found .debug_line free space 0x{x} to 0x{x}\n", .{ | ||
| 924 | off, | ||
| 925 | off + file_size_hint, | ||
| 926 | }); | ||
| 927 | try self.sections.append(self.allocator, .{ | ||
| 928 | .sh_name = try self.makeString(".debug_line"), | ||
| 929 | .sh_type = elf.SHT_PROGBITS, | ||
| 930 | .sh_flags = 0, | ||
| 931 | .sh_addr = 0, | ||
| 932 | .sh_offset = off, | ||
| 933 | .sh_size = file_size_hint, | ||
| 934 | .sh_link = 0, | ||
| 935 | .sh_info = 0, | ||
| 936 | .sh_addralign = p_align, | ||
| 937 | .sh_entsize = 0, | ||
| 938 | }); | ||
| 939 | self.shdr_table_dirty = true; | ||
| 940 | self.debug_line_header_dirty = true; | ||
| 941 | } | ||
| 701 | const shsize: u64 = switch (self.ptr_width) { | 942 | const shsize: u64 = switch (self.ptr_width) { |
| 702 | .p32 => @sizeOf(elf.Elf32_Shdr), | 943 | .p32 => @sizeOf(elf.Elf32_Shdr), |
| 703 | .p64 => @sizeOf(elf.Elf64_Shdr), | 944 | .p64 => @sizeOf(elf.Elf64_Shdr), |
| ... | @@ -733,12 +974,307 @@ pub const File = struct { | ... | @@ -733,12 +974,307 @@ pub const File = struct { |
| 733 | 974 | ||
| 734 | /// Commit pending changes and write headers. | 975 | /// Commit pending changes and write headers. |
| 735 | pub fn flush(self: *Elf) !void { | 976 | pub fn flush(self: *Elf) !void { |
| 736 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 977 | const target_endian = self.base.options.target.cpu.arch.endian(); |
| 978 | const foreign_endian = target_endian != std.Target.current.cpu.arch.endian(); | ||
| 979 | const ptr_width_bytes: u8 = self.ptrWidthBytes(); | ||
| 980 | const init_len_size: usize = switch (self.ptr_width) { | ||
| 981 | .p32 => 4, | ||
| 982 | .p64 => 12, | ||
| 983 | }; | ||
| 737 | 984 | ||
| 738 | // Unfortunately these have to be buffered and done at the end because ELF does not allow | 985 | // Unfortunately these have to be buffered and done at the end because ELF does not allow |
| 739 | // mixing local and global symbols within a symbol table. | 986 | // mixing local and global symbols within a symbol table. |
| 740 | try self.writeAllGlobalSymbols(); | 987 | try self.writeAllGlobalSymbols(); |
| 741 | 988 | ||
| 989 | if (self.debug_abbrev_section_dirty) { | ||
| 990 | const debug_abbrev_sect = &self.sections.items[self.debug_abbrev_section_index.?]; | ||
| 991 | |||
| 992 | // These are LEB encoded but since the values are all less than 127 | ||
| 993 | // we can simply append these bytes. | ||
| 994 | const abbrev_buf = [_]u8{ | ||
| 995 | 1, DW.TAG_compile_unit, DW.CHILDREN_no, // header | ||
| 996 | DW.AT_stmt_list, DW.FORM_sec_offset, | ||
| 997 | DW.AT_low_pc , DW.FORM_addr, | ||
| 998 | DW.AT_high_pc , DW.FORM_addr, | ||
| 999 | DW.AT_name , DW.FORM_strp, | ||
| 1000 | DW.AT_comp_dir , DW.FORM_strp, | ||
| 1001 | DW.AT_producer , DW.FORM_strp, | ||
| 1002 | DW.AT_language , DW.FORM_data2, | ||
| 1003 | 0, 0, // table sentinel | ||
| 1004 | |||
| 1005 | 0, 0, 0, // section sentinel | ||
| 1006 | }; | ||
| 1007 | |||
| 1008 | const needed_size = abbrev_buf.len; | ||
| 1009 | const allocated_size = self.allocatedSize(debug_abbrev_sect.sh_offset); | ||
| 1010 | if (needed_size > allocated_size) { | ||
| 1011 | debug_abbrev_sect.sh_size = 0; // free the space | ||
| 1012 | debug_abbrev_sect.sh_offset = self.findFreeSpace(needed_size, 1); | ||
| 1013 | } | ||
| 1014 | debug_abbrev_sect.sh_size = needed_size; | ||
| 1015 | log.debug(.link, ".debug_abbrev start=0x{x} end=0x{x}\n", .{ | ||
| 1016 | debug_abbrev_sect.sh_offset, | ||
| 1017 | debug_abbrev_sect.sh_offset + needed_size, | ||
| 1018 | }); | ||
| 1019 | |||
| 1020 | const abbrev_offset = 0; | ||
| 1021 | self.debug_abbrev_table_offset = abbrev_offset; | ||
| 1022 | try self.file.?.pwriteAll(&abbrev_buf, debug_abbrev_sect.sh_offset + abbrev_offset); | ||
| 1023 | if (!self.shdr_table_dirty) { | ||
| 1024 | // Then it won't get written with the others and we need to do it. | ||
| 1025 | try self.writeSectHeader(self.debug_abbrev_section_index.?); | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | self.debug_abbrev_section_dirty = false; | ||
| 1029 | } | ||
| 1030 | if (self.debug_info_section_dirty) { | ||
| 1031 | const debug_info_sect = &self.sections.items[self.debug_info_section_index.?]; | ||
| 1032 | |||
| 1033 | var di_buf = std.ArrayList(u8).init(self.allocator); | ||
| 1034 | defer di_buf.deinit(); | ||
| 1035 | |||
| 1036 | // Enough for a 64-bit header and main compilation unit without resizing. | ||
| 1037 | try di_buf.ensureCapacity(100); | ||
| 1038 | |||
| 1039 | // initial length - length of the .debug_info contribution for this compilation unit, | ||
| 1040 | // not including the initial length itself. | ||
| 1041 | // We have to come back and write it later after we know the size. | ||
| 1042 | const init_len_index = di_buf.items.len; | ||
| 1043 | di_buf.items.len += init_len_size; | ||
| 1044 | const after_init_len = di_buf.items.len; | ||
| 1045 | mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // DWARF version | ||
| 1046 | const abbrev_offset = self.debug_abbrev_table_offset.?; | ||
| 1047 | switch (self.ptr_width) { | ||
| 1048 | .p32 => { | ||
| 1049 | mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, abbrev_offset), target_endian); | ||
| 1050 | di_buf.appendAssumeCapacity(4); // address size | ||
| 1051 | }, | ||
| 1052 | .p64 => { | ||
| 1053 | mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), abbrev_offset, target_endian); | ||
| 1054 | di_buf.appendAssumeCapacity(8); // address size | ||
| 1055 | }, | ||
| 1056 | } | ||
| 1057 | // Write the form for the compile unit, which must match the abbrev table above. | ||
| 1058 | const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path); | ||
| 1059 | const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path); | ||
| 1060 | const producer_strp = try self.makeDebugString("zig (TODO version here)"); | ||
| 1061 | // Currently only one compilation unit is supported, so the address range is simply | ||
| 1062 | // identical to the main program header virtual address and memory size. | ||
| 1063 | const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | ||
| 1064 | const low_pc = text_phdr.p_vaddr; | ||
| 1065 | const high_pc = text_phdr.p_vaddr + text_phdr.p_memsz; | ||
| 1066 | |||
| 1067 | di_buf.appendAssumeCapacity(1); // abbrev tag, matching the value from the abbrev table header | ||
| 1068 | self.writeDwarfAddrAssumeCapacity(&di_buf, 0); // DW.AT_stmt_list, DW.FORM_sec_offset | ||
| 1069 | self.writeDwarfAddrAssumeCapacity(&di_buf, low_pc); | ||
| 1070 | self.writeDwarfAddrAssumeCapacity(&di_buf, high_pc); | ||
| 1071 | self.writeDwarfAddrAssumeCapacity(&di_buf, name_strp); | ||
| 1072 | self.writeDwarfAddrAssumeCapacity(&di_buf, comp_dir_strp); | ||
| 1073 | self.writeDwarfAddrAssumeCapacity(&di_buf, producer_strp); | ||
| 1074 | // We are still waiting on dwarf-std.org to assign DW_LANG_Zig a number: | ||
| 1075 | // http://dwarfstd.org/ShowIssue.php?issue=171115.1 | ||
| 1076 | // Until then we say it is C99. | ||
| 1077 | mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), DW.LANG_C99, target_endian); | ||
| 1078 | |||
| 1079 | const init_len = di_buf.items.len - after_init_len; | ||
| 1080 | switch (self.ptr_width) { | ||
| 1081 | .p32 => { | ||
| 1082 | mem.writeInt(u32, di_buf.items[init_len_index..][0..4], @intCast(u32, init_len), target_endian); | ||
| 1083 | }, | ||
| 1084 | .p64 => { | ||
| 1085 | // initial length - length of the .debug_info contribution for this compilation unit, | ||
| 1086 | // not including the initial length itself. | ||
| 1087 | di_buf.items[init_len_index..][0..4].* = [_]u8{ 0xff, 0xff, 0xff, 0xff }; | ||
| 1088 | mem.writeInt(u64, di_buf.items[init_len_index + 4..][0..8], init_len, target_endian); | ||
| 1089 | }, | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | const needed_size = di_buf.items.len; | ||
| 1093 | const allocated_size = self.allocatedSize(debug_info_sect.sh_offset); | ||
| 1094 | if (needed_size > allocated_size) { | ||
| 1095 | debug_info_sect.sh_size = 0; // free the space | ||
| 1096 | debug_info_sect.sh_offset = self.findFreeSpace(needed_size, 1); | ||
| 1097 | } | ||
| 1098 | debug_info_sect.sh_size = needed_size; | ||
| 1099 | log.debug(.link, ".debug_info start=0x{x} end=0x{x}\n", .{ | ||
| 1100 | debug_info_sect.sh_offset, | ||
| 1101 | debug_info_sect.sh_offset + needed_size, | ||
| 1102 | }); | ||
| 1103 | |||
| 1104 | try self.file.?.pwriteAll(di_buf.items, debug_info_sect.sh_offset); | ||
| 1105 | if (!self.shdr_table_dirty) { | ||
| 1106 | // Then it won't get written with the others and we need to do it. | ||
| 1107 | try self.writeSectHeader(self.debug_info_section_index.?); | ||
| 1108 | } | ||
| 1109 | |||
| 1110 | self.debug_info_section_dirty = false; | ||
| 1111 | } | ||
| 1112 | if (self.debug_aranges_section_dirty) { | ||
| 1113 | const debug_aranges_sect = &self.sections.items[self.debug_aranges_section_index.?]; | ||
| 1114 | |||
| 1115 | var di_buf = std.ArrayList(u8).init(self.allocator); | ||
| 1116 | defer di_buf.deinit(); | ||
| 1117 | |||
| 1118 | // Enough for all the data without resizing. When support for more compilation units | ||
| 1119 | // is added, the size of this section will become more variable. | ||
| 1120 | try di_buf.ensureCapacity(100); | ||
| 1121 | |||
| 1122 | // initial length - length of the .debug_aranges contribution for this compilation unit, | ||
| 1123 | // not including the initial length itself. | ||
| 1124 | // We have to come back and write it later after we know the size. | ||
| 1125 | const init_len_index = di_buf.items.len; | ||
| 1126 | di_buf.items.len += init_len_size; | ||
| 1127 | const after_init_len = di_buf.items.len; | ||
| 1128 | mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 2, target_endian); // version | ||
| 1129 | // When more than one compilation unit is supported, this will be the offset to it. | ||
| 1130 | // For now it is always at offset 0 in .debug_info. | ||
| 1131 | self.writeDwarfAddrAssumeCapacity(&di_buf, 0); // .debug_info offset | ||
| 1132 | di_buf.appendAssumeCapacity(ptr_width_bytes); // address_size | ||
| 1133 | di_buf.appendAssumeCapacity(0); // segment_selector_size | ||
| 1134 | |||
| 1135 | const end_header_offset = di_buf.items.len; | ||
| 1136 | const begin_entries_offset = mem.alignForward(end_header_offset, ptr_width_bytes * 2); | ||
| 1137 | di_buf.appendNTimesAssumeCapacity(0, begin_entries_offset - end_header_offset); | ||
| 1138 | |||
| 1139 | // Currently only one compilation unit is supported, so the address range is simply | ||
| 1140 | // identical to the main program header virtual address and memory size. | ||
| 1141 | const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | ||
| 1142 | self.writeDwarfAddrAssumeCapacity(&di_buf, text_phdr.p_vaddr); | ||
| 1143 | self.writeDwarfAddrAssumeCapacity(&di_buf, text_phdr.p_memsz); | ||
| 1144 | |||
| 1145 | // Sentinel. | ||
| 1146 | self.writeDwarfAddrAssumeCapacity(&di_buf, 0); | ||
| 1147 | self.writeDwarfAddrAssumeCapacity(&di_buf, 0); | ||
| 1148 | |||
| 1149 | // Go back and populate the initial length. | ||
| 1150 | const init_len = di_buf.items.len - after_init_len; | ||
| 1151 | switch (self.ptr_width) { | ||
| 1152 | .p32 => { | ||
| 1153 | mem.writeInt(u32, di_buf.items[init_len_index..][0..4], @intCast(u32, init_len), target_endian); | ||
| 1154 | }, | ||
| 1155 | .p64 => { | ||
| 1156 | // initial length - length of the .debug_aranges contribution for this compilation unit, | ||
| 1157 | // not including the initial length itself. | ||
| 1158 | di_buf.items[init_len_index..][0..4].* = [_]u8{ 0xff, 0xff, 0xff, 0xff }; | ||
| 1159 | mem.writeInt(u64, di_buf.items[init_len_index + 4..][0..8], init_len, target_endian); | ||
| 1160 | }, | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | const needed_size = di_buf.items.len; | ||
| 1164 | const allocated_size = self.allocatedSize(debug_aranges_sect.sh_offset); | ||
| 1165 | if (needed_size > allocated_size) { | ||
| 1166 | debug_aranges_sect.sh_size = 0; // free the space | ||
| 1167 | debug_aranges_sect.sh_offset = self.findFreeSpace(needed_size, 16); | ||
| 1168 | } | ||
| 1169 | debug_aranges_sect.sh_size = needed_size; | ||
| 1170 | log.debug(.link, ".debug_aranges start=0x{x} end=0x{x}\n", .{ | ||
| 1171 | debug_aranges_sect.sh_offset, | ||
| 1172 | debug_aranges_sect.sh_offset + needed_size, | ||
| 1173 | }); | ||
| 1174 | |||
| 1175 | try self.file.?.pwriteAll(di_buf.items, debug_aranges_sect.sh_offset); | ||
| 1176 | if (!self.shdr_table_dirty) { | ||
| 1177 | // Then it won't get written with the others and we need to do it. | ||
| 1178 | try self.writeSectHeader(self.debug_aranges_section_index.?); | ||
| 1179 | } | ||
| 1180 | |||
| 1181 | self.debug_aranges_section_dirty = false; | ||
| 1182 | } | ||
| 1183 | if (self.debug_line_header_dirty) { | ||
| 1184 | const dbg_line_prg_off = self.getDebugLineProgramOff(); | ||
| 1185 | const dbg_line_prg_end = self.getDebugLineProgramEnd(); | ||
| 1186 | assert(dbg_line_prg_end != 0); | ||
| 1187 | |||
| 1188 | const debug_line_sect = &self.sections.items[self.debug_line_section_index.?]; | ||
| 1189 | |||
| 1190 | var di_buf = std.ArrayList(u8).init(self.allocator); | ||
| 1191 | defer di_buf.deinit(); | ||
| 1192 | |||
| 1193 | // The size of this header is variable, depending on the number of directories, | ||
| 1194 | // files, and padding. We have a function to compute the upper bound size, however, | ||
| 1195 | // because it's needed for determining where to put the offset of the first `SrcFn`. | ||
| 1196 | try di_buf.ensureCapacity(self.dbgLineNeededHeaderBytes()); | ||
| 1197 | |||
| 1198 | // initial length - length of the .debug_line contribution for this compilation unit, | ||
| 1199 | // not including the initial length itself. | ||
| 1200 | const after_init_len = di_buf.items.len + init_len_size; | ||
| 1201 | const init_len = dbg_line_prg_end - after_init_len; | ||
| 1202 | switch (self.ptr_width) { | ||
| 1203 | .p32 => { | ||
| 1204 | mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len), target_endian); | ||
| 1205 | }, | ||
| 1206 | .p64 => { | ||
| 1207 | di_buf.appendNTimesAssumeCapacity(0xff, 4); | ||
| 1208 | mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), init_len, target_endian); | ||
| 1209 | }, | ||
| 1210 | } | ||
| 1211 | |||
| 1212 | mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // version | ||
| 1213 | |||
| 1214 | // Empirically, debug info consumers do not respect this field, or otherwise | ||
| 1215 | // consider it to be an error when it does not point exactly to the end of the header. | ||
| 1216 | // Therefore we rely on the NOP jump at the beginning of the Line Number Program for | ||
| 1217 | // padding rather than this field. | ||
| 1218 | const before_header_len = di_buf.items.len; | ||
| 1219 | di_buf.items.len += ptr_width_bytes; // We will come back and write this. | ||
| 1220 | const after_header_len = di_buf.items.len; | ||
| 1221 | |||
| 1222 | const opcode_base = DW.LNS_set_isa + 1; | ||
| 1223 | di_buf.appendSliceAssumeCapacity(&[_]u8{ | ||
| 1224 | 1, // minimum_instruction_length | ||
| 1225 | 1, // maximum_operations_per_instruction | ||
| 1226 | 1, // default_is_stmt | ||
| 1227 | 1, // line_base (signed) | ||
| 1228 | 1, // line_range | ||
| 1229 | opcode_base, | ||
| 1230 | |||
| 1231 | // Standard opcode lengths. The number of items here is based on `opcode_base`. | ||
| 1232 | // The value is the number of LEB128 operands the instruction takes. | ||
| 1233 | 0, // `DW.LNS_copy` | ||
| 1234 | 1, // `DW.LNS_advance_pc` | ||
| 1235 | 1, // `DW.LNS_advance_line` | ||
| 1236 | 1, // `DW.LNS_set_file` | ||
| 1237 | 1, // `DW.LNS_set_column` | ||
| 1238 | 0, // `DW.LNS_negate_stmt` | ||
| 1239 | 0, // `DW.LNS_set_basic_block` | ||
| 1240 | 0, // `DW.LNS_const_add_pc` | ||
| 1241 | 1, // `DW.LNS_fixed_advance_pc` | ||
| 1242 | 0, // `DW.LNS_set_prologue_end` | ||
| 1243 | 0, // `DW.LNS_set_epilogue_begin` | ||
| 1244 | 1, // `DW.LNS_set_isa` | ||
| 1245 | |||
| 1246 | 0, // include_directories (none except the compilation unit cwd) | ||
| 1247 | }); | ||
| 1248 | // file_names[0] | ||
| 1249 | di_buf.appendSliceAssumeCapacity(self.base.options.root_pkg.root_src_path); // relative path name | ||
| 1250 | di_buf.appendSliceAssumeCapacity(&[_]u8{ | ||
| 1251 | 0, // null byte for the relative path name | ||
| 1252 | 0, // directory_index | ||
| 1253 | 0, // mtime (TODO supply this) | ||
| 1254 | 0, // file size bytes (TODO supply this) | ||
| 1255 | 0, // file_names sentinel | ||
| 1256 | }); | ||
| 1257 | |||
| 1258 | const header_len = di_buf.items.len - after_header_len; | ||
| 1259 | switch (self.ptr_width) { | ||
| 1260 | .p32 => { | ||
| 1261 | mem.writeInt(u32, di_buf.items[before_header_len..][0..4], @intCast(u32, header_len), target_endian); | ||
| 1262 | }, | ||
| 1263 | .p64 => { | ||
| 1264 | mem.writeInt(u64, di_buf.items[before_header_len..][0..8], header_len, target_endian); | ||
| 1265 | }, | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | // We use NOPs because consumers empirically do not respect the header length field. | ||
| 1269 | if (di_buf.items.len > dbg_line_prg_off) { | ||
| 1270 | // Move the first N files to the end to make more padding for the header. | ||
| 1271 | @panic("TODO: handle .debug_line header exceeding its padding"); | ||
| 1272 | } | ||
| 1273 | const jmp_amt = dbg_line_prg_off - di_buf.items.len; | ||
| 1274 | try self.pwriteWithNops(0, di_buf.items, jmp_amt, debug_line_sect.sh_offset); | ||
| 1275 | self.debug_line_header_dirty = false; | ||
| 1276 | } | ||
| 1277 | |||
| 742 | if (self.phdr_table_dirty) { | 1278 | if (self.phdr_table_dirty) { |
| 743 | const phsize: u64 = switch (self.ptr_width) { | 1279 | const phsize: u64 = switch (self.ptr_width) { |
| 744 | .p32 => @sizeOf(elf.Elf32_Phdr), | 1280 | .p32 => @sizeOf(elf.Elf32_Phdr), |
| ... | @@ -796,7 +1332,7 @@ pub const File = struct { | ... | @@ -796,7 +1332,7 @@ pub const File = struct { |
| 796 | shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); | 1332 | shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); |
| 797 | } | 1333 | } |
| 798 | shstrtab_sect.sh_size = needed_size; | 1334 | shstrtab_sect.sh_size = needed_size; |
| 799 | std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); | 1335 | log.debug(.link, "writing shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); |
| 800 | 1336 | ||
| 801 | try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); | 1337 | try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); |
| 802 | if (!self.shdr_table_dirty) { | 1338 | if (!self.shdr_table_dirty) { |
| ... | @@ -806,6 +1342,27 @@ pub const File = struct { | ... | @@ -806,6 +1342,27 @@ pub const File = struct { |
| 806 | self.shstrtab_dirty = false; | 1342 | self.shstrtab_dirty = false; |
| 807 | } | 1343 | } |
| 808 | } | 1344 | } |
| 1345 | { | ||
| 1346 | const debug_strtab_sect = &self.sections.items[self.debug_str_section_index.?]; | ||
| 1347 | if (self.debug_strtab_dirty or self.debug_strtab.items.len != debug_strtab_sect.sh_size) { | ||
| 1348 | const allocated_size = self.allocatedSize(debug_strtab_sect.sh_offset); | ||
| 1349 | const needed_size = self.debug_strtab.items.len; | ||
| 1350 | |||
| 1351 | if (needed_size > allocated_size) { | ||
| 1352 | debug_strtab_sect.sh_size = 0; // free the space | ||
| 1353 | debug_strtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); | ||
| 1354 | } | ||
| 1355 | debug_strtab_sect.sh_size = needed_size; | ||
| 1356 | log.debug(.link, "debug_strtab start=0x{x} end=0x{x}\n", .{ debug_strtab_sect.sh_offset, debug_strtab_sect.sh_offset + needed_size }); | ||
| 1357 | |||
| 1358 | try self.file.?.pwriteAll(self.debug_strtab.items, debug_strtab_sect.sh_offset); | ||
| 1359 | if (!self.shdr_table_dirty) { | ||
| 1360 | // Then it won't get written with the others and we need to do it. | ||
| 1361 | try self.writeSectHeader(self.debug_str_section_index.?); | ||
| 1362 | } | ||
| 1363 | self.debug_strtab_dirty = false; | ||
| 1364 | } | ||
| 1365 | } | ||
| 809 | if (self.shdr_table_dirty) { | 1366 | if (self.shdr_table_dirty) { |
| 810 | const shsize: u64 = switch (self.ptr_width) { | 1367 | const shsize: u64 = switch (self.ptr_width) { |
| 811 | .p32 => @sizeOf(elf.Elf32_Shdr), | 1368 | .p32 => @sizeOf(elf.Elf32_Shdr), |
| ... | @@ -842,7 +1399,7 @@ pub const File = struct { | ... | @@ -842,7 +1399,7 @@ pub const File = struct { |
| 842 | 1399 | ||
| 843 | for (buf) |*shdr, i| { | 1400 | for (buf) |*shdr, i| { |
| 844 | shdr.* = self.sections.items[i]; | 1401 | shdr.* = self.sections.items[i]; |
| 845 | std.log.debug(.link, "writing section {}\n", .{shdr.*}); | 1402 | log.debug(.link, "writing section {}\n", .{shdr.*}); |
| 846 | if (foreign_endian) { | 1403 | if (foreign_endian) { |
| 847 | bswapAllFields(elf.Elf64_Shdr, shdr); | 1404 | bswapAllFields(elf.Elf64_Shdr, shdr); |
| 848 | } | 1405 | } |
| ... | @@ -852,8 +1409,8 @@ pub const File = struct { | ... | @@ -852,8 +1409,8 @@ pub const File = struct { |
| 852 | } | 1409 | } |
| 853 | self.shdr_table_dirty = false; | 1410 | self.shdr_table_dirty = false; |
| 854 | } | 1411 | } |
| 855 | if (self.entry_addr == null and self.options.output_mode == .Exe) { | 1412 | if (self.entry_addr == null and self.base.options.output_mode == .Exe) { |
| 856 | std.log.debug(.link, "no_entry_point_found = true\n", .{}); | 1413 | log.debug(.link, "no_entry_point_found = true\n", .{}); |
| 857 | self.error_flags.no_entry_point_found = true; | 1414 | self.error_flags.no_entry_point_found = true; |
| 858 | } else { | 1415 | } else { |
| 859 | self.error_flags.no_entry_point_found = false; | 1416 | self.error_flags.no_entry_point_found = false; |
| ... | @@ -861,14 +1418,27 @@ pub const File = struct { | ... | @@ -861,14 +1418,27 @@ pub const File = struct { |
| 861 | } | 1418 | } |
| 862 | 1419 | ||
| 863 | // The point of flush() is to commit changes, so nothing should be dirty after this. | 1420 | // The point of flush() is to commit changes, so nothing should be dirty after this. |
| 1421 | assert(!self.debug_info_section_dirty); | ||
| 1422 | assert(!self.debug_abbrev_section_dirty); | ||
| 1423 | assert(!self.debug_aranges_section_dirty); | ||
| 1424 | assert(!self.debug_line_header_dirty); | ||
| 864 | assert(!self.phdr_table_dirty); | 1425 | assert(!self.phdr_table_dirty); |
| 865 | assert(!self.shdr_table_dirty); | 1426 | assert(!self.shdr_table_dirty); |
| 866 | assert(!self.shstrtab_dirty); | 1427 | assert(!self.shstrtab_dirty); |
| 1428 | assert(!self.debug_strtab_dirty); | ||
| 867 | assert(!self.offset_table_count_dirty); | 1429 | assert(!self.offset_table_count_dirty); |
| 868 | const syms_sect = &self.sections.items[self.symtab_section_index.?]; | 1430 | const syms_sect = &self.sections.items[self.symtab_section_index.?]; |
| 869 | assert(syms_sect.sh_info == self.local_symbols.items.len); | 1431 | assert(syms_sect.sh_info == self.local_symbols.items.len); |
| 870 | } | 1432 | } |
| 871 | 1433 | ||
| 1434 | fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void { | ||
| 1435 | const target_endian = self.base.options.target.cpu.arch.endian(); | ||
| 1436 | switch (self.ptr_width) { | ||
| 1437 | .p32 => mem.writeInt(u32, buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, addr), target_endian), | ||
| 1438 | .p64 => mem.writeInt(u64, buf.addManyAsArrayAssumeCapacity(8), addr, target_endian), | ||
| 1439 | } | ||
| 1440 | } | ||
| 1441 | |||
| 872 | fn writeElfHeader(self: *Elf) !void { | 1442 | fn writeElfHeader(self: *Elf) !void { |
| 873 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; | 1443 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; |
| 874 | 1444 | ||
| ... | @@ -882,7 +1452,7 @@ pub const File = struct { | ... | @@ -882,7 +1452,7 @@ pub const File = struct { |
| 882 | }; | 1452 | }; |
| 883 | index += 1; | 1453 | index += 1; |
| 884 | 1454 | ||
| 885 | const endian = self.options.target.cpu.arch.endian(); | 1455 | const endian = self.base.options.target.cpu.arch.endian(); |
| 886 | hdr_buf[index] = switch (endian) { | 1456 | hdr_buf[index] = switch (endian) { |
| 887 | .Little => elf.ELFDATA2LSB, | 1457 | .Little => elf.ELFDATA2LSB, |
| 888 | .Big => elf.ELFDATA2MSB, | 1458 | .Big => elf.ELFDATA2MSB, |
| ... | @@ -900,10 +1470,10 @@ pub const File = struct { | ... | @@ -900,10 +1470,10 @@ pub const File = struct { |
| 900 | 1470 | ||
| 901 | assert(index == 16); | 1471 | assert(index == 16); |
| 902 | 1472 | ||
| 903 | const elf_type = switch (self.options.output_mode) { | 1473 | const elf_type = switch (self.base.options.output_mode) { |
| 904 | .Exe => elf.ET.EXEC, | 1474 | .Exe => elf.ET.EXEC, |
| 905 | .Obj => elf.ET.REL, | 1475 | .Obj => elf.ET.REL, |
| 906 | .Lib => switch (self.options.link_mode) { | 1476 | .Lib => switch (self.base.options.link_mode) { |
| 907 | .Static => elf.ET.REL, | 1477 | .Static => elf.ET.REL, |
| 908 | .Dynamic => elf.ET.DYN, | 1478 | .Dynamic => elf.ET.DYN, |
| 909 | }, | 1479 | }, |
| ... | @@ -911,7 +1481,7 @@ pub const File = struct { | ... | @@ -911,7 +1481,7 @@ pub const File = struct { |
| 911 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian); | 1481 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian); |
| 912 | index += 2; | 1482 | index += 2; |
| 913 | 1483 | ||
| 914 | const machine = self.options.target.cpu.arch.toElfMachine(); | 1484 | const machine = self.base.options.target.cpu.arch.toElfMachine(); |
| 915 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian); | 1485 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian); |
| 916 | index += 2; | 1486 | index += 2; |
| 917 | 1487 | ||
| ... | @@ -1129,6 +1699,15 @@ pub const File = struct { | ... | @@ -1129,6 +1699,15 @@ pub const File = struct { |
| 1129 | phdr.p_memsz = needed_size; | 1699 | phdr.p_memsz = needed_size; |
| 1130 | phdr.p_filesz = needed_size; | 1700 | phdr.p_filesz = needed_size; |
| 1131 | 1701 | ||
| 1702 | // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address | ||
| 1703 | // range of the compilation unit. When we expand the text section, this range changes, | ||
| 1704 | // so the .debug_info section becomes dirty. | ||
| 1705 | self.debug_info_section_dirty = true; | ||
| 1706 | // This becomes dirty for the same reason. We could potentially make this more | ||
| 1707 | // fine-grained with the addition of support for more compilation units. It is planned to | ||
| 1708 | // model each package as a different compilation unit. | ||
| 1709 | self.debug_aranges_section_dirty = true; | ||
| 1710 | |||
| 1132 | self.phdr_table_dirty = true; // TODO look into making only the one program header dirty | 1711 | self.phdr_table_dirty = true; // TODO look into making only the one program header dirty |
| 1133 | self.shdr_table_dirty = true; // TODO look into making only the one section dirty | 1712 | self.shdr_table_dirty = true; // TODO look into making only the one section dirty |
| 1134 | } | 1713 | } |
| ... | @@ -1160,17 +1739,14 @@ pub const File = struct { | ... | @@ -1160,17 +1739,14 @@ pub const File = struct { |
| 1160 | pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { | 1739 | pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { |
| 1161 | if (decl.link.local_sym_index != 0) return; | 1740 | if (decl.link.local_sym_index != 0) return; |
| 1162 | 1741 | ||
| 1163 | // Here we also ensure capacity for the free lists so that they can be appended to without fail. | ||
| 1164 | try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1); | 1742 | try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1); |
| 1165 | try self.local_symbol_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); | ||
| 1166 | try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1); | 1743 | try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1); |
| 1167 | try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); | ||
| 1168 | 1744 | ||
| 1169 | if (self.local_symbol_free_list.popOrNull()) |i| { | 1745 | if (self.local_symbol_free_list.popOrNull()) |i| { |
| 1170 | std.log.debug(.link, "reusing symbol index {} for {}\n", .{ i, decl.name }); | 1746 | log.debug(.link, "reusing symbol index {} for {}\n", .{ i, decl.name }); |
| 1171 | decl.link.local_sym_index = i; | 1747 | decl.link.local_sym_index = i; |
| 1172 | } else { | 1748 | } else { |
| 1173 | std.log.debug(.link, "allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name }); | 1749 | log.debug(.link, "allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name }); |
| 1174 | decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); | 1750 | decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); |
| 1175 | _ = self.local_symbols.addOneAssumeCapacity(); | 1751 | _ = self.local_symbols.addOneAssumeCapacity(); |
| 1176 | } | 1752 | } |
| ... | @@ -1197,23 +1773,107 @@ pub const File = struct { | ... | @@ -1197,23 +1773,107 @@ pub const File = struct { |
| 1197 | } | 1773 | } |
| 1198 | 1774 | ||
| 1199 | pub fn freeDecl(self: *Elf, decl: *Module.Decl) void { | 1775 | pub fn freeDecl(self: *Elf, decl: *Module.Decl) void { |
| 1776 | // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. | ||
| 1200 | self.freeTextBlock(&decl.link); | 1777 | self.freeTextBlock(&decl.link); |
| 1201 | if (decl.link.local_sym_index != 0) { | 1778 | if (decl.link.local_sym_index != 0) { |
| 1202 | self.local_symbol_free_list.appendAssumeCapacity(decl.link.local_sym_index); | 1779 | self.local_symbol_free_list.append(self.allocator, decl.link.local_sym_index) catch {}; |
| 1203 | self.offset_table_free_list.appendAssumeCapacity(decl.link.offset_table_index); | 1780 | self.offset_table_free_list.append(self.allocator, decl.link.offset_table_index) catch {}; |
| 1204 | 1781 | ||
| 1205 | self.local_symbols.items[decl.link.local_sym_index].st_info = 0; | 1782 | self.local_symbols.items[decl.link.local_sym_index].st_info = 0; |
| 1206 | 1783 | ||
| 1207 | decl.link.local_sym_index = 0; | 1784 | decl.link.local_sym_index = 0; |
| 1208 | } | 1785 | } |
| 1786 | // TODO make this logic match freeTextBlock. Maybe abstract the logic out since the same thing | ||
| 1787 | // is desired for both. | ||
| 1788 | _ = self.dbg_line_fn_free_list.remove(&decl.fn_link); | ||
| 1789 | if (decl.fn_link.prev) |prev| { | ||
| 1790 | _ = self.dbg_line_fn_free_list.put(self.allocator, prev, {}) catch {}; | ||
| 1791 | prev.next = decl.fn_link.next; | ||
| 1792 | if (decl.fn_link.next) |next| { | ||
| 1793 | next.prev = prev; | ||
| 1794 | } else { | ||
| 1795 | self.dbg_line_fn_last = prev; | ||
| 1796 | } | ||
| 1797 | } else if (decl.fn_link.next) |next| { | ||
| 1798 | self.dbg_line_fn_first = next; | ||
| 1799 | next.prev = null; | ||
| 1800 | } | ||
| 1801 | if (self.dbg_line_fn_first == &decl.fn_link) { | ||
| 1802 | self.dbg_line_fn_first = null; | ||
| 1803 | } | ||
| 1804 | if (self.dbg_line_fn_last == &decl.fn_link) { | ||
| 1805 | self.dbg_line_fn_last = null; | ||
| 1806 | } | ||
| 1209 | } | 1807 | } |
| 1210 | 1808 | ||
| 1211 | pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { | 1809 | pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { |
| 1810 | const tracy = trace(@src()); | ||
| 1811 | defer tracy.end(); | ||
| 1812 | |||
| 1212 | var code_buffer = std.ArrayList(u8).init(self.allocator); | 1813 | var code_buffer = std.ArrayList(u8).init(self.allocator); |
| 1213 | defer code_buffer.deinit(); | 1814 | defer code_buffer.deinit(); |
| 1214 | 1815 | ||
| 1816 | var dbg_line_buffer = std.ArrayList(u8).init(self.allocator); | ||
| 1817 | defer dbg_line_buffer.deinit(); | ||
| 1818 | |||
| 1215 | const typed_value = decl.typed_value.most_recent.typed_value; | 1819 | const typed_value = decl.typed_value.most_recent.typed_value; |
| 1216 | const code = switch (try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer)) { | 1820 | const is_fn: bool = switch (typed_value.ty.zigTypeTag()) { |
| 1821 | .Fn => true, | ||
| 1822 | else => false, | ||
| 1823 | }; | ||
| 1824 | if (is_fn) { | ||
| 1825 | // For functions we need to add a prologue to the debug line program. | ||
| 1826 | try dbg_line_buffer.ensureCapacity(26); | ||
| 1827 | |||
| 1828 | const line_off: u28 = blk: { | ||
| 1829 | if (decl.scope.cast(Module.Scope.File)) |scope_file| { | ||
| 1830 | const tree = scope_file.contents.tree; | ||
| 1831 | const file_ast_decls = tree.root_node.decls(); | ||
| 1832 | // TODO Look into improving the performance here by adding a token-index-to-line | ||
| 1833 | // lookup table. Currently this involves scanning over the source code for newlines. | ||
| 1834 | const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; | ||
| 1835 | const block = fn_proto.body().?.castTag(.Block).?; | ||
| 1836 | const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); | ||
| 1837 | break :blk @intCast(u28, line_delta); | ||
| 1838 | } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| { | ||
| 1839 | const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src; | ||
| 1840 | const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off); | ||
| 1841 | break :blk @intCast(u28, line_delta); | ||
| 1842 | } else { | ||
| 1843 | unreachable; | ||
| 1844 | } | ||
| 1845 | }; | ||
| 1846 | |||
| 1847 | const ptr_width_bytes = self.ptrWidthBytes(); | ||
| 1848 | dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{ | ||
| 1849 | DW.LNS_extended_op, | ||
| 1850 | ptr_width_bytes + 1, | ||
| 1851 | DW.LNE_set_address, | ||
| 1852 | }); | ||
| 1853 | // This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`. | ||
| 1854 | assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len); | ||
| 1855 | dbg_line_buffer.items.len += ptr_width_bytes; | ||
| 1856 | |||
| 1857 | dbg_line_buffer.appendAssumeCapacity(DW.LNS_advance_line); | ||
| 1858 | // This is the "relocatable" relative line offset from the previous function's end curly | ||
| 1859 | // to this function's begin curly. | ||
| 1860 | assert(self.getRelocDbgLineOff() == dbg_line_buffer.items.len); | ||
| 1861 | // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later. | ||
| 1862 | leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line_off); | ||
| 1863 | |||
| 1864 | dbg_line_buffer.appendAssumeCapacity(DW.LNS_set_file); | ||
| 1865 | assert(self.getRelocDbgFileIndex() == dbg_line_buffer.items.len); | ||
| 1866 | // Once we support more than one source file, this will have the ability to be more | ||
| 1867 | // than one possible value. | ||
| 1868 | const file_index = 1; | ||
| 1869 | leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index); | ||
| 1870 | |||
| 1871 | // Emit a line for the begin curly with prologue_end=false. The codegen will | ||
| 1872 | // do the work of setting prologue_end=true and epilogue_begin=true. | ||
| 1873 | dbg_line_buffer.appendAssumeCapacity(DW.LNS_copy); | ||
| 1874 | } | ||
| 1875 | const res = try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer, &dbg_line_buffer); | ||
| 1876 | const code = switch (res) { | ||
| 1217 | .externally_managed => |x| x, | 1877 | .externally_managed => |x| x, |
| 1218 | .appended => code_buffer.items, | 1878 | .appended => code_buffer.items, |
| 1219 | .fail => |em| { | 1879 | .fail => |em| { |
| ... | @@ -1223,12 +1883,9 @@ pub const File = struct { | ... | @@ -1223,12 +1883,9 @@ pub const File = struct { |
| 1223 | }, | 1883 | }, |
| 1224 | }; | 1884 | }; |
| 1225 | 1885 | ||
| 1226 | const required_alignment = typed_value.ty.abiAlignment(self.options.target); | 1886 | const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); |
| 1227 | 1887 | ||
| 1228 | const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) { | 1888 | const stt_bits: u8 = if (is_fn) elf.STT_FUNC else elf.STT_OBJECT; |
| 1229 | .Fn => elf.STT_FUNC, | ||
| 1230 | else => elf.STT_OBJECT, | ||
| 1231 | }; | ||
| 1232 | 1889 | ||
| 1233 | assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes() | 1890 | assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes() |
| 1234 | const local_sym = &self.local_symbols.items[decl.link.local_sym_index]; | 1891 | const local_sym = &self.local_symbols.items[decl.link.local_sym_index]; |
| ... | @@ -1238,11 +1895,11 @@ pub const File = struct { | ... | @@ -1238,11 +1895,11 @@ pub const File = struct { |
| 1238 | !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); | 1895 | !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); |
| 1239 | if (need_realloc) { | 1896 | if (need_realloc) { |
| 1240 | const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); | 1897 | const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); |
| 1241 | std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); | 1898 | log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); |
| 1242 | if (vaddr != local_sym.st_value) { | 1899 | if (vaddr != local_sym.st_value) { |
| 1243 | local_sym.st_value = vaddr; | 1900 | local_sym.st_value = vaddr; |
| 1244 | 1901 | ||
| 1245 | std.log.debug(.link, " (writing new offset table entry)\n", .{}); | 1902 | log.debug(.link, " (writing new offset table entry)\n", .{}); |
| 1246 | self.offset_table.items[decl.link.offset_table_index] = vaddr; | 1903 | self.offset_table.items[decl.link.offset_table_index] = vaddr; |
| 1247 | try self.writeOffsetTableEntry(decl.link.offset_table_index); | 1904 | try self.writeOffsetTableEntry(decl.link.offset_table_index); |
| 1248 | } | 1905 | } |
| ... | @@ -1260,7 +1917,7 @@ pub const File = struct { | ... | @@ -1260,7 +1917,7 @@ pub const File = struct { |
| 1260 | const decl_name = mem.spanZ(decl.name); | 1917 | const decl_name = mem.spanZ(decl.name); |
| 1261 | const name_str_index = try self.makeString(decl_name); | 1918 | const name_str_index = try self.makeString(decl_name); |
| 1262 | const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); | 1919 | const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); |
| 1263 | std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); | 1920 | log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); |
| 1264 | errdefer self.freeTextBlock(&decl.link); | 1921 | errdefer self.freeTextBlock(&decl.link); |
| 1265 | 1922 | ||
| 1266 | local_sym.* = .{ | 1923 | local_sym.* = .{ |
| ... | @@ -1281,6 +1938,94 @@ pub const File = struct { | ... | @@ -1281,6 +1938,94 @@ pub const File = struct { |
| 1281 | const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset; | 1938 | const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset; |
| 1282 | try self.file.?.pwriteAll(code, file_offset); | 1939 | try self.file.?.pwriteAll(code, file_offset); |
| 1283 | 1940 | ||
| 1941 | // If the Decl is a function, we need to update the .debug_line program. | ||
| 1942 | if (is_fn) { | ||
| 1943 | // Perform the relocation based on vaddr. | ||
| 1944 | const target_endian = self.base.options.target.cpu.arch.endian(); | ||
| 1945 | switch (self.ptr_width) { | ||
| 1946 | .p32 => { | ||
| 1947 | const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..4]; | ||
| 1948 | mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian); | ||
| 1949 | }, | ||
| 1950 | .p64 => { | ||
| 1951 | const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8]; | ||
| 1952 | mem.writeInt(u64, ptr, local_sym.st_value, target_endian); | ||
| 1953 | }, | ||
| 1954 | } | ||
| 1955 | |||
| 1956 | try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS_extended_op, 1, DW.LNE_end_sequence }); | ||
| 1957 | |||
| 1958 | // Now we have the full contents and may allocate a region to store it. | ||
| 1959 | |||
| 1960 | const debug_line_sect = &self.sections.items[self.debug_line_section_index.?]; | ||
| 1961 | const src_fn = &decl.fn_link; | ||
| 1962 | src_fn.len = @intCast(u32, dbg_line_buffer.items.len); | ||
| 1963 | if (self.dbg_line_fn_last) |last| { | ||
| 1964 | if (src_fn.next) |next| { | ||
| 1965 | // Update existing function - non-last item. | ||
| 1966 | if (src_fn.off + src_fn.len + min_nop_size > next.off) { | ||
| 1967 | // It grew too big, so we move it to a new location. | ||
| 1968 | if (src_fn.prev) |prev| { | ||
| 1969 | _ = self.dbg_line_fn_free_list.put(self.allocator, prev, {}) catch {}; | ||
| 1970 | prev.next = src_fn.next; | ||
| 1971 | } | ||
| 1972 | next.prev = src_fn.prev; | ||
| 1973 | src_fn.next = null; | ||
| 1974 | // Populate where it used to be with NOPs. | ||
| 1975 | const file_pos = debug_line_sect.sh_offset + src_fn.off; | ||
| 1976 | try self.pwriteWithNops(0, &[0]u8{}, src_fn.len, file_pos); | ||
| 1977 | // TODO Look at the free list before appending at the end. | ||
| 1978 | src_fn.prev = last; | ||
| 1979 | last.next = src_fn; | ||
| 1980 | self.dbg_line_fn_last = src_fn; | ||
| 1981 | |||
| 1982 | src_fn.off = last.off + (last.len * alloc_num / alloc_den); | ||
| 1983 | } | ||
| 1984 | } else if (src_fn.prev == null) { | ||
| 1985 | // Append new function. | ||
| 1986 | // TODO Look at the free list before appending at the end. | ||
| 1987 | src_fn.prev = last; | ||
| 1988 | last.next = src_fn; | ||
| 1989 | self.dbg_line_fn_last = src_fn; | ||
| 1990 | |||
| 1991 | src_fn.off = last.off + (last.len * alloc_num / alloc_den); | ||
| 1992 | } | ||
| 1993 | } else { | ||
| 1994 | // This is the first function of the Line Number Program. | ||
| 1995 | self.dbg_line_fn_first = src_fn; | ||
| 1996 | self.dbg_line_fn_last = src_fn; | ||
| 1997 | |||
| 1998 | src_fn.off = self.dbgLineNeededHeaderBytes() * alloc_num / alloc_den; | ||
| 1999 | } | ||
| 2000 | |||
| 2001 | const last_src_fn = self.dbg_line_fn_last.?; | ||
| 2002 | const needed_size = last_src_fn.off + last_src_fn.len; | ||
| 2003 | if (needed_size != debug_line_sect.sh_size) { | ||
| 2004 | if (needed_size > self.allocatedSize(debug_line_sect.sh_offset)) { | ||
| 2005 | const new_offset = self.findFreeSpace(needed_size, 1); | ||
| 2006 | const existing_size = last_src_fn.off; | ||
| 2007 | log.debug(.link, "moving .debug_line section: {} bytes from 0x{x} to 0x{x}\n", .{ | ||
| 2008 | existing_size, | ||
| 2009 | debug_line_sect.sh_offset, | ||
| 2010 | new_offset, | ||
| 2011 | }); | ||
| 2012 | const amt = try self.file.?.copyRangeAll(debug_line_sect.sh_offset, self.file.?, new_offset, existing_size); | ||
| 2013 | if (amt != existing_size) return error.InputOutput; | ||
| 2014 | debug_line_sect.sh_offset = new_offset; | ||
| 2015 | } | ||
| 2016 | debug_line_sect.sh_size = needed_size; | ||
| 2017 | self.shdr_table_dirty = true; // TODO look into making only the one section dirty | ||
| 2018 | self.debug_line_header_dirty = true; | ||
| 2019 | } | ||
| 2020 | const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0; | ||
| 2021 | const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0; | ||
| 2022 | |||
| 2023 | // We only have support for one compilation unit so far, so the offsets are directly | ||
| 2024 | // from the .debug_line section. | ||
| 2025 | const file_pos = debug_line_sect.sh_offset + src_fn.off; | ||
| 2026 | try self.pwriteWithNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos); | ||
| 2027 | } | ||
| 2028 | |||
| 1284 | // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. | 2029 | // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. |
| 1285 | const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; | 2030 | const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; |
| 1286 | return self.updateDeclExports(module, decl, decl_exports); | 2031 | return self.updateDeclExports(module, decl, decl_exports); |
| ... | @@ -1293,10 +2038,10 @@ pub const File = struct { | ... | @@ -1293,10 +2038,10 @@ pub const File = struct { |
| 1293 | decl: *const Module.Decl, | 2038 | decl: *const Module.Decl, |
| 1294 | exports: []const *Module.Export, | 2039 | exports: []const *Module.Export, |
| 1295 | ) !void { | 2040 | ) !void { |
| 1296 | // In addition to ensuring capacity for global_symbols, we also ensure capacity for freeing all of | 2041 | const tracy = trace(@src()); |
| 1297 | // them, so that deleting exports is guaranteed to succeed. | 2042 | defer tracy.end(); |
| 2043 | |||
| 1298 | try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len); | 2044 | try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len); |
| 1299 | try self.global_symbol_free_list.ensureCapacity(self.allocator, self.global_symbols.items.len); | ||
| 1300 | const typed_value = decl.typed_value.most_recent.typed_value; | 2045 | const typed_value = decl.typed_value.most_recent.typed_value; |
| 1301 | if (decl.link.local_sym_index == 0) return; | 2046 | if (decl.link.local_sym_index == 0) return; |
| 1302 | const decl_sym = self.local_symbols.items[decl.link.local_sym_index]; | 2047 | const decl_sym = self.local_symbols.items[decl.link.local_sym_index]; |
| ... | @@ -1361,16 +2106,38 @@ pub const File = struct { | ... | @@ -1361,16 +2106,38 @@ pub const File = struct { |
| 1361 | } | 2106 | } |
| 1362 | } | 2107 | } |
| 1363 | 2108 | ||
| 2109 | /// Must be called only after a successful call to `updateDecl`. | ||
| 2110 | pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Decl) !void { | ||
| 2111 | const tracy = trace(@src()); | ||
| 2112 | defer tracy.end(); | ||
| 2113 | |||
| 2114 | const scope_file = decl.scope.cast(Module.Scope.File).?; | ||
| 2115 | const tree = scope_file.contents.tree; | ||
| 2116 | const file_ast_decls = tree.root_node.decls(); | ||
| 2117 | // TODO Look into improving the performance here by adding a token-index-to-line | ||
| 2118 | // lookup table. Currently this involves scanning over the source code for newlines. | ||
| 2119 | const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; | ||
| 2120 | const block = fn_proto.body().?.castTag(.Block).?; | ||
| 2121 | const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); | ||
| 2122 | const casted_line_off = @intCast(u28, line_delta); | ||
| 2123 | |||
| 2124 | const shdr = &self.sections.items[self.debug_line_section_index.?]; | ||
| 2125 | const file_pos = shdr.sh_offset + decl.fn_link.off + self.getRelocDbgLineOff(); | ||
| 2126 | var data: [4]u8 = undefined; | ||
| 2127 | leb128.writeUnsignedFixed(4, &data, casted_line_off); | ||
| 2128 | try self.file.?.pwriteAll(&data, file_pos); | ||
| 2129 | } | ||
| 2130 | |||
| 1364 | pub fn deleteExport(self: *Elf, exp: Export) void { | 2131 | pub fn deleteExport(self: *Elf, exp: Export) void { |
| 1365 | const sym_index = exp.sym_index orelse return; | 2132 | const sym_index = exp.sym_index orelse return; |
| 1366 | self.global_symbol_free_list.appendAssumeCapacity(sym_index); | 2133 | self.global_symbol_free_list.append(self.allocator, sym_index) catch {}; |
| 1367 | self.global_symbols.items[sym_index].st_info = 0; | 2134 | self.global_symbols.items[sym_index].st_info = 0; |
| 1368 | } | 2135 | } |
| 1369 | 2136 | ||
| 1370 | fn writeProgHeader(self: *Elf, index: usize) !void { | 2137 | fn writeProgHeader(self: *Elf, index: usize) !void { |
| 1371 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 2138 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 1372 | const offset = self.program_headers.items[index].p_offset; | 2139 | const offset = self.program_headers.items[index].p_offset; |
| 1373 | switch (self.options.target.cpu.arch.ptrBitWidth()) { | 2140 | switch (self.base.options.target.cpu.arch.ptrBitWidth()) { |
| 1374 | 32 => { | 2141 | 32 => { |
| 1375 | var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; | 2142 | var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; |
| 1376 | if (foreign_endian) { | 2143 | if (foreign_endian) { |
| ... | @@ -1390,15 +2157,15 @@ pub const File = struct { | ... | @@ -1390,15 +2157,15 @@ pub const File = struct { |
| 1390 | } | 2157 | } |
| 1391 | 2158 | ||
| 1392 | fn writeSectHeader(self: *Elf, index: usize) !void { | 2159 | fn writeSectHeader(self: *Elf, index: usize) !void { |
| 1393 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 2160 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 1394 | const offset = self.sections.items[index].sh_offset; | 2161 | switch (self.base.options.target.cpu.arch.ptrBitWidth()) { |
| 1395 | switch (self.options.target.cpu.arch.ptrBitWidth()) { | ||
| 1396 | 32 => { | 2162 | 32 => { |
| 1397 | var shdr: [1]elf.Elf32_Shdr = undefined; | 2163 | var shdr: [1]elf.Elf32_Shdr = undefined; |
| 1398 | shdr[0] = sectHeaderTo32(self.sections.items[index]); | 2164 | shdr[0] = sectHeaderTo32(self.sections.items[index]); |
| 1399 | if (foreign_endian) { | 2165 | if (foreign_endian) { |
| 1400 | bswapAllFields(elf.Elf32_Shdr, &shdr[0]); | 2166 | bswapAllFields(elf.Elf32_Shdr, &shdr[0]); |
| 1401 | } | 2167 | } |
| 2168 | const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr); | ||
| 1402 | return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); | 2169 | return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); |
| 1403 | }, | 2170 | }, |
| 1404 | 64 => { | 2171 | 64 => { |
| ... | @@ -1406,6 +2173,7 @@ pub const File = struct { | ... | @@ -1406,6 +2173,7 @@ pub const File = struct { |
| 1406 | if (foreign_endian) { | 2173 | if (foreign_endian) { |
| 1407 | bswapAllFields(elf.Elf64_Shdr, &shdr[0]); | 2174 | bswapAllFields(elf.Elf64_Shdr, &shdr[0]); |
| 1408 | } | 2175 | } |
| 2176 | const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr); | ||
| 1409 | return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); | 2177 | return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); |
| 1410 | }, | 2178 | }, |
| 1411 | else => return error.UnsupportedArchitecture, | 2179 | else => return error.UnsupportedArchitecture, |
| ... | @@ -1415,10 +2183,7 @@ pub const File = struct { | ... | @@ -1415,10 +2183,7 @@ pub const File = struct { |
| 1415 | fn writeOffsetTableEntry(self: *Elf, index: usize) !void { | 2183 | fn writeOffsetTableEntry(self: *Elf, index: usize) !void { |
| 1416 | const shdr = &self.sections.items[self.got_section_index.?]; | 2184 | const shdr = &self.sections.items[self.got_section_index.?]; |
| 1417 | const phdr = &self.program_headers.items[self.phdr_got_index.?]; | 2185 | const phdr = &self.program_headers.items[self.phdr_got_index.?]; |
| 1418 | const entry_size: u16 = switch (self.ptr_width) { | 2186 | const entry_size: u16 = self.ptrWidthBytes(); |
| 1419 | .p32 => 4, | ||
| 1420 | .p64 => 8, | ||
| 1421 | }; | ||
| 1422 | if (self.offset_table_count_dirty) { | 2187 | if (self.offset_table_count_dirty) { |
| 1423 | // TODO Also detect virtual address collisions. | 2188 | // TODO Also detect virtual address collisions. |
| 1424 | const allocated_size = self.allocatedSize(shdr.sh_offset); | 2189 | const allocated_size = self.allocatedSize(shdr.sh_offset); |
| ... | @@ -1440,7 +2205,7 @@ pub const File = struct { | ... | @@ -1440,7 +2205,7 @@ pub const File = struct { |
| 1440 | 2205 | ||
| 1441 | self.offset_table_count_dirty = false; | 2206 | self.offset_table_count_dirty = false; |
| 1442 | } | 2207 | } |
| 1443 | const endian = self.options.target.cpu.arch.endian(); | 2208 | const endian = self.base.options.target.cpu.arch.endian(); |
| 1444 | const off = shdr.sh_offset + @as(u64, entry_size) * index; | 2209 | const off = shdr.sh_offset + @as(u64, entry_size) * index; |
| 1445 | switch (self.ptr_width) { | 2210 | switch (self.ptr_width) { |
| 1446 | .p32 => { | 2211 | .p32 => { |
| ... | @@ -1482,7 +2247,7 @@ pub const File = struct { | ... | @@ -1482,7 +2247,7 @@ pub const File = struct { |
| 1482 | syms_sect.sh_size = needed_size; // anticipating adding the global symbols later | 2247 | syms_sect.sh_size = needed_size; // anticipating adding the global symbols later |
| 1483 | self.shdr_table_dirty = true; // TODO look into only writing one section | 2248 | self.shdr_table_dirty = true; // TODO look into only writing one section |
| 1484 | } | 2249 | } |
| 1485 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 2250 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 1486 | switch (self.ptr_width) { | 2251 | switch (self.ptr_width) { |
| 1487 | .p32 => { | 2252 | .p32 => { |
| 1488 | var sym = [1]elf.Elf32_Sym{ | 2253 | var sym = [1]elf.Elf32_Sym{ |
| ... | @@ -1518,7 +2283,7 @@ pub const File = struct { | ... | @@ -1518,7 +2283,7 @@ pub const File = struct { |
| 1518 | .p32 => @sizeOf(elf.Elf32_Sym), | 2283 | .p32 => @sizeOf(elf.Elf32_Sym), |
| 1519 | .p64 => @sizeOf(elf.Elf64_Sym), | 2284 | .p64 => @sizeOf(elf.Elf64_Sym), |
| 1520 | }; | 2285 | }; |
| 1521 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 2286 | const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 1522 | const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size; | 2287 | const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size; |
| 1523 | switch (self.ptr_width) { | 2288 | switch (self.ptr_width) { |
| 1524 | .p32 => { | 2289 | .p32 => { |
| ... | @@ -1561,106 +2326,124 @@ pub const File = struct { | ... | @@ -1561,106 +2326,124 @@ pub const File = struct { |
| 1561 | }, | 2326 | }, |
| 1562 | } | 2327 | } |
| 1563 | } | 2328 | } |
| 1564 | }; | ||
| 1565 | }; | ||
| 1566 | 2329 | ||
| 1567 | /// Truncates the existing file contents and overwrites the contents. | 2330 | fn ptrWidthBytes(self: Elf) u8 { |
| 1568 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | 2331 | return switch (self.ptr_width) { |
| 1569 | pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !File.Elf { | 2332 | .p32 => 4, |
| 1570 | switch (options.output_mode) { | 2333 | .p64 => 8, |
| 1571 | .Exe => {}, | 2334 | }; |
| 1572 | .Obj => {}, | 2335 | } |
| 1573 | .Lib => return error.TODOImplementWritingLibFiles, | ||
| 1574 | } | ||
| 1575 | switch (options.object_format) { | ||
| 1576 | .c => unreachable, | ||
| 1577 | .unknown => unreachable, // TODO remove this tag from the enum | ||
| 1578 | .coff => return error.TODOImplementWritingCOFF, | ||
| 1579 | .elf => {}, | ||
| 1580 | .macho => return error.TODOImplementWritingMachO, | ||
| 1581 | .wasm => return error.TODOImplementWritingWasmObjects, | ||
| 1582 | .hex => return error.TODOImplementWritingHex, | ||
| 1583 | .raw => return error.TODOImplementWritingRaw, | ||
| 1584 | } | ||
| 1585 | 2336 | ||
| 1586 | var self: File.Elf = .{ | 2337 | /// The reloc offset for the virtual address of a function in its Line Number Program. |
| 1587 | .allocator = allocator, | 2338 | /// Size is a virtual address integer. |
| 1588 | .file = file, | 2339 | const dbg_line_vaddr_reloc_index = 3; |
| 1589 | .options = options, | ||
| 1590 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { | ||
| 1591 | 32 => .p32, | ||
| 1592 | 64 => .p64, | ||
| 1593 | else => return error.UnsupportedELFArchitecture, | ||
| 1594 | }, | ||
| 1595 | .shdr_table_dirty = true, | ||
| 1596 | .owns_file_handle = false, | ||
| 1597 | }; | ||
| 1598 | errdefer self.deinit(); | ||
| 1599 | |||
| 1600 | // Index 0 is always a null symbol. | ||
| 1601 | try self.local_symbols.append(allocator, .{ | ||
| 1602 | .st_name = 0, | ||
| 1603 | .st_info = 0, | ||
| 1604 | .st_other = 0, | ||
| 1605 | .st_shndx = 0, | ||
| 1606 | .st_value = 0, | ||
| 1607 | .st_size = 0, | ||
| 1608 | }); | ||
| 1609 | |||
| 1610 | // There must always be a null section in index 0 | ||
| 1611 | try self.sections.append(allocator, .{ | ||
| 1612 | .sh_name = 0, | ||
| 1613 | .sh_type = elf.SHT_NULL, | ||
| 1614 | .sh_flags = 0, | ||
| 1615 | .sh_addr = 0, | ||
| 1616 | .sh_offset = 0, | ||
| 1617 | .sh_size = 0, | ||
| 1618 | .sh_link = 0, | ||
| 1619 | .sh_info = 0, | ||
| 1620 | .sh_addralign = 0, | ||
| 1621 | .sh_entsize = 0, | ||
| 1622 | }); | ||
| 1623 | |||
| 1624 | try self.populateMissingMetadata(); | ||
| 1625 | |||
| 1626 | return self; | ||
| 1627 | } | ||
| 1628 | 2340 | ||
| 1629 | /// Returns error.IncrFailed if incremental update could not be performed. | 2341 | /// The reloc offset for the line offset of a function from the previous function's line. |
| 1630 | fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !File.Elf { | 2342 | /// It's a fixed-size 4-byte ULEB128. |
| 1631 | switch (options.output_mode) { | 2343 | fn getRelocDbgLineOff(self: Elf) usize { |
| 1632 | .Exe => {}, | 2344 | return dbg_line_vaddr_reloc_index + self.ptrWidthBytes() + 1; |
| 1633 | .Obj => {}, | 2345 | } |
| 1634 | .Lib => return error.IncrFailed, | ||
| 1635 | } | ||
| 1636 | switch (options.object_format) { | ||
| 1637 | .unknown => unreachable, // TODO remove this tag from the enum | ||
| 1638 | .c => unreachable, | ||
| 1639 | .coff => return error.IncrFailed, | ||
| 1640 | .elf => {}, | ||
| 1641 | .macho => return error.IncrFailed, | ||
| 1642 | .wasm => return error.IncrFailed, | ||
| 1643 | .hex => return error.IncrFailed, | ||
| 1644 | .raw => return error.IncrFailed, | ||
| 1645 | } | ||
| 1646 | var self: File.Elf = .{ | ||
| 1647 | .allocator = allocator, | ||
| 1648 | .file = file, | ||
| 1649 | .owns_file_handle = false, | ||
| 1650 | .options = options, | ||
| 1651 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { | ||
| 1652 | 32 => .p32, | ||
| 1653 | 64 => .p64, | ||
| 1654 | else => return error.UnsupportedELFArchitecture, | ||
| 1655 | }, | ||
| 1656 | }; | ||
| 1657 | errdefer self.deinit(); | ||
| 1658 | 2346 | ||
| 1659 | // TODO implement reading the elf file | 2347 | fn getRelocDbgFileIndex(self: Elf) usize { |
| 1660 | return error.IncrFailed; | 2348 | return self.getRelocDbgLineOff() + 5; |
| 1661 | //try self.populateMissingMetadata(); | 2349 | } |
| 1662 | //return self; | 2350 | |
| 1663 | } | 2351 | fn dbgLineNeededHeaderBytes(self: Elf) u32 { |
| 2352 | const directory_entry_format_count = 1; | ||
| 2353 | const file_name_entry_format_count = 1; | ||
| 2354 | const directory_count = 1; | ||
| 2355 | const file_name_count = 1; | ||
| 2356 | return @intCast(u32, 53 + directory_entry_format_count * 2 + file_name_entry_format_count * 2 + | ||
| 2357 | directory_count * 8 + file_name_count * 8 + | ||
| 2358 | // These are encoded as DW.FORM_string rather than DW.FORM_strp as we would like | ||
| 2359 | // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly. | ||
| 2360 | self.base.options.root_pkg.root_src_dir_path.len + | ||
| 2361 | self.base.options.root_pkg.root_src_path.len); | ||
| 2362 | |||
| 2363 | } | ||
| 2364 | |||
| 2365 | /// Writes to the file a buffer, prefixed and suffixed by the specified number of | ||
| 2366 | /// bytes of NOPs. Asserts each padding size is at least `min_nop_size` and total padding bytes | ||
| 2367 | /// are less than 126,976 bytes (if this limit is ever reached, this function can be | ||
| 2368 | /// improved to make more than one pwritev call, or the limit can be raised by a fixed | ||
| 2369 | /// amount by increasing the length of `vecs`). | ||
| 2370 | fn pwriteWithNops( | ||
| 2371 | self: *Elf, | ||
| 2372 | prev_padding_size: usize, | ||
| 2373 | buf: []const u8, | ||
| 2374 | next_padding_size: usize, | ||
| 2375 | offset: usize, | ||
| 2376 | ) !void { | ||
| 2377 | const page_of_nops = [1]u8{DW.LNS_negate_stmt} ** 4096; | ||
| 2378 | const three_byte_nop = [3]u8{DW.LNS_advance_pc, 0b1000_0000, 0}; | ||
| 2379 | var vecs: [32]std.os.iovec_const = undefined; | ||
| 2380 | var vec_index: usize = 0; | ||
| 2381 | { | ||
| 2382 | var padding_left = prev_padding_size; | ||
| 2383 | if (padding_left % 2 != 0) { | ||
| 2384 | vecs[vec_index] = .{ | ||
| 2385 | .iov_base = &three_byte_nop, | ||
| 2386 | .iov_len = three_byte_nop.len, | ||
| 2387 | }; | ||
| 2388 | vec_index += 1; | ||
| 2389 | padding_left -= three_byte_nop.len; | ||
| 2390 | } | ||
| 2391 | while (padding_left > page_of_nops.len) { | ||
| 2392 | vecs[vec_index] = .{ | ||
| 2393 | .iov_base = &page_of_nops, | ||
| 2394 | .iov_len = page_of_nops.len, | ||
| 2395 | }; | ||
| 2396 | vec_index += 1; | ||
| 2397 | padding_left -= page_of_nops.len; | ||
| 2398 | } | ||
| 2399 | if (padding_left > 0) { | ||
| 2400 | vecs[vec_index] = .{ | ||
| 2401 | .iov_base = &page_of_nops, | ||
| 2402 | .iov_len = padding_left, | ||
| 2403 | }; | ||
| 2404 | vec_index += 1; | ||
| 2405 | } | ||
| 2406 | } | ||
| 2407 | |||
| 2408 | vecs[vec_index] = .{ | ||
| 2409 | .iov_base = buf.ptr, | ||
| 2410 | .iov_len = buf.len, | ||
| 2411 | }; | ||
| 2412 | vec_index += 1; | ||
| 2413 | |||
| 2414 | { | ||
| 2415 | var padding_left = next_padding_size; | ||
| 2416 | if (padding_left % 2 != 0) { | ||
| 2417 | vecs[vec_index] = .{ | ||
| 2418 | .iov_base = &three_byte_nop, | ||
| 2419 | .iov_len = three_byte_nop.len, | ||
| 2420 | }; | ||
| 2421 | vec_index += 1; | ||
| 2422 | padding_left -= three_byte_nop.len; | ||
| 2423 | } | ||
| 2424 | while (padding_left > page_of_nops.len) { | ||
| 2425 | vecs[vec_index] = .{ | ||
| 2426 | .iov_base = &page_of_nops, | ||
| 2427 | .iov_len = page_of_nops.len, | ||
| 2428 | }; | ||
| 2429 | vec_index += 1; | ||
| 2430 | padding_left -= page_of_nops.len; | ||
| 2431 | } | ||
| 2432 | if (padding_left > 0) { | ||
| 2433 | vecs[vec_index] = .{ | ||
| 2434 | .iov_base = &page_of_nops, | ||
| 2435 | .iov_len = padding_left, | ||
| 2436 | }; | ||
| 2437 | vec_index += 1; | ||
| 2438 | } | ||
| 2439 | } | ||
| 2440 | try self.file.?.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); | ||
| 2441 | } | ||
| 2442 | |||
| 2443 | const min_nop_size = 2; | ||
| 2444 | |||
| 2445 | }; | ||
| 2446 | }; | ||
| 1664 | 2447 | ||
| 1665 | /// Saturating multiplication | 2448 | /// Saturating multiplication |
| 1666 | fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { | 2449 | fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { |
src-self-hosted/main.zig+12-13| ... | @@ -10,9 +10,7 @@ const Module = @import("Module.zig"); | ... | @@ -10,9 +10,7 @@ const Module = @import("Module.zig"); |
| 10 | const link = @import("link.zig"); | 10 | const link = @import("link.zig"); |
| 11 | const Package = @import("Package.zig"); | 11 | const Package = @import("Package.zig"); |
| 12 | const zir = @import("zir.zig"); | 12 | const zir = @import("zir.zig"); |
| 13 | 13 | const build_options = @import("build_options"); | |
| 14 | // TODO Improve async I/O enough that we feel comfortable doing this. | ||
| 15 | //pub const io_mode = .evented; | ||
| 16 | 14 | ||
| 17 | pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB | 15 | pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB |
| 18 | 16 | ||
| ... | @@ -47,18 +45,16 @@ pub fn log( | ... | @@ -47,18 +45,16 @@ pub fn log( |
| 47 | if (@enumToInt(level) > @enumToInt(std.log.level)) | 45 | if (@enumToInt(level) > @enumToInt(std.log.level)) |
| 48 | return; | 46 | return; |
| 49 | 47 | ||
| 50 | const scope_prefix = "(" ++ switch (scope) { | 48 | const scope_name = @tagName(scope); |
| 51 | // Uncomment to hide logs | 49 | const ok = comptime for (build_options.log_scopes) |log_scope| { |
| 52 | //.compiler, | 50 | if (mem.eql(u8, log_scope, scope_name)) |
| 53 | .module, | 51 | break true; |
| 54 | .liveness, | 52 | } else false; |
| 55 | .link, | ||
| 56 | => return, | ||
| 57 | 53 | ||
| 58 | else => @tagName(scope), | 54 | if (!ok) |
| 59 | } ++ "): "; | 55 | return; |
| 60 | 56 | ||
| 61 | const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix; | 57 | const prefix = "[" ++ @tagName(level) ++ "] " ++ "(" ++ @tagName(scope) ++ "): "; |
| 62 | 58 | ||
| 63 | // Print the message to stderr, silently ignoring any errors | 59 | // Print the message to stderr, silently ignoring any errors |
| 64 | std.debug.print(prefix ++ format, args); | 60 | std.debug.print(prefix ++ format, args); |
| ... | @@ -94,6 +90,8 @@ pub fn main() !void { | ... | @@ -94,6 +90,8 @@ pub fn main() !void { |
| 94 | return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target); | 90 | return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target); |
| 95 | } else if (mem.eql(u8, cmd, "version")) { | 91 | } else if (mem.eql(u8, cmd, "version")) { |
| 96 | // Need to set up the build script to give the version as a comptime value. | 92 | // Need to set up the build script to give the version as a comptime value. |
| 93 | // TODO when you solve this, also take a look at link.zig, there is a placeholder | ||
| 94 | // that says "TODO version here". | ||
| 97 | std.debug.print("TODO version command not implemented yet\n", .{}); | 95 | std.debug.print("TODO version command not implemented yet\n", .{}); |
| 98 | return error.Unimplemented; | 96 | return error.Unimplemented; |
| 99 | } else if (mem.eql(u8, cmd, "zen")) { | 97 | } else if (mem.eql(u8, cmd, "zen")) { |
| ... | @@ -492,6 +490,7 @@ fn buildOutputType( | ... | @@ -492,6 +490,7 @@ fn buildOutputType( |
| 492 | defer root_pkg.destroy(); | 490 | defer root_pkg.destroy(); |
| 493 | 491 | ||
| 494 | var module = try Module.init(gpa, .{ | 492 | var module = try Module.init(gpa, .{ |
| 493 | .root_name = root_name, | ||
| 495 | .target = target_info.target, | 494 | .target = target_info.target, |
| 496 | .output_mode = output_mode, | 495 | .output_mode = output_mode, |
| 497 | .root_pkg = root_pkg, | 496 | .root_pkg = root_pkg, |
src-self-hosted/test.zig+67-15| ... | @@ -4,6 +4,11 @@ const Module = @import("Module.zig"); | ... | @@ -4,6 +4,11 @@ const Module = @import("Module.zig"); |
| 4 | const Allocator = std.mem.Allocator; | 4 | const Allocator = std.mem.Allocator; |
| 5 | const zir = @import("zir.zig"); | 5 | const zir = @import("zir.zig"); |
| 6 | const Package = @import("Package.zig"); | 6 | const Package = @import("Package.zig"); |
| 7 | const build_options = @import("build_options"); | ||
| 8 | const enable_qemu: bool = build_options.enable_qemu; | ||
| 9 | const enable_wine: bool = build_options.enable_wine; | ||
| 10 | const enable_wasmtime: bool = build_options.enable_wasmtime; | ||
| 11 | const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_dir; | ||
| 7 | 12 | ||
| 8 | const cheader = @embedFile("cbe.h"); | 13 | const cheader = @embedFile("cbe.h"); |
| 9 | 14 | ||
| ... | @@ -401,8 +406,6 @@ pub const TestContext = struct { | ... | @@ -401,8 +406,6 @@ pub const TestContext = struct { |
| 401 | const root_node = try progress.start("tests", self.cases.items.len); | 406 | const root_node = try progress.start("tests", self.cases.items.len); |
| 402 | defer root_node.end(); | 407 | defer root_node.end(); |
| 403 | 408 | ||
| 404 | const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{}); | ||
| 405 | |||
| 406 | for (self.cases.items) |case| { | 409 | for (self.cases.items) |case| { |
| 407 | std.testing.base_allocator_instance.reset(); | 410 | std.testing.base_allocator_instance.reset(); |
| 408 | 411 | ||
| ... | @@ -415,13 +418,19 @@ pub const TestContext = struct { | ... | @@ -415,13 +418,19 @@ pub const TestContext = struct { |
| 415 | progress.initial_delay_ns = 0; | 418 | progress.initial_delay_ns = 0; |
| 416 | progress.refresh_rate_ns = 0; | 419 | progress.refresh_rate_ns = 0; |
| 417 | 420 | ||
| 418 | const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target); | 421 | try self.runOneCase(std.testing.allocator, &prg_node, case); |
| 419 | try self.runOneCase(std.testing.allocator, &prg_node, case, info.target); | ||
| 420 | try std.testing.allocator_instance.validate(); | 422 | try std.testing.allocator_instance.validate(); |
| 421 | } | 423 | } |
| 422 | } | 424 | } |
| 423 | 425 | ||
| 424 | fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case, target: std.Target) !void { | 426 | fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case) !void { |
| 427 | const target_info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target); | ||
| 428 | const target = target_info.target; | ||
| 429 | |||
| 430 | var arena_allocator = std.heap.ArenaAllocator.init(allocator); | ||
| 431 | defer arena_allocator.deinit(); | ||
| 432 | const arena = &arena_allocator.allocator; | ||
| 433 | |||
| 425 | var tmp = std.testing.tmpDir(.{}); | 434 | var tmp = std.testing.tmpDir(.{}); |
| 426 | defer tmp.cleanup(); | 435 | defer tmp.cleanup(); |
| 427 | 436 | ||
| ... | @@ -429,10 +438,10 @@ pub const TestContext = struct { | ... | @@ -429,10 +438,10 @@ pub const TestContext = struct { |
| 429 | const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); | 438 | const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); |
| 430 | defer root_pkg.destroy(); | 439 | defer root_pkg.destroy(); |
| 431 | 440 | ||
| 432 | const bin_name = try std.zig.binNameAlloc(allocator, "test_case", target, case.output_mode, null); | 441 | const bin_name = try std.zig.binNameAlloc(arena, "test_case", target, case.output_mode, null); |
| 433 | defer allocator.free(bin_name); | ||
| 434 | 442 | ||
| 435 | var module = try Module.init(allocator, .{ | 443 | var module = try Module.init(allocator, .{ |
| 444 | .root_name = "test_case", | ||
| 436 | .target = target, | 445 | .target = target, |
| 437 | // TODO: support tests for object file building, and library builds | 446 | // TODO: support tests for object file building, and library builds |
| 438 | // and linking. This will require a rework to support multi-file | 447 | // and linking. This will require a rework to support multi-file |
| ... | @@ -484,8 +493,7 @@ pub const TestContext = struct { | ... | @@ -484,8 +493,7 @@ pub const TestContext = struct { |
| 484 | // incremental updates | 493 | // incremental updates |
| 485 | var file = try tmp.dir.openFile(bin_name, .{ .read = true }); | 494 | var file = try tmp.dir.openFile(bin_name, .{ .read = true }); |
| 486 | defer file.close(); | 495 | defer file.close(); |
| 487 | var out = file.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!"); | 496 | var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!"); |
| 488 | defer allocator.free(out); | ||
| 489 | 497 | ||
| 490 | if (expected_output.len != out.len) { | 498 | if (expected_output.len != out.len) { |
| 491 | std.debug.warn("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); | 499 | 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 { | ... | @@ -532,8 +540,7 @@ pub const TestContext = struct { |
| 532 | var test_node = update_node.start("assert", null); | 540 | var test_node = update_node.start("assert", null); |
| 533 | test_node.activate(); | 541 | test_node.activate(); |
| 534 | defer test_node.end(); | 542 | defer test_node.end(); |
| 535 | var handled_errors = try allocator.alloc(bool, e.len); | 543 | var handled_errors = try arena.alloc(bool, e.len); |
| 536 | defer allocator.free(handled_errors); | ||
| 537 | for (handled_errors) |*h| { | 544 | for (handled_errors) |*h| { |
| 538 | h.* = false; | 545 | h.* = false; |
| 539 | } | 546 | } |
| ... | @@ -568,14 +575,59 @@ pub const TestContext = struct { | ... | @@ -568,14 +575,59 @@ pub const TestContext = struct { |
| 568 | exec_node.activate(); | 575 | exec_node.activate(); |
| 569 | defer exec_node.end(); | 576 | defer exec_node.end(); |
| 570 | 577 | ||
| 571 | try module.makeBinFileExecutable(); | 578 | var argv = std.ArrayList([]const u8).init(allocator); |
| 579 | defer argv.deinit(); | ||
| 580 | |||
| 581 | const exe_path = try std.fmt.allocPrint(arena, "." ++ std.fs.path.sep_str ++ "{}", .{bin_name}); | ||
| 582 | |||
| 583 | switch (case.target.getExternalExecutor()) { | ||
| 584 | .native => try argv.append(exe_path), | ||
| 585 | .unavailable => return, // No executor available; pass test. | ||
| 586 | |||
| 587 | .qemu => |qemu_bin_name| if (enable_qemu) { | ||
| 588 | // TODO Ability for test cases to specify whether to link libc. | ||
| 589 | const need_cross_glibc = false; // target.isGnuLibC() and self.is_linking_libc; | ||
| 590 | const glibc_dir_arg = if (need_cross_glibc) | ||
| 591 | glibc_multi_install_dir orelse return // glibc dir not available; pass test | ||
| 592 | else | ||
| 593 | null; | ||
| 594 | try argv.append(qemu_bin_name); | ||
| 595 | if (glibc_dir_arg) |dir| { | ||
| 596 | const linux_triple = try target.linuxTriple(arena); | ||
| 597 | const full_dir = try std.fs.path.join(arena, &[_][]const u8{ | ||
| 598 | dir, | ||
| 599 | linux_triple, | ||
| 600 | }); | ||
| 601 | |||
| 602 | try argv.append("-L"); | ||
| 603 | try argv.append(full_dir); | ||
| 604 | } | ||
| 605 | try argv.append(exe_path); | ||
| 606 | } else { | ||
| 607 | return; // QEMU not available; pass test. | ||
| 608 | }, | ||
| 609 | |||
| 610 | .wine => |wine_bin_name| if (enable_wine) { | ||
| 611 | try argv.append(wine_bin_name); | ||
| 612 | try argv.append(exe_path); | ||
| 613 | } else { | ||
| 614 | return; // Wine not available; pass test. | ||
| 615 | }, | ||
| 616 | |||
| 617 | .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) { | ||
| 618 | try argv.append(wasmtime_bin_name); | ||
| 619 | try argv.append("--dir=."); | ||
| 620 | try argv.append(exe_path); | ||
| 621 | } else { | ||
| 622 | return; // wasmtime not available; pass test. | ||
| 623 | }, | ||
| 624 | } | ||
| 572 | 625 | ||
| 573 | const exe_path = try std.fmt.allocPrint(allocator, "." ++ std.fs.path.sep_str ++ "{}", .{bin_name}); | 626 | try module.makeBinFileExecutable(); |
| 574 | defer allocator.free(exe_path); | ||
| 575 | 627 | ||
| 576 | break :x try std.ChildProcess.exec(.{ | 628 | break :x try std.ChildProcess.exec(.{ |
| 577 | .allocator = allocator, | 629 | .allocator = allocator, |
| 578 | .argv = &[_][]const u8{exe_path}, | 630 | .argv = argv.items, |
| 579 | .cwd_dir = tmp.dir, | 631 | .cwd_dir = tmp.dir, |
| 580 | }); | 632 | }); |
| 581 | }; | 633 | }; |
src-self-hosted/translate_c.zig+245-200| ... | @@ -8,7 +8,6 @@ const Token = std.zig.Token; | ... | @@ -8,7 +8,6 @@ const Token = std.zig.Token; |
| 8 | usingnamespace @import("clang.zig"); | 8 | usingnamespace @import("clang.zig"); |
| 9 | const ctok = std.c.tokenizer; | 9 | const ctok = std.c.tokenizer; |
| 10 | const CToken = std.c.Token; | 10 | const CToken = std.c.Token; |
| 11 | const CTokenList = std.c.tokenizer.Source.TokenList; | ||
| 12 | const mem = std.mem; | 11 | const mem = std.mem; |
| 13 | const math = std.math; | 12 | const math = std.math; |
| 14 | 13 | ||
| ... | @@ -2864,7 +2863,6 @@ fn transCharLiteral( | ... | @@ -2864,7 +2863,6 @@ fn transCharLiteral( |
| 2864 | "TODO: support character literal kind {}", | 2863 | "TODO: support character literal kind {}", |
| 2865 | .{kind}, | 2864 | .{kind}, |
| 2866 | ), | 2865 | ), |
| 2867 | else => unreachable, | ||
| 2868 | }; | 2866 | }; |
| 2869 | if (suppress_as == .no_as) { | 2867 | if (suppress_as == .no_as) { |
| 2870 | return maybeSuppressResult(rp, scope, result_used, int_lit_node); | 2868 | return maybeSuppressResult(rp, scope, result_used, int_lit_node); |
| ... | @@ -3070,13 +3068,30 @@ fn transUnaryExprOrTypeTraitExpr( | ... | @@ -3070,13 +3068,30 @@ fn transUnaryExprOrTypeTraitExpr( |
| 3070 | stmt: *const ZigClangUnaryExprOrTypeTraitExpr, | 3068 | stmt: *const ZigClangUnaryExprOrTypeTraitExpr, |
| 3071 | result_used: ResultUsed, | 3069 | result_used: ResultUsed, |
| 3072 | ) TransError!*ast.Node { | 3070 | ) TransError!*ast.Node { |
| 3071 | const loc = ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(stmt); | ||
| 3073 | const type_node = try transQualType( | 3072 | const type_node = try transQualType( |
| 3074 | rp, | 3073 | rp, |
| 3075 | ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(stmt), | 3074 | ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(stmt), |
| 3076 | ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(stmt), | 3075 | loc, |
| 3077 | ); | 3076 | ); |
| 3078 | 3077 | ||
| 3079 | const builtin_node = try rp.c.createBuiltinCall("@sizeOf", 1); | 3078 | const kind = ZigClangUnaryExprOrTypeTraitExpr_getKind(stmt); |
| 3079 | const kind_str = switch (kind) { | ||
| 3080 | .SizeOf => "@sizeOf", | ||
| 3081 | .AlignOf => "@alignOf", | ||
| 3082 | .PreferredAlignOf, | ||
| 3083 | .VecStep, | ||
| 3084 | .OpenMPRequiredSimdAlign, | ||
| 3085 | => return revertAndWarn( | ||
| 3086 | rp, | ||
| 3087 | error.UnsupportedTranslation, | ||
| 3088 | loc, | ||
| 3089 | "Unsupported type trait kind {}", | ||
| 3090 | .{kind}, | ||
| 3091 | ), | ||
| 3092 | }; | ||
| 3093 | |||
| 3094 | const builtin_node = try rp.c.createBuiltinCall(kind_str, 1); | ||
| 3080 | builtin_node.params()[0] = type_node; | 3095 | builtin_node.params()[0] = type_node; |
| 3081 | builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); | 3096 | builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); |
| 3082 | return maybeSuppressResult(rp, scope, result_used, &builtin_node.base); | 3097 | return maybeSuppressResult(rp, scope, result_used, &builtin_node.base); |
| ... | @@ -4515,7 +4530,7 @@ const CtrlFlow = struct { | ... | @@ -4515,7 +4530,7 @@ const CtrlFlow = struct { |
| 4515 | const ltoken = try appendToken(c, kw, kw_text); | 4530 | const ltoken = try appendToken(c, kw, kw_text); |
| 4516 | const label_token = if (label) |l| blk: { | 4531 | const label_token = if (label) |l| blk: { |
| 4517 | _ = try appendToken(c, .Colon, ":"); | 4532 | _ = try appendToken(c, .Colon, ":"); |
| 4518 | break :blk try appendToken(c, .Identifier, l); | 4533 | break :blk try appendIdentifier(c, l); |
| 4519 | } else null; | 4534 | } else null; |
| 4520 | return CtrlFlow{ | 4535 | return CtrlFlow{ |
| 4521 | .c = c, | 4536 | .c = c, |
| ... | @@ -5197,16 +5212,39 @@ pub fn freeErrors(errors: []ClangErrMsg) void { | ... | @@ -5197,16 +5212,39 @@ pub fn freeErrors(errors: []ClangErrMsg) void { |
| 5197 | ZigClangErrorMsg_delete(errors.ptr, errors.len); | 5212 | ZigClangErrorMsg_delete(errors.ptr, errors.len); |
| 5198 | } | 5213 | } |
| 5199 | 5214 | ||
| 5215 | const CTokIterator = struct { | ||
| 5216 | source: []const u8, | ||
| 5217 | list: []const CToken, | ||
| 5218 | i: usize = 0, | ||
| 5219 | |||
| 5220 | fn peek(self: *CTokIterator) ?CToken.Id { | ||
| 5221 | if (self.i >= self.list.len) return null; | ||
| 5222 | return self.list[self.i + 1].id; | ||
| 5223 | } | ||
| 5224 | |||
| 5225 | fn next(self: *CTokIterator) ?CToken.Id { | ||
| 5226 | if (self.i >= self.list.len) return null; | ||
| 5227 | self.i += 1; | ||
| 5228 | return self.list[self.i].id; | ||
| 5229 | } | ||
| 5230 | |||
| 5231 | fn slice(self: *CTokIterator, index: usize) []const u8 { | ||
| 5232 | const tok = self.list[index]; | ||
| 5233 | return self.source[tok.start..tok.end]; | ||
| 5234 | } | ||
| 5235 | }; | ||
| 5236 | |||
| 5200 | fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { | 5237 | fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { |
| 5201 | // TODO if we see #undef, delete it from the table | 5238 | // TODO if we see #undef, delete it from the table |
| 5202 | var it = ZigClangASTUnit_getLocalPreprocessingEntities_begin(unit); | 5239 | var it = ZigClangASTUnit_getLocalPreprocessingEntities_begin(unit); |
| 5203 | const it_end = ZigClangASTUnit_getLocalPreprocessingEntities_end(unit); | 5240 | const it_end = ZigClangASTUnit_getLocalPreprocessingEntities_end(unit); |
| 5204 | var tok_list = CTokenList.init(c.arena); | 5241 | var tok_list = std.ArrayList(CToken).init(c.gpa); |
| 5242 | defer tok_list.deinit(); | ||
| 5205 | const scope = c.global_scope; | 5243 | const scope = c.global_scope; |
| 5206 | 5244 | ||
| 5207 | while (it.I != it_end.I) : (it.I += 1) { | 5245 | while (it.I != it_end.I) : (it.I += 1) { |
| 5208 | const entity = ZigClangPreprocessingRecord_iterator_deref(it); | 5246 | const entity = ZigClangPreprocessingRecord_iterator_deref(it); |
| 5209 | tok_list.shrink(0); | 5247 | tok_list.items.len = 0; |
| 5210 | switch (ZigClangPreprocessedEntity_getKind(entity)) { | 5248 | switch (ZigClangPreprocessedEntity_getKind(entity)) { |
| 5211 | .MacroDefinitionKind => { | 5249 | .MacroDefinitionKind => { |
| 5212 | const macro = @ptrCast(*ZigClangMacroDefinitionRecord, entity); | 5250 | const macro = @ptrCast(*ZigClangMacroDefinitionRecord, entity); |
| ... | @@ -5224,38 +5262,34 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { | ... | @@ -5224,38 +5262,34 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { |
| 5224 | const begin_c = ZigClangSourceManager_getCharacterData(c.source_manager, begin_loc); | 5262 | const begin_c = ZigClangSourceManager_getCharacterData(c.source_manager, begin_loc); |
| 5225 | const slice = begin_c[0..mem.len(begin_c)]; | 5263 | const slice = begin_c[0..mem.len(begin_c)]; |
| 5226 | 5264 | ||
| 5227 | tok_list.shrink(0); | ||
| 5228 | var tokenizer = std.c.Tokenizer{ | 5265 | var tokenizer = std.c.Tokenizer{ |
| 5229 | .source = &std.c.tokenizer.Source{ | 5266 | .buffer = slice, |
| 5230 | .buffer = slice, | ||
| 5231 | .file_name = undefined, | ||
| 5232 | .tokens = undefined, | ||
| 5233 | }, | ||
| 5234 | }; | 5267 | }; |
| 5235 | while (true) { | 5268 | while (true) { |
| 5236 | const tok = tokenizer.next(); | 5269 | const tok = tokenizer.next(); |
| 5237 | switch (tok.id) { | 5270 | switch (tok.id) { |
| 5238 | .Nl, .Eof => { | 5271 | .Nl, .Eof => { |
| 5239 | try tok_list.push(tok); | 5272 | try tok_list.append(tok); |
| 5240 | break; | 5273 | break; |
| 5241 | }, | 5274 | }, |
| 5242 | .LineComment, .MultiLineComment => continue, | 5275 | .LineComment, .MultiLineComment => continue, |
| 5243 | else => {}, | 5276 | else => {}, |
| 5244 | } | 5277 | } |
| 5245 | try tok_list.push(tok); | 5278 | try tok_list.append(tok); |
| 5246 | } | 5279 | } |
| 5247 | 5280 | ||
| 5248 | var tok_it = tok_list.iterator(0); | 5281 | var tok_it = CTokIterator{ |
| 5249 | const first_tok = tok_it.next().?; | 5282 | .source = slice, |
| 5250 | assert(mem.eql(u8, slice[first_tok.start..first_tok.end], name)); | 5283 | .list = tok_list.items, |
| 5284 | }; | ||
| 5285 | assert(mem.eql(u8, tok_it.slice(0), name)); | ||
| 5251 | 5286 | ||
| 5252 | var macro_fn = false; | 5287 | var macro_fn = false; |
| 5253 | const next = tok_it.peek().?; | 5288 | switch (tok_it.peek().?) { |
| 5254 | switch (next.id) { | ||
| 5255 | .Identifier => { | 5289 | .Identifier => { |
| 5256 | // if it equals itself, ignore. for example, from stdio.h: | 5290 | // if it equals itself, ignore. for example, from stdio.h: |
| 5257 | // #define stdin stdin | 5291 | // #define stdin stdin |
| 5258 | if (mem.eql(u8, name, slice[next.start..next.end])) { | 5292 | if (mem.eql(u8, name, tok_it.slice(1))) { |
| 5259 | continue; | 5293 | continue; |
| 5260 | } | 5294 | } |
| 5261 | }, | 5295 | }, |
| ... | @@ -5266,15 +5300,15 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { | ... | @@ -5266,15 +5300,15 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { |
| 5266 | }, | 5300 | }, |
| 5267 | .LParen => { | 5301 | .LParen => { |
| 5268 | // if the name is immediately followed by a '(' then it is a function | 5302 | // if the name is immediately followed by a '(' then it is a function |
| 5269 | macro_fn = first_tok.end == next.start; | 5303 | macro_fn = tok_it.list[0].end == tok_it.list[1].start; |
| 5270 | }, | 5304 | }, |
| 5271 | else => {}, | 5305 | else => {}, |
| 5272 | } | 5306 | } |
| 5273 | 5307 | ||
| 5274 | (if (macro_fn) | 5308 | (if (macro_fn) |
| 5275 | transMacroFnDefine(c, &tok_it, slice, mangled_name, begin_loc) | 5309 | transMacroFnDefine(c, &tok_it, mangled_name, begin_loc) |
| 5276 | else | 5310 | else |
| 5277 | transMacroDefine(c, &tok_it, slice, mangled_name, begin_loc)) catch |err| switch (err) { | 5311 | transMacroDefine(c, &tok_it, mangled_name, begin_loc)) catch |err| switch (err) { |
| 5278 | error.ParseError => continue, | 5312 | error.ParseError => continue, |
| 5279 | error.OutOfMemory => |e| return e, | 5313 | error.OutOfMemory => |e| return e, |
| 5280 | }; | 5314 | }; |
| ... | @@ -5284,7 +5318,7 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { | ... | @@ -5284,7 +5318,7 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { |
| 5284 | } | 5318 | } |
| 5285 | } | 5319 | } |
| 5286 | 5320 | ||
| 5287 | fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void { | 5321 | fn transMacroDefine(c: *Context, it: *CTokIterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void { |
| 5288 | const scope = &c.global_scope.base; | 5322 | const scope = &c.global_scope.base; |
| 5289 | 5323 | ||
| 5290 | const visib_tok = try appendToken(c, .Keyword_pub, "pub"); | 5324 | const visib_tok = try appendToken(c, .Keyword_pub, "pub"); |
| ... | @@ -5292,15 +5326,15 @@ fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, n | ... | @@ -5292,15 +5326,15 @@ fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, n |
| 5292 | const name_tok = try appendIdentifier(c, name); | 5326 | const name_tok = try appendIdentifier(c, name); |
| 5293 | const eq_token = try appendToken(c, .Equal, "="); | 5327 | const eq_token = try appendToken(c, .Equal, "="); |
| 5294 | 5328 | ||
| 5295 | const init_node = try parseCExpr(c, it, source, source_loc, scope); | 5329 | const init_node = try parseCExpr(c, it, source_loc, scope); |
| 5296 | const last = it.next().?; | 5330 | const last = it.next().?; |
| 5297 | if (last.id != .Eof and last.id != .Nl) | 5331 | if (last != .Eof and last != .Nl) |
| 5298 | return failDecl( | 5332 | return failDecl( |
| 5299 | c, | 5333 | c, |
| 5300 | source_loc, | 5334 | source_loc, |
| 5301 | name, | 5335 | name, |
| 5302 | "unable to translate C expr: unexpected token .{}", | 5336 | "unable to translate C expr: unexpected token .{}", |
| 5303 | .{@tagName(last.id)}, | 5337 | .{@tagName(last)}, |
| 5304 | ); | 5338 | ); |
| 5305 | 5339 | ||
| 5306 | const semicolon_token = try appendToken(c, .Semicolon, ";"); | 5340 | const semicolon_token = try appendToken(c, .Semicolon, ";"); |
| ... | @@ -5316,7 +5350,7 @@ fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, n | ... | @@ -5316,7 +5350,7 @@ fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, n |
| 5316 | _ = try c.global_scope.macro_table.put(name, &node.base); | 5350 | _ = try c.global_scope.macro_table.put(name, &node.base); |
| 5317 | } | 5351 | } |
| 5318 | 5352 | ||
| 5319 | fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void { | 5353 | fn transMacroFnDefine(c: *Context, it: *CTokIterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void { |
| 5320 | var block_scope = try Scope.Block.init(c, &c.global_scope.base, null); | 5354 | var block_scope = try Scope.Block.init(c, &c.global_scope.base, null); |
| 5321 | defer block_scope.deinit(); | 5355 | defer block_scope.deinit(); |
| 5322 | const scope = &block_scope.base; | 5356 | const scope = &block_scope.base; |
| ... | @@ -5327,7 +5361,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5327,7 +5361,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5327 | const name_tok = try appendIdentifier(c, name); | 5361 | const name_tok = try appendIdentifier(c, name); |
| 5328 | _ = try appendToken(c, .LParen, "("); | 5362 | _ = try appendToken(c, .LParen, "("); |
| 5329 | 5363 | ||
| 5330 | if (it.next().?.id != .LParen) { | 5364 | if (it.next().? != .LParen) { |
| 5331 | return failDecl( | 5365 | return failDecl( |
| 5332 | c, | 5366 | c, |
| 5333 | source_loc, | 5367 | source_loc, |
| ... | @@ -5341,8 +5375,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5341,8 +5375,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5341 | defer fn_params.deinit(); | 5375 | defer fn_params.deinit(); |
| 5342 | 5376 | ||
| 5343 | while (true) { | 5377 | while (true) { |
| 5344 | const param_tok = it.next().?; | 5378 | if (it.next().? != .Identifier) { |
| 5345 | if (param_tok.id != .Identifier) { | ||
| 5346 | return failDecl( | 5379 | return failDecl( |
| 5347 | c, | 5380 | c, |
| 5348 | source_loc, | 5381 | source_loc, |
| ... | @@ -5352,7 +5385,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5352,7 +5385,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5352 | ); | 5385 | ); |
| 5353 | } | 5386 | } |
| 5354 | 5387 | ||
| 5355 | const mangled_name = try block_scope.makeMangledName(c, source[param_tok.start..param_tok.end]); | 5388 | const mangled_name = try block_scope.makeMangledName(c, it.slice(it.i)); |
| 5356 | const param_name_tok = try appendIdentifier(c, mangled_name); | 5389 | const param_name_tok = try appendIdentifier(c, mangled_name); |
| 5357 | _ = try appendToken(c, .Colon, ":"); | 5390 | _ = try appendToken(c, .Colon, ":"); |
| 5358 | 5391 | ||
| ... | @@ -5370,13 +5403,13 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5370,13 +5403,13 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5370 | .param_type = .{ .any_type = &any_type.base }, | 5403 | .param_type = .{ .any_type = &any_type.base }, |
| 5371 | }; | 5404 | }; |
| 5372 | 5405 | ||
| 5373 | if (it.peek().?.id != .Comma) | 5406 | if (it.peek().? != .Comma) |
| 5374 | break; | 5407 | break; |
| 5375 | _ = it.next(); | 5408 | _ = it.next(); |
| 5376 | _ = try appendToken(c, .Comma, ","); | 5409 | _ = try appendToken(c, .Comma, ","); |
| 5377 | } | 5410 | } |
| 5378 | 5411 | ||
| 5379 | if (it.next().?.id != .RParen) { | 5412 | if (it.next().? != .RParen) { |
| 5380 | return failDecl( | 5413 | return failDecl( |
| 5381 | c, | 5414 | c, |
| 5382 | source_loc, | 5415 | source_loc, |
| ... | @@ -5391,15 +5424,15 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5391,15 +5424,15 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5391 | const type_of = try c.createBuiltinCall("@TypeOf", 1); | 5424 | const type_of = try c.createBuiltinCall("@TypeOf", 1); |
| 5392 | 5425 | ||
| 5393 | const return_kw = try appendToken(c, .Keyword_return, "return"); | 5426 | const return_kw = try appendToken(c, .Keyword_return, "return"); |
| 5394 | const expr = try parseCExpr(c, it, source, source_loc, scope); | 5427 | const expr = try parseCExpr(c, it, source_loc, scope); |
| 5395 | const last = it.next().?; | 5428 | const last = it.next().?; |
| 5396 | if (last.id != .Eof and last.id != .Nl) | 5429 | if (last != .Eof and last != .Nl) |
| 5397 | return failDecl( | 5430 | return failDecl( |
| 5398 | c, | 5431 | c, |
| 5399 | source_loc, | 5432 | source_loc, |
| 5400 | name, | 5433 | name, |
| 5401 | "unable to translate C expr: unexpected token .{}", | 5434 | "unable to translate C expr: unexpected token .{}", |
| 5402 | .{@tagName(last.id)}, | 5435 | .{@tagName(last)}, |
| 5403 | ); | 5436 | ); |
| 5404 | _ = try appendToken(c, .Semicolon, ";"); | 5437 | _ = try appendToken(c, .Semicolon, ";"); |
| 5405 | const type_of_arg = if (expr.tag != .Block) expr else blk: { | 5438 | 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, | ... | @@ -5436,28 +5469,27 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5436 | 5469 | ||
| 5437 | const ParseError = Error || error{ParseError}; | 5470 | const ParseError = Error || error{ParseError}; |
| 5438 | 5471 | ||
| 5439 | fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | 5472 | fn parseCExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { |
| 5440 | const node = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 5473 | const node = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 5441 | switch (it.next().?.id) { | 5474 | switch (it.next().?) { |
| 5442 | .QuestionMark => { | 5475 | .QuestionMark => { |
| 5443 | // must come immediately after expr | 5476 | // must come immediately after expr |
| 5444 | _ = try appendToken(c, .RParen, ")"); | 5477 | _ = try appendToken(c, .RParen, ")"); |
| 5445 | const if_node = try transCreateNodeIf(c); | 5478 | const if_node = try transCreateNodeIf(c); |
| 5446 | if_node.condition = node; | 5479 | if_node.condition = node; |
| 5447 | if_node.body = try parseCPrimaryExpr(c, it, source, source_loc, scope); | 5480 | if_node.body = try parseCPrimaryExpr(c, it, source_loc, scope); |
| 5448 | if (it.next().?.id != .Colon) { | 5481 | if (it.next().? != .Colon) { |
| 5449 | const first_tok = it.list.at(0); | ||
| 5450 | try failDecl( | 5482 | try failDecl( |
| 5451 | c, | 5483 | c, |
| 5452 | source_loc, | 5484 | source_loc, |
| 5453 | source[first_tok.start..first_tok.end], | 5485 | it.slice(0), |
| 5454 | "unable to translate C expr: expected ':'", | 5486 | "unable to translate C expr: expected ':'", |
| 5455 | .{}, | 5487 | .{}, |
| 5456 | ); | 5488 | ); |
| 5457 | return error.ParseError; | 5489 | return error.ParseError; |
| 5458 | } | 5490 | } |
| 5459 | if_node.@"else" = try transCreateNodeElse(c); | 5491 | if_node.@"else" = try transCreateNodeElse(c); |
| 5460 | if_node.@"else".?.body = try parseCPrimaryExpr(c, it, source, source_loc, scope); | 5492 | if_node.@"else".?.body = try parseCPrimaryExpr(c, it, source_loc, scope); |
| 5461 | return &if_node.base; | 5493 | return &if_node.base; |
| 5462 | }, | 5494 | }, |
| 5463 | .Comma => { | 5495 | .Comma => { |
| ... | @@ -5480,10 +5512,10 @@ fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_ | ... | @@ -5480,10 +5512,10 @@ fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_ |
| 5480 | }; | 5512 | }; |
| 5481 | try block_scope.statements.append(&op_node.base); | 5513 | try block_scope.statements.append(&op_node.base); |
| 5482 | 5514 | ||
| 5483 | last = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 5515 | last = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 5484 | _ = try appendToken(c, .Semicolon, ";"); | 5516 | _ = try appendToken(c, .Semicolon, ";"); |
| 5485 | if (it.next().?.id != .Comma) { | 5517 | if (it.next().? != .Comma) { |
| 5486 | _ = it.prev(); | 5518 | it.i -= 1; |
| 5487 | break; | 5519 | break; |
| 5488 | } | 5520 | } |
| 5489 | } | 5521 | } |
| ... | @@ -5494,70 +5526,74 @@ fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_ | ... | @@ -5494,70 +5526,74 @@ fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_ |
| 5494 | return &block_node.base; | 5526 | return &block_node.base; |
| 5495 | }, | 5527 | }, |
| 5496 | else => { | 5528 | else => { |
| 5497 | _ = it.prev(); | 5529 | it.i -= 1; |
| 5498 | return node; | 5530 | return node; |
| 5499 | }, | 5531 | }, |
| 5500 | } | 5532 | } |
| 5501 | } | 5533 | } |
| 5502 | 5534 | ||
| 5503 | fn parseCNumLit(c: *Context, tok: *CToken, source: []const u8, source_loc: ZigClangSourceLocation) ParseError!*ast.Node { | 5535 | fn parseCNumLit(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation) ParseError!*ast.Node { |
| 5504 | var lit_bytes = source[tok.start..tok.end]; | 5536 | var lit_bytes = it.slice(it.i); |
| 5505 | 5537 | ||
| 5506 | if (tok.id == .IntegerLiteral) { | 5538 | switch (it.list[it.i].id) { |
| 5507 | if (lit_bytes.len > 2 and lit_bytes[0] == '0') { | 5539 | .IntegerLiteral => |suffix| { |
| 5508 | switch (lit_bytes[1]) { | 5540 | if (lit_bytes.len > 2 and lit_bytes[0] == '0') { |
| 5509 | '0'...'7' => { | 5541 | switch (lit_bytes[1]) { |
| 5510 | // Octal | 5542 | '0'...'7' => { |
| 5511 | lit_bytes = try std.fmt.allocPrint(c.arena, "0o{}", .{lit_bytes}); | 5543 | // Octal |
| 5512 | }, | 5544 | lit_bytes = try std.fmt.allocPrint(c.arena, "0o{}", .{lit_bytes}); |
| 5513 | 'X' => { | 5545 | }, |
| 5514 | // Hexadecimal with capital X, valid in C but not in Zig | 5546 | 'X' => { |
| 5515 | lit_bytes = try std.fmt.allocPrint(c.arena, "0x{}", .{lit_bytes[2..]}); | 5547 | // Hexadecimal with capital X, valid in C but not in Zig |
| 5516 | }, | 5548 | lit_bytes = try std.fmt.allocPrint(c.arena, "0x{}", .{lit_bytes[2..]}); |
| 5517 | else => {}, | 5549 | }, |
| 5550 | else => {}, | ||
| 5551 | } | ||
| 5518 | } | 5552 | } |
| 5519 | } | ||
| 5520 | 5553 | ||
| 5521 | if (tok.id.IntegerLiteral == .None) { | 5554 | if (suffix == .none) { |
| 5522 | return transCreateNodeInt(c, lit_bytes); | 5555 | return transCreateNodeInt(c, lit_bytes); |
| 5523 | } | 5556 | } |
| 5524 | 5557 | ||
| 5525 | const cast_node = try c.createBuiltinCall("@as", 2); | 5558 | const cast_node = try c.createBuiltinCall("@as", 2); |
| 5526 | cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (tok.id.IntegerLiteral) { | 5559 | cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (suffix) { |
| 5527 | .U => "c_uint", | 5560 | .u => "c_uint", |
| 5528 | .L => "c_long", | 5561 | .l => "c_long", |
| 5529 | .LU => "c_ulong", | 5562 | .lu => "c_ulong", |
| 5530 | .LL => "c_longlong", | 5563 | .ll => "c_longlong", |
| 5531 | .LLU => "c_ulonglong", | 5564 | .llu => "c_ulonglong", |
| 5532 | else => unreachable, | 5565 | else => unreachable, |
| 5533 | }); | 5566 | }); |
| 5534 | lit_bytes = lit_bytes[0 .. lit_bytes.len - switch (tok.id.IntegerLiteral) { | 5567 | lit_bytes = lit_bytes[0 .. lit_bytes.len - switch (suffix) { |
| 5535 | .U, .L => @as(u8, 1), | 5568 | .u, .l => @as(u8, 1), |
| 5536 | .LU, .LL => 2, | 5569 | .lu, .ll => 2, |
| 5537 | .LLU => 3, | 5570 | .llu => 3, |
| 5538 | else => unreachable, | 5571 | else => unreachable, |
| 5539 | }]; | 5572 | }]; |
| 5540 | _ = try appendToken(c, .Comma, ","); | 5573 | _ = try appendToken(c, .Comma, ","); |
| 5541 | cast_node.params()[1] = try transCreateNodeInt(c, lit_bytes); | 5574 | cast_node.params()[1] = try transCreateNodeInt(c, lit_bytes); |
| 5542 | cast_node.rparen_token = try appendToken(c, .RParen, ")"); | 5575 | cast_node.rparen_token = try appendToken(c, .RParen, ")"); |
| 5543 | return &cast_node.base; | 5576 | return &cast_node.base; |
| 5544 | } else if (tok.id == .FloatLiteral) { | 5577 | }, |
| 5545 | if (lit_bytes[0] == '.') | 5578 | .FloatLiteral => |suffix| { |
| 5546 | lit_bytes = try std.fmt.allocPrint(c.arena, "0{}", .{lit_bytes}); | 5579 | if (lit_bytes[0] == '.') |
| 5547 | if (tok.id.FloatLiteral == .None) { | 5580 | lit_bytes = try std.fmt.allocPrint(c.arena, "0{}", .{lit_bytes}); |
| 5548 | return transCreateNodeFloat(c, lit_bytes); | 5581 | if (suffix == .none) { |
| 5549 | } | 5582 | return transCreateNodeFloat(c, lit_bytes); |
| 5550 | const cast_node = try c.createBuiltinCall("@as", 2); | 5583 | } |
| 5551 | cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (tok.id.FloatLiteral) { | 5584 | const cast_node = try c.createBuiltinCall("@as", 2); |
| 5552 | .F => "f32", | 5585 | cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (suffix) { |
| 5553 | .L => "c_longdouble", | 5586 | .f => "f32", |
| 5554 | else => unreachable, | 5587 | .l => "c_longdouble", |
| 5555 | }); | 5588 | else => unreachable, |
| 5556 | _ = try appendToken(c, .Comma, ","); | 5589 | }); |
| 5557 | cast_node.params()[1] = try transCreateNodeFloat(c, lit_bytes[0 .. lit_bytes.len - 1]); | 5590 | _ = try appendToken(c, .Comma, ","); |
| 5558 | cast_node.rparen_token = try appendToken(c, .RParen, ")"); | 5591 | cast_node.params()[1] = try transCreateNodeFloat(c, lit_bytes[0 .. lit_bytes.len - 1]); |
| 5559 | return &cast_node.base; | 5592 | cast_node.rparen_token = try appendToken(c, .RParen, ")"); |
| 5560 | } else unreachable; | 5593 | return &cast_node.base; |
| 5594 | }, | ||
| 5595 | else => unreachable, | ||
| 5596 | } | ||
| 5561 | } | 5597 | } |
| 5562 | 5598 | ||
| 5563 | fn zigifyEscapeSequences(ctx: *Context, source_bytes: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ![]const u8 { | 5599 | 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 | ... | @@ -5720,13 +5756,13 @@ fn zigifyEscapeSequences(ctx: *Context, source_bytes: []const u8, name: []const |
| 5720 | return bytes[0..i]; | 5756 | return bytes[0..i]; |
| 5721 | } | 5757 | } |
| 5722 | 5758 | ||
| 5723 | fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | 5759 | fn parseCPrimaryExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { |
| 5724 | const tok = it.next().?; | 5760 | const tok = it.next().?; |
| 5725 | switch (tok.id) { | 5761 | const slice = it.slice(it.i); |
| 5762 | switch (tok) { | ||
| 5726 | .CharLiteral => { | 5763 | .CharLiteral => { |
| 5727 | const first_tok = it.list.at(0); | 5764 | if (slice[0] != '\'' or slice[1] == '\\' or slice.len == 3) { |
| 5728 | if (source[tok.start] != '\'' or source[tok.start + 1] == '\\' or tok.end - tok.start == 3) { | 5765 | const token = try appendToken(c, .CharLiteral, try zigifyEscapeSequences(c, slice, it.slice(0), source_loc)); |
| 5729 | const token = try appendToken(c, .CharLiteral, try zigifyEscapeSequences(c, source[tok.start..tok.end], source[first_tok.start..first_tok.end], source_loc)); | ||
| 5730 | const node = try c.arena.create(ast.Node.OneToken); | 5766 | const node = try c.arena.create(ast.Node.OneToken); |
| 5731 | node.* = .{ | 5767 | node.* = .{ |
| 5732 | .base = .{ .tag = .CharLiteral }, | 5768 | .base = .{ .tag = .CharLiteral }, |
| ... | @@ -5734,7 +5770,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5734,7 +5770,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5734 | }; | 5770 | }; |
| 5735 | return &node.base; | 5771 | return &node.base; |
| 5736 | } else { | 5772 | } else { |
| 5737 | const token = try appendTokenFmt(c, .IntegerLiteral, "0x{x}", .{source[tok.start + 1 .. tok.end - 1]}); | 5773 | const token = try appendTokenFmt(c, .IntegerLiteral, "0x{x}", .{slice[1 .. slice.len - 1]}); |
| 5738 | const node = try c.arena.create(ast.Node.OneToken); | 5774 | const node = try c.arena.create(ast.Node.OneToken); |
| 5739 | node.* = .{ | 5775 | node.* = .{ |
| 5740 | .base = .{ .tag = .IntegerLiteral }, | 5776 | .base = .{ .tag = .IntegerLiteral }, |
| ... | @@ -5744,8 +5780,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5744,8 +5780,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5744 | } | 5780 | } |
| 5745 | }, | 5781 | }, |
| 5746 | .StringLiteral => { | 5782 | .StringLiteral => { |
| 5747 | const first_tok = it.list.at(0); | 5783 | const token = try appendToken(c, .StringLiteral, try zigifyEscapeSequences(c, slice, it.slice(0), source_loc)); |
| 5748 | const token = try appendToken(c, .StringLiteral, try zigifyEscapeSequences(c, source[tok.start..tok.end], source[first_tok.start..first_tok.end], source_loc)); | ||
| 5749 | const node = try c.arena.create(ast.Node.OneToken); | 5784 | const node = try c.arena.create(ast.Node.OneToken); |
| 5750 | node.* = .{ | 5785 | node.* = .{ |
| 5751 | .base = .{ .tag = .StringLiteral }, | 5786 | .base = .{ .tag = .StringLiteral }, |
| ... | @@ -5754,7 +5789,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5754,7 +5789,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5754 | return &node.base; | 5789 | return &node.base; |
| 5755 | }, | 5790 | }, |
| 5756 | .IntegerLiteral, .FloatLiteral => { | 5791 | .IntegerLiteral, .FloatLiteral => { |
| 5757 | return parseCNumLit(c, tok, source, source_loc); | 5792 | return parseCNumLit(c, it, source_loc); |
| 5758 | }, | 5793 | }, |
| 5759 | // eventually this will be replaced by std.c.parse which will handle these correctly | 5794 | // eventually this will be replaced by std.c.parse which will handle these correctly |
| 5760 | .Keyword_void => return transCreateNodeIdentifierUnchecked(c, "c_void"), | 5795 | .Keyword_void => return transCreateNodeIdentifierUnchecked(c, "c_void"), |
| ... | @@ -5764,22 +5799,50 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5764,22 +5799,50 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5764 | .Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_int"), | 5799 | .Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_int"), |
| 5765 | .Keyword_float => return transCreateNodeIdentifierUnchecked(c, "f32"), | 5800 | .Keyword_float => return transCreateNodeIdentifierUnchecked(c, "f32"), |
| 5766 | .Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_short"), | 5801 | .Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_short"), |
| 5767 | .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "c_char"), | 5802 | .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "u8"), |
| 5768 | .Keyword_unsigned => return transCreateNodeIdentifierUnchecked(c, "c_uint"), | 5803 | .Keyword_unsigned => if (it.next()) |t| switch (t) { |
| 5804 | .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "u8"), | ||
| 5805 | .Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_ushort"), | ||
| 5806 | .Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_uint"), | ||
| 5807 | .Keyword_long => if (it.peek() != null and it.peek().? == .Keyword_long) { | ||
| 5808 | _ = it.next(); | ||
| 5809 | return transCreateNodeIdentifierUnchecked(c, "c_ulonglong"); | ||
| 5810 | } else return transCreateNodeIdentifierUnchecked(c, "c_ulong"), | ||
| 5811 | else => { | ||
| 5812 | it.i -= 1; | ||
| 5813 | return transCreateNodeIdentifierUnchecked(c, "c_uint"); | ||
| 5814 | }, | ||
| 5815 | } else { | ||
| 5816 | return transCreateNodeIdentifierUnchecked(c, "c_uint"); | ||
| 5817 | }, | ||
| 5818 | .Keyword_signed => if (it.next()) |t| switch (t) { | ||
| 5819 | .Keyword_char => return transCreateNodeIdentifierUnchecked(c, "i8"), | ||
| 5820 | .Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_short"), | ||
| 5821 | .Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_int"), | ||
| 5822 | .Keyword_long => if (it.peek() != null and it.peek().? == .Keyword_long) { | ||
| 5823 | _ = it.next(); | ||
| 5824 | return transCreateNodeIdentifierUnchecked(c, "c_longlong"); | ||
| 5825 | } else return transCreateNodeIdentifierUnchecked(c, "c_long"), | ||
| 5826 | else => { | ||
| 5827 | it.i -= 1; | ||
| 5828 | return transCreateNodeIdentifierUnchecked(c, "c_int"); | ||
| 5829 | }, | ||
| 5830 | } else { | ||
| 5831 | return transCreateNodeIdentifierUnchecked(c, "c_int"); | ||
| 5832 | }, | ||
| 5769 | .Identifier => { | 5833 | .Identifier => { |
| 5770 | const mangled_name = scope.getAlias(source[tok.start..tok.end]); | 5834 | const mangled_name = scope.getAlias(it.slice(it.i)); |
| 5771 | return transCreateNodeIdentifier(c, mangled_name); | 5835 | return transCreateNodeIdentifier(c, mangled_name); |
| 5772 | }, | 5836 | }, |
| 5773 | .LParen => { | 5837 | .LParen => { |
| 5774 | const inner_node = try parseCExpr(c, it, source, source_loc, scope); | 5838 | const inner_node = try parseCExpr(c, it, source_loc, scope); |
| 5775 | 5839 | ||
| 5776 | const next_id = it.next().?.id; | 5840 | const next_id = it.next().?; |
| 5777 | if (next_id != .RParen) { | 5841 | if (next_id != .RParen) { |
| 5778 | const first_tok = it.list.at(0); | ||
| 5779 | try failDecl( | 5842 | try failDecl( |
| 5780 | c, | 5843 | c, |
| 5781 | source_loc, | 5844 | source_loc, |
| 5782 | source[first_tok.start..first_tok.end], | 5845 | it.slice(0), |
| 5783 | "unable to translate C expr: expected ')'' instead got: {}", | 5846 | "unable to translate C expr: expected ')'' instead got: {}", |
| 5784 | .{@tagName(next_id)}, | 5847 | .{@tagName(next_id)}, |
| 5785 | ); | 5848 | ); |
| ... | @@ -5787,7 +5850,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5787,7 +5850,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5787 | } | 5850 | } |
| 5788 | var saw_l_paren = false; | 5851 | var saw_l_paren = false; |
| 5789 | var saw_integer_literal = false; | 5852 | var saw_integer_literal = false; |
| 5790 | switch (it.peek().?.id) { | 5853 | switch (it.peek().?) { |
| 5791 | // (type)(to_cast) | 5854 | // (type)(to_cast) |
| 5792 | .LParen => { | 5855 | .LParen => { |
| 5793 | saw_l_paren = true; | 5856 | saw_l_paren = true; |
| ... | @@ -5805,14 +5868,13 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5805,14 +5868,13 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5805 | // hack to get zig fmt to render a comma in builtin calls | 5868 | // hack to get zig fmt to render a comma in builtin calls |
| 5806 | _ = try appendToken(c, .Comma, ","); | 5869 | _ = try appendToken(c, .Comma, ","); |
| 5807 | 5870 | ||
| 5808 | const node_to_cast = try parseCExpr(c, it, source, source_loc, scope); | 5871 | const node_to_cast = try parseCExpr(c, it, source_loc, scope); |
| 5809 | 5872 | ||
| 5810 | if (saw_l_paren and it.next().?.id != .RParen) { | 5873 | if (saw_l_paren and it.next().? != .RParen) { |
| 5811 | const first_tok = it.list.at(0); | ||
| 5812 | try failDecl( | 5874 | try failDecl( |
| 5813 | c, | 5875 | c, |
| 5814 | source_loc, | 5876 | source_loc, |
| 5815 | source[first_tok.start..first_tok.end], | 5877 | it.slice(0), |
| 5816 | "unable to translate C expr: expected ')''", | 5878 | "unable to translate C expr: expected ')''", |
| 5817 | .{}, | 5879 | .{}, |
| 5818 | ); | 5880 | ); |
| ... | @@ -5843,13 +5905,12 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -5843,13 +5905,12 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 5843 | return &group_node.base; | 5905 | return &group_node.base; |
| 5844 | }, | 5906 | }, |
| 5845 | else => { | 5907 | else => { |
| 5846 | const first_tok = it.list.at(0); | ||
| 5847 | try failDecl( | 5908 | try failDecl( |
| 5848 | c, | 5909 | c, |
| 5849 | source_loc, | 5910 | source_loc, |
| 5850 | source[first_tok.start..first_tok.end], | 5911 | it.slice(0), |
| 5851 | "unable to translate C expr: unexpected token .{}", | 5912 | "unable to translate C expr: unexpected token .{}", |
| 5852 | .{@tagName(tok.id)}, | 5913 | .{@tagName(tok)}, |
| 5853 | ); | 5914 | ); |
| 5854 | return error.ParseError; | 5915 | return error.ParseError; |
| 5855 | }, | 5916 | }, |
| ... | @@ -5957,61 +6018,52 @@ fn macroIntToBool(c: *Context, node: *ast.Node) !*ast.Node { | ... | @@ -5957,61 +6018,52 @@ fn macroIntToBool(c: *Context, node: *ast.Node) !*ast.Node { |
| 5957 | return &group_node.base; | 6018 | return &group_node.base; |
| 5958 | } | 6019 | } |
| 5959 | 6020 | ||
| 5960 | fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | 6021 | fn parseCSuffixOpExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { |
| 5961 | var node = try parseCPrimaryExpr(c, it, source, source_loc, scope); | 6022 | var node = try parseCPrimaryExpr(c, it, source_loc, scope); |
| 5962 | while (true) { | 6023 | while (true) { |
| 5963 | const tok = it.next().?; | ||
| 5964 | var op_token: ast.TokenIndex = undefined; | 6024 | var op_token: ast.TokenIndex = undefined; |
| 5965 | var op_id: ast.Node.Tag = undefined; | 6025 | var op_id: ast.Node.Tag = undefined; |
| 5966 | var bool_op = false; | 6026 | var bool_op = false; |
| 5967 | switch (tok.id) { | 6027 | switch (it.next().?) { |
| 5968 | .Period => { | 6028 | .Period => { |
| 5969 | const name_tok = it.next().?; | 6029 | if (it.next().? != .Identifier) { |
| 5970 | if (name_tok.id != .Identifier) { | ||
| 5971 | const first_tok = it.list.at(0); | ||
| 5972 | try failDecl( | 6030 | try failDecl( |
| 5973 | c, | 6031 | c, |
| 5974 | source_loc, | 6032 | source_loc, |
| 5975 | source[first_tok.start..first_tok.end], | 6033 | it.slice(0), |
| 5976 | "unable to translate C expr: expected identifier", | 6034 | "unable to translate C expr: expected identifier", |
| 5977 | .{}, | 6035 | .{}, |
| 5978 | ); | 6036 | ); |
| 5979 | return error.ParseError; | 6037 | return error.ParseError; |
| 5980 | } | 6038 | } |
| 5981 | 6039 | ||
| 5982 | node = try transCreateNodeFieldAccess(c, node, source[name_tok.start..name_tok.end]); | 6040 | node = try transCreateNodeFieldAccess(c, node, it.slice(it.i)); |
| 5983 | continue; | 6041 | continue; |
| 5984 | }, | 6042 | }, |
| 5985 | .Arrow => { | 6043 | .Arrow => { |
| 5986 | const name_tok = it.next().?; | 6044 | if (it.next().? != .Identifier) { |
| 5987 | if (name_tok.id != .Identifier) { | ||
| 5988 | const first_tok = it.list.at(0); | ||
| 5989 | try failDecl( | 6045 | try failDecl( |
| 5990 | c, | 6046 | c, |
| 5991 | source_loc, | 6047 | source_loc, |
| 5992 | source[first_tok.start..first_tok.end], | 6048 | it.slice(0), |
| 5993 | "unable to translate C expr: expected identifier", | 6049 | "unable to translate C expr: expected identifier", |
| 5994 | .{}, | 6050 | .{}, |
| 5995 | ); | 6051 | ); |
| 5996 | return error.ParseError; | 6052 | return error.ParseError; |
| 5997 | } | 6053 | } |
| 5998 | const deref = try transCreateNodePtrDeref(c, node); | 6054 | const deref = try transCreateNodePtrDeref(c, node); |
| 5999 | node = try transCreateNodeFieldAccess(c, deref, source[name_tok.start..name_tok.end]); | 6055 | node = try transCreateNodeFieldAccess(c, deref, it.slice(it.i)); |
| 6000 | continue; | 6056 | continue; |
| 6001 | }, | 6057 | }, |
| 6002 | .Asterisk => { | 6058 | .Asterisk => { |
| 6003 | if (it.peek().?.id == .RParen) { | 6059 | if (it.peek().? == .RParen) { |
| 6004 | // type *) | 6060 | // type *) |
| 6005 | 6061 | ||
| 6006 | // hack to get zig fmt to render a comma in builtin calls | 6062 | // hack to get zig fmt to render a comma in builtin calls |
| 6007 | _ = try appendToken(c, .Comma, ","); | 6063 | _ = try appendToken(c, .Comma, ","); |
| 6008 | 6064 | ||
| 6009 | // * token | ||
| 6010 | _ = it.prev(); | ||
| 6011 | // last token of `node` | 6065 | // last token of `node` |
| 6012 | const prev_id = it.prev().?.id; | 6066 | const prev_id = it.list[it.i - 1].id; |
| 6013 | _ = it.next(); | ||
| 6014 | _ = it.next(); | ||
| 6015 | 6067 | ||
| 6016 | if (prev_id == .Keyword_void) { | 6068 | if (prev_id == .Keyword_void) { |
| 6017 | const ptr = try transCreateNodePtrType(c, false, false, .Asterisk); | 6069 | const ptr = try transCreateNodePtrType(c, false, false, .Asterisk); |
| ... | @@ -6082,15 +6134,14 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -6082,15 +6134,14 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 6082 | }, | 6134 | }, |
| 6083 | .LBracket => { | 6135 | .LBracket => { |
| 6084 | const arr_node = try transCreateNodeArrayAccess(c, node); | 6136 | const arr_node = try transCreateNodeArrayAccess(c, node); |
| 6085 | arr_node.index_expr = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 6137 | arr_node.index_expr = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 6086 | arr_node.rtoken = try appendToken(c, .RBracket, "]"); | 6138 | arr_node.rtoken = try appendToken(c, .RBracket, "]"); |
| 6087 | node = &arr_node.base; | 6139 | node = &arr_node.base; |
| 6088 | if (it.next().?.id != .RBracket) { | 6140 | if (it.next().? != .RBracket) { |
| 6089 | const first_tok = it.list.at(0); | ||
| 6090 | try failDecl( | 6141 | try failDecl( |
| 6091 | c, | 6142 | c, |
| 6092 | source_loc, | 6143 | source_loc, |
| 6093 | source[first_tok.start..first_tok.end], | 6144 | it.slice(0), |
| 6094 | "unable to translate C expr: expected ']'", | 6145 | "unable to translate C expr: expected ']'", |
| 6095 | .{}, | 6146 | .{}, |
| 6096 | ); | 6147 | ); |
| ... | @@ -6103,23 +6154,21 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -6103,23 +6154,21 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 6103 | var call_params = std.ArrayList(*ast.Node).init(c.gpa); | 6154 | var call_params = std.ArrayList(*ast.Node).init(c.gpa); |
| 6104 | defer call_params.deinit(); | 6155 | defer call_params.deinit(); |
| 6105 | while (true) { | 6156 | while (true) { |
| 6106 | const arg = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 6157 | const arg = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 6107 | try call_params.append(arg); | 6158 | try call_params.append(arg); |
| 6108 | const next = it.next().?; | 6159 | switch (it.next().?) { |
| 6109 | if (next.id == .Comma) | 6160 | .Comma => _ = try appendToken(c, .Comma, ","), |
| 6110 | _ = try appendToken(c, .Comma, ",") | 6161 | .RParen => break, |
| 6111 | else if (next.id == .RParen) | 6162 | else => { |
| 6112 | break | 6163 | try failDecl( |
| 6113 | else { | 6164 | c, |
| 6114 | const first_tok = it.list.at(0); | 6165 | source_loc, |
| 6115 | try failDecl( | 6166 | it.slice(0), |
| 6116 | c, | 6167 | "unable to translate C expr: expected ',' or ')'", |
| 6117 | source_loc, | 6168 | .{}, |
| 6118 | source[first_tok.start..first_tok.end], | 6169 | ); |
| 6119 | "unable to translate C expr: expected ',' or ')'", | 6170 | return error.ParseError; |
| 6120 | .{}, | 6171 | }, |
| 6121 | ); | ||
| 6122 | return error.ParseError; | ||
| 6123 | } | 6172 | } |
| 6124 | } | 6173 | } |
| 6125 | const call_node = try ast.Node.Call.alloc(c.arena, call_params.items.len); | 6174 | 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, | ... | @@ -6144,23 +6193,21 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 6144 | defer init_vals.deinit(); | 6193 | defer init_vals.deinit(); |
| 6145 | 6194 | ||
| 6146 | while (true) { | 6195 | while (true) { |
| 6147 | const val = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 6196 | const val = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 6148 | try init_vals.append(val); | 6197 | try init_vals.append(val); |
| 6149 | const next = it.next().?; | 6198 | switch (it.next().?) { |
| 6150 | if (next.id == .Comma) | 6199 | .Comma => _ = try appendToken(c, .Comma, ","), |
| 6151 | _ = try appendToken(c, .Comma, ",") | 6200 | .RBrace => break, |
| 6152 | else if (next.id == .RBrace) | 6201 | else => { |
| 6153 | break | 6202 | try failDecl( |
| 6154 | else { | 6203 | c, |
| 6155 | const first_tok = it.list.at(0); | 6204 | source_loc, |
| 6156 | try failDecl( | 6205 | it.slice(0), |
| 6157 | c, | 6206 | "unable to translate C expr: expected ',' or '}}'", |
| 6158 | source_loc, | 6207 | .{}, |
| 6159 | source[first_tok.start..first_tok.end], | 6208 | ); |
| 6160 | "unable to translate C expr: expected ',' or '}}'", | 6209 | return error.ParseError; |
| 6161 | .{}, | 6210 | }, |
| 6162 | ); | ||
| 6163 | return error.ParseError; | ||
| 6164 | } | 6211 | } |
| 6165 | } | 6212 | } |
| 6166 | const tuple_node = try ast.Node.StructInitializerDot.alloc(c.arena, init_vals.items.len); | 6213 | 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, | ... | @@ -6207,22 +6254,22 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 6207 | op_id = .ArrayCat; | 6254 | op_id = .ArrayCat; |
| 6208 | op_token = try appendToken(c, .PlusPlus, "++"); | 6255 | op_token = try appendToken(c, .PlusPlus, "++"); |
| 6209 | 6256 | ||
| 6210 | _ = it.prev(); | 6257 | it.i -= 1; |
| 6211 | }, | 6258 | }, |
| 6212 | .Identifier => { | 6259 | .Identifier => { |
| 6213 | op_id = .ArrayCat; | 6260 | op_id = .ArrayCat; |
| 6214 | op_token = try appendToken(c, .PlusPlus, "++"); | 6261 | op_token = try appendToken(c, .PlusPlus, "++"); |
| 6215 | 6262 | ||
| 6216 | _ = it.prev(); | 6263 | it.i -= 1; |
| 6217 | }, | 6264 | }, |
| 6218 | else => { | 6265 | else => { |
| 6219 | _ = it.prev(); | 6266 | it.i -= 1; |
| 6220 | return node; | 6267 | return node; |
| 6221 | }, | 6268 | }, |
| 6222 | } | 6269 | } |
| 6223 | const cast_fn = if (bool_op) macroIntToBool else macroBoolToInt; | 6270 | const cast_fn = if (bool_op) macroIntToBool else macroBoolToInt; |
| 6224 | const lhs_node = try cast_fn(c, node); | 6271 | const lhs_node = try cast_fn(c, node); |
| 6225 | const rhs_node = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 6272 | const rhs_node = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 6226 | const op_node = try c.arena.create(ast.Node.SimpleInfixOp); | 6273 | const op_node = try c.arena.create(ast.Node.SimpleInfixOp); |
| 6227 | op_node.* = .{ | 6274 | op_node.* = .{ |
| 6228 | .base = .{ .tag = op_id }, | 6275 | .base = .{ .tag = op_id }, |
| ... | @@ -6234,38 +6281,36 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, | ... | @@ -6234,38 +6281,36 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, |
| 6234 | } | 6281 | } |
| 6235 | } | 6282 | } |
| 6236 | 6283 | ||
| 6237 | fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | 6284 | fn parseCPrefixOpExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { |
| 6238 | const op_tok = it.next().?; | 6285 | switch (it.next().?) { |
| 6239 | |||
| 6240 | switch (op_tok.id) { | ||
| 6241 | .Bang => { | 6286 | .Bang => { |
| 6242 | const node = try transCreateNodeSimplePrefixOp(c, .BoolNot, .Bang, "!"); | 6287 | const node = try transCreateNodeSimplePrefixOp(c, .BoolNot, .Bang, "!"); |
| 6243 | node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 6288 | node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 6244 | return &node.base; | 6289 | return &node.base; |
| 6245 | }, | 6290 | }, |
| 6246 | .Minus => { | 6291 | .Minus => { |
| 6247 | const node = try transCreateNodeSimplePrefixOp(c, .Negation, .Minus, "-"); | 6292 | const node = try transCreateNodeSimplePrefixOp(c, .Negation, .Minus, "-"); |
| 6248 | node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 6293 | node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 6249 | return &node.base; | 6294 | return &node.base; |
| 6250 | }, | 6295 | }, |
| 6251 | .Plus => return try parseCPrefixOpExpr(c, it, source, source_loc, scope), | 6296 | .Plus => return try parseCPrefixOpExpr(c, it, source_loc, scope), |
| 6252 | .Tilde => { | 6297 | .Tilde => { |
| 6253 | const node = try transCreateNodeSimplePrefixOp(c, .BitNot, .Tilde, "~"); | 6298 | const node = try transCreateNodeSimplePrefixOp(c, .BitNot, .Tilde, "~"); |
| 6254 | node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 6299 | node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 6255 | return &node.base; | 6300 | return &node.base; |
| 6256 | }, | 6301 | }, |
| 6257 | .Asterisk => { | 6302 | .Asterisk => { |
| 6258 | const node = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 6303 | const node = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 6259 | return try transCreateNodePtrDeref(c, node); | 6304 | return try transCreateNodePtrDeref(c, node); |
| 6260 | }, | 6305 | }, |
| 6261 | .Ampersand => { | 6306 | .Ampersand => { |
| 6262 | const node = try transCreateNodeSimplePrefixOp(c, .AddressOf, .Ampersand, "&"); | 6307 | const node = try transCreateNodeSimplePrefixOp(c, .AddressOf, .Ampersand, "&"); |
| 6263 | node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | 6308 | node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope); |
| 6264 | return &node.base; | 6309 | return &node.base; |
| 6265 | }, | 6310 | }, |
| 6266 | else => { | 6311 | else => { |
| 6267 | _ = it.prev(); | 6312 | it.i -= 1; |
| 6268 | return try parseCSuffixOpExpr(c, it, source, source_loc, scope); | 6313 | return try parseCSuffixOpExpr(c, it, source_loc, scope); |
| 6269 | }, | 6314 | }, |
| 6270 | } | 6315 | } |
| 6271 | } | 6316 | } |
src-self-hosted/type.zig+132-16| ... | @@ -67,6 +67,7 @@ pub const Type = extern union { | ... | @@ -67,6 +67,7 @@ pub const Type = extern union { |
| 67 | 67 | ||
| 68 | .array, .array_u8_sentinel_0 => return .Array, | 68 | .array, .array_u8_sentinel_0 => return .Array, |
| 69 | .single_const_pointer => return .Pointer, | 69 | .single_const_pointer => return .Pointer, |
| 70 | .single_mut_pointer => return .Pointer, | ||
| 70 | .single_const_pointer_to_comptime_int => return .Pointer, | 71 | .single_const_pointer_to_comptime_int => return .Pointer, |
| 71 | .const_slice_u8 => return .Pointer, | 72 | .const_slice_u8 => return .Pointer, |
| 72 | } | 73 | } |
| ... | @@ -261,6 +262,15 @@ pub const Type = extern union { | ... | @@ -261,6 +262,15 @@ pub const Type = extern union { |
| 261 | }; | 262 | }; |
| 262 | return Type{ .ptr_otherwise = &new_payload.base }; | 263 | return Type{ .ptr_otherwise = &new_payload.base }; |
| 263 | }, | 264 | }, |
| 265 | .single_mut_pointer => { | ||
| 266 | const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", self.ptr_otherwise); | ||
| 267 | const new_payload = try allocator.create(Payload.SingleMutPointer); | ||
| 268 | new_payload.* = .{ | ||
| 269 | .base = payload.base, | ||
| 270 | .pointee_type = try payload.pointee_type.copy(allocator), | ||
| 271 | }; | ||
| 272 | return Type{ .ptr_otherwise = &new_payload.base }; | ||
| 273 | }, | ||
| 264 | .int_signed => return self.copyPayloadShallow(allocator, Payload.IntSigned), | 274 | .int_signed => return self.copyPayloadShallow(allocator, Payload.IntSigned), |
| 265 | .int_unsigned => return self.copyPayloadShallow(allocator, Payload.IntUnsigned), | 275 | .int_unsigned => return self.copyPayloadShallow(allocator, Payload.IntUnsigned), |
| 266 | .function => { | 276 | .function => { |
| ... | @@ -368,6 +378,12 @@ pub const Type = extern union { | ... | @@ -368,6 +378,12 @@ pub const Type = extern union { |
| 368 | ty = payload.pointee_type; | 378 | ty = payload.pointee_type; |
| 369 | continue; | 379 | continue; |
| 370 | }, | 380 | }, |
| 381 | .single_mut_pointer => { | ||
| 382 | const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", ty.ptr_otherwise); | ||
| 383 | try out_stream.writeAll("*"); | ||
| 384 | ty = payload.pointee_type; | ||
| 385 | continue; | ||
| 386 | }, | ||
| 371 | .int_signed => { | 387 | .int_signed => { |
| 372 | const payload = @fieldParentPtr(Payload.IntSigned, "base", ty.ptr_otherwise); | 388 | const payload = @fieldParentPtr(Payload.IntSigned, "base", ty.ptr_otherwise); |
| 373 | return out_stream.print("i{}", .{payload.bits}); | 389 | return out_stream.print("i{}", .{payload.bits}); |
| ... | @@ -467,6 +483,7 @@ pub const Type = extern union { | ... | @@ -467,6 +483,7 @@ pub const Type = extern union { |
| 467 | .array_u8_sentinel_0, | 483 | .array_u8_sentinel_0, |
| 468 | .array, // TODO check for zero bits | 484 | .array, // TODO check for zero bits |
| 469 | .single_const_pointer, | 485 | .single_const_pointer, |
| 486 | .single_mut_pointer, | ||
| 470 | .int_signed, // TODO check for zero bits | 487 | .int_signed, // TODO check for zero bits |
| 471 | .int_unsigned, // TODO check for zero bits | 488 | .int_unsigned, // TODO check for zero bits |
| 472 | => true, | 489 | => true, |
| ... | @@ -493,13 +510,18 @@ pub const Type = extern union { | ... | @@ -493,13 +510,18 @@ pub const Type = extern union { |
| 493 | .u8, | 510 | .u8, |
| 494 | .i8, | 511 | .i8, |
| 495 | .bool, | 512 | .bool, |
| 513 | .array_u8_sentinel_0, | ||
| 514 | => return 1, | ||
| 515 | |||
| 496 | .fn_noreturn_no_args, // represents machine code; not a pointer | 516 | .fn_noreturn_no_args, // represents machine code; not a pointer |
| 497 | .fn_void_no_args, // represents machine code; not a pointer | 517 | .fn_void_no_args, // represents machine code; not a pointer |
| 498 | .fn_naked_noreturn_no_args, // represents machine code; not a pointer | 518 | .fn_naked_noreturn_no_args, // represents machine code; not a pointer |
| 499 | .fn_ccc_void_no_args, // represents machine code; not a pointer | 519 | .fn_ccc_void_no_args, // represents machine code; not a pointer |
| 500 | .function, // represents machine code; not a pointer | 520 | .function, // represents machine code; not a pointer |
| 501 | .array_u8_sentinel_0, | 521 | => return switch (target.cpu.arch) { |
| 502 | => return 1, | 522 | .riscv64 => 2, |
| 523 | else => 1, | ||
| 524 | }, | ||
| 503 | 525 | ||
| 504 | .i16, .u16 => return 2, | 526 | .i16, .u16 => return 2, |
| 505 | .i32, .u32 => return 4, | 527 | .i32, .u32 => return 4, |
| ... | @@ -510,6 +532,7 @@ pub const Type = extern union { | ... | @@ -510,6 +532,7 @@ pub const Type = extern union { |
| 510 | .single_const_pointer_to_comptime_int, | 532 | .single_const_pointer_to_comptime_int, |
| 511 | .const_slice_u8, | 533 | .const_slice_u8, |
| 512 | .single_const_pointer, | 534 | .single_const_pointer, |
| 535 | .single_mut_pointer, | ||
| 513 | => return @divExact(target.cpu.arch.ptrBitWidth(), 8), | 536 | => return @divExact(target.cpu.arch.ptrBitWidth(), 8), |
| 514 | 537 | ||
| 515 | .c_short => return @divExact(CType.short.sizeInBits(target), 8), | 538 | .c_short => return @divExact(CType.short.sizeInBits(target), 8), |
| ... | @@ -591,6 +614,7 @@ pub const Type = extern union { | ... | @@ -591,6 +614,7 @@ pub const Type = extern union { |
| 591 | .single_const_pointer_to_comptime_int, | 614 | .single_const_pointer_to_comptime_int, |
| 592 | .const_slice_u8, | 615 | .const_slice_u8, |
| 593 | .single_const_pointer, | 616 | .single_const_pointer, |
| 617 | .single_mut_pointer, | ||
| 594 | => return @divExact(target.cpu.arch.ptrBitWidth(), 8), | 618 | => return @divExact(target.cpu.arch.ptrBitWidth(), 8), |
| 595 | 619 | ||
| 596 | .c_short => return @divExact(CType.short.sizeInBits(target), 8), | 620 | .c_short => return @divExact(CType.short.sizeInBits(target), 8), |
| ... | @@ -671,6 +695,7 @@ pub const Type = extern union { | ... | @@ -671,6 +695,7 @@ pub const Type = extern union { |
| 671 | => false, | 695 | => false, |
| 672 | 696 | ||
| 673 | .single_const_pointer, | 697 | .single_const_pointer, |
| 698 | .single_mut_pointer, | ||
| 674 | .single_const_pointer_to_comptime_int, | 699 | .single_const_pointer_to_comptime_int, |
| 675 | => true, | 700 | => true, |
| 676 | }; | 701 | }; |
| ... | @@ -714,6 +739,7 @@ pub const Type = extern union { | ... | @@ -714,6 +739,7 @@ pub const Type = extern union { |
| 714 | .array, | 739 | .array, |
| 715 | .array_u8_sentinel_0, | 740 | .array_u8_sentinel_0, |
| 716 | .single_const_pointer, | 741 | .single_const_pointer, |
| 742 | .single_mut_pointer, | ||
| 717 | .single_const_pointer_to_comptime_int, | 743 | .single_const_pointer_to_comptime_int, |
| 718 | .fn_noreturn_no_args, | 744 | .fn_noreturn_no_args, |
| 719 | .fn_void_no_args, | 745 | .fn_void_no_args, |
| ... | @@ -728,8 +754,7 @@ pub const Type = extern union { | ... | @@ -728,8 +754,7 @@ pub const Type = extern union { |
| 728 | }; | 754 | }; |
| 729 | } | 755 | } |
| 730 | 756 | ||
| 731 | /// Asserts the type is a pointer type. | 757 | pub fn isConstPtr(self: Type) bool { |
| 732 | pub fn pointerIsConst(self: Type) bool { | ||
| 733 | return switch (self.tag()) { | 758 | return switch (self.tag()) { |
| 734 | .u8, | 759 | .u8, |
| 735 | .i8, | 760 | .i8, |
| ... | @@ -773,7 +798,8 @@ pub const Type = extern union { | ... | @@ -773,7 +798,8 @@ pub const Type = extern union { |
| 773 | .function, | 798 | .function, |
| 774 | .int_unsigned, | 799 | .int_unsigned, |
| 775 | .int_signed, | 800 | .int_signed, |
| 776 | => unreachable, | 801 | .single_mut_pointer, |
| 802 | => false, | ||
| 777 | 803 | ||
| 778 | .single_const_pointer, | 804 | .single_const_pointer, |
| 779 | .single_const_pointer_to_comptime_int, | 805 | .single_const_pointer_to_comptime_int, |
| ... | @@ -782,6 +808,58 @@ pub const Type = extern union { | ... | @@ -782,6 +808,58 @@ pub const Type = extern union { |
| 782 | }; | 808 | }; |
| 783 | } | 809 | } |
| 784 | 810 | ||
| 811 | pub fn isVolatilePtr(self: Type) bool { | ||
| 812 | return switch (self.tag()) { | ||
| 813 | .u8, | ||
| 814 | .i8, | ||
| 815 | .u16, | ||
| 816 | .i16, | ||
| 817 | .u32, | ||
| 818 | .i32, | ||
| 819 | .u64, | ||
| 820 | .i64, | ||
| 821 | .usize, | ||
| 822 | .isize, | ||
| 823 | .c_short, | ||
| 824 | .c_ushort, | ||
| 825 | .c_int, | ||
| 826 | .c_uint, | ||
| 827 | .c_long, | ||
| 828 | .c_ulong, | ||
| 829 | .c_longlong, | ||
| 830 | .c_ulonglong, | ||
| 831 | .c_longdouble, | ||
| 832 | .f16, | ||
| 833 | .f32, | ||
| 834 | .f64, | ||
| 835 | .f128, | ||
| 836 | .c_void, | ||
| 837 | .bool, | ||
| 838 | .void, | ||
| 839 | .type, | ||
| 840 | .anyerror, | ||
| 841 | .comptime_int, | ||
| 842 | .comptime_float, | ||
| 843 | .noreturn, | ||
| 844 | .@"null", | ||
| 845 | .@"undefined", | ||
| 846 | .array, | ||
| 847 | .array_u8_sentinel_0, | ||
| 848 | .fn_noreturn_no_args, | ||
| 849 | .fn_void_no_args, | ||
| 850 | .fn_naked_noreturn_no_args, | ||
| 851 | .fn_ccc_void_no_args, | ||
| 852 | .function, | ||
| 853 | .int_unsigned, | ||
| 854 | .int_signed, | ||
| 855 | .single_mut_pointer, | ||
| 856 | .single_const_pointer, | ||
| 857 | .single_const_pointer_to_comptime_int, | ||
| 858 | .const_slice_u8, | ||
| 859 | => false, | ||
| 860 | }; | ||
| 861 | } | ||
| 862 | |||
| 785 | /// Asserts the type is a pointer or array type. | 863 | /// Asserts the type is a pointer or array type. |
| 786 | pub fn elemType(self: Type) Type { | 864 | pub fn elemType(self: Type) Type { |
| 787 | return switch (self.tag()) { | 865 | return switch (self.tag()) { |
| ... | @@ -829,6 +907,7 @@ pub const Type = extern union { | ... | @@ -829,6 +907,7 @@ pub const Type = extern union { |
| 829 | 907 | ||
| 830 | .array => self.cast(Payload.Array).?.elem_type, | 908 | .array => self.cast(Payload.Array).?.elem_type, |
| 831 | .single_const_pointer => self.cast(Payload.SingleConstPointer).?.pointee_type, | 909 | .single_const_pointer => self.cast(Payload.SingleConstPointer).?.pointee_type, |
| 910 | .single_mut_pointer => self.cast(Payload.SingleMutPointer).?.pointee_type, | ||
| 832 | .array_u8_sentinel_0, .const_slice_u8 => Type.initTag(.u8), | 911 | .array_u8_sentinel_0, .const_slice_u8 => Type.initTag(.u8), |
| 833 | .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int), | 912 | .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int), |
| 834 | }; | 913 | }; |
| ... | @@ -876,6 +955,7 @@ pub const Type = extern union { | ... | @@ -876,6 +955,7 @@ pub const Type = extern union { |
| 876 | .fn_ccc_void_no_args, | 955 | .fn_ccc_void_no_args, |
| 877 | .function, | 956 | .function, |
| 878 | .single_const_pointer, | 957 | .single_const_pointer, |
| 958 | .single_mut_pointer, | ||
| 879 | .single_const_pointer_to_comptime_int, | 959 | .single_const_pointer_to_comptime_int, |
| 880 | .const_slice_u8, | 960 | .const_slice_u8, |
| 881 | .int_unsigned, | 961 | .int_unsigned, |
| ... | @@ -929,6 +1009,7 @@ pub const Type = extern union { | ... | @@ -929,6 +1009,7 @@ pub const Type = extern union { |
| 929 | .fn_ccc_void_no_args, | 1009 | .fn_ccc_void_no_args, |
| 930 | .function, | 1010 | .function, |
| 931 | .single_const_pointer, | 1011 | .single_const_pointer, |
| 1012 | .single_mut_pointer, | ||
| 932 | .single_const_pointer_to_comptime_int, | 1013 | .single_const_pointer_to_comptime_int, |
| 933 | .const_slice_u8, | 1014 | .const_slice_u8, |
| 934 | .int_unsigned, | 1015 | .int_unsigned, |
| ... | @@ -970,6 +1051,7 @@ pub const Type = extern union { | ... | @@ -970,6 +1051,7 @@ pub const Type = extern union { |
| 970 | .function, | 1051 | .function, |
| 971 | .array, | 1052 | .array, |
| 972 | .single_const_pointer, | 1053 | .single_const_pointer, |
| 1054 | .single_mut_pointer, | ||
| 973 | .single_const_pointer_to_comptime_int, | 1055 | .single_const_pointer_to_comptime_int, |
| 974 | .array_u8_sentinel_0, | 1056 | .array_u8_sentinel_0, |
| 975 | .const_slice_u8, | 1057 | .const_slice_u8, |
| ... | @@ -1024,6 +1106,7 @@ pub const Type = extern union { | ... | @@ -1024,6 +1106,7 @@ pub const Type = extern union { |
| 1024 | .function, | 1106 | .function, |
| 1025 | .array, | 1107 | .array, |
| 1026 | .single_const_pointer, | 1108 | .single_const_pointer, |
| 1109 | .single_mut_pointer, | ||
| 1027 | .single_const_pointer_to_comptime_int, | 1110 | .single_const_pointer_to_comptime_int, |
| 1028 | .array_u8_sentinel_0, | 1111 | .array_u8_sentinel_0, |
| 1029 | .const_slice_u8, | 1112 | .const_slice_u8, |
| ... | @@ -1078,6 +1161,7 @@ pub const Type = extern union { | ... | @@ -1078,6 +1161,7 @@ pub const Type = extern union { |
| 1078 | .function, | 1161 | .function, |
| 1079 | .array, | 1162 | .array, |
| 1080 | .single_const_pointer, | 1163 | .single_const_pointer, |
| 1164 | .single_mut_pointer, | ||
| 1081 | .single_const_pointer_to_comptime_int, | 1165 | .single_const_pointer_to_comptime_int, |
| 1082 | .array_u8_sentinel_0, | 1166 | .array_u8_sentinel_0, |
| 1083 | .const_slice_u8, | 1167 | .const_slice_u8, |
| ... | @@ -1130,6 +1214,7 @@ pub const Type = extern union { | ... | @@ -1130,6 +1214,7 @@ pub const Type = extern union { |
| 1130 | .function, | 1214 | .function, |
| 1131 | .array, | 1215 | .array, |
| 1132 | .single_const_pointer, | 1216 | .single_const_pointer, |
| 1217 | .single_mut_pointer, | ||
| 1133 | .single_const_pointer_to_comptime_int, | 1218 | .single_const_pointer_to_comptime_int, |
| 1134 | .array_u8_sentinel_0, | 1219 | .array_u8_sentinel_0, |
| 1135 | .const_slice_u8, | 1220 | .const_slice_u8, |
| ... | @@ -1211,6 +1296,7 @@ pub const Type = extern union { | ... | @@ -1211,6 +1296,7 @@ pub const Type = extern union { |
| 1211 | .@"undefined", | 1296 | .@"undefined", |
| 1212 | .array, | 1297 | .array, |
| 1213 | .single_const_pointer, | 1298 | .single_const_pointer, |
| 1299 | .single_mut_pointer, | ||
| 1214 | .single_const_pointer_to_comptime_int, | 1300 | .single_const_pointer_to_comptime_int, |
| 1215 | .array_u8_sentinel_0, | 1301 | .array_u8_sentinel_0, |
| 1216 | .const_slice_u8, | 1302 | .const_slice_u8, |
| ... | @@ -1268,6 +1354,7 @@ pub const Type = extern union { | ... | @@ -1268,6 +1354,7 @@ pub const Type = extern union { |
| 1268 | .@"undefined", | 1354 | .@"undefined", |
| 1269 | .array, | 1355 | .array, |
| 1270 | .single_const_pointer, | 1356 | .single_const_pointer, |
| 1357 | .single_mut_pointer, | ||
| 1271 | .single_const_pointer_to_comptime_int, | 1358 | .single_const_pointer_to_comptime_int, |
| 1272 | .array_u8_sentinel_0, | 1359 | .array_u8_sentinel_0, |
| 1273 | .const_slice_u8, | 1360 | .const_slice_u8, |
| ... | @@ -1324,6 +1411,7 @@ pub const Type = extern union { | ... | @@ -1324,6 +1411,7 @@ pub const Type = extern union { |
| 1324 | .@"undefined", | 1411 | .@"undefined", |
| 1325 | .array, | 1412 | .array, |
| 1326 | .single_const_pointer, | 1413 | .single_const_pointer, |
| 1414 | .single_mut_pointer, | ||
| 1327 | .single_const_pointer_to_comptime_int, | 1415 | .single_const_pointer_to_comptime_int, |
| 1328 | .array_u8_sentinel_0, | 1416 | .array_u8_sentinel_0, |
| 1329 | .const_slice_u8, | 1417 | .const_slice_u8, |
| ... | @@ -1380,6 +1468,7 @@ pub const Type = extern union { | ... | @@ -1380,6 +1468,7 @@ pub const Type = extern union { |
| 1380 | .@"undefined", | 1468 | .@"undefined", |
| 1381 | .array, | 1469 | .array, |
| 1382 | .single_const_pointer, | 1470 | .single_const_pointer, |
| 1471 | .single_mut_pointer, | ||
| 1383 | .single_const_pointer_to_comptime_int, | 1472 | .single_const_pointer_to_comptime_int, |
| 1384 | .array_u8_sentinel_0, | 1473 | .array_u8_sentinel_0, |
| 1385 | .const_slice_u8, | 1474 | .const_slice_u8, |
| ... | @@ -1433,6 +1522,7 @@ pub const Type = extern union { | ... | @@ -1433,6 +1522,7 @@ pub const Type = extern union { |
| 1433 | .@"undefined", | 1522 | .@"undefined", |
| 1434 | .array, | 1523 | .array, |
| 1435 | .single_const_pointer, | 1524 | .single_const_pointer, |
| 1525 | .single_mut_pointer, | ||
| 1436 | .single_const_pointer_to_comptime_int, | 1526 | .single_const_pointer_to_comptime_int, |
| 1437 | .array_u8_sentinel_0, | 1527 | .array_u8_sentinel_0, |
| 1438 | .const_slice_u8, | 1528 | .const_slice_u8, |
| ... | @@ -1486,6 +1576,7 @@ pub const Type = extern union { | ... | @@ -1486,6 +1576,7 @@ pub const Type = extern union { |
| 1486 | .@"undefined", | 1576 | .@"undefined", |
| 1487 | .array, | 1577 | .array, |
| 1488 | .single_const_pointer, | 1578 | .single_const_pointer, |
| 1579 | .single_mut_pointer, | ||
| 1489 | .single_const_pointer_to_comptime_int, | 1580 | .single_const_pointer_to_comptime_int, |
| 1490 | .array_u8_sentinel_0, | 1581 | .array_u8_sentinel_0, |
| 1491 | .const_slice_u8, | 1582 | .const_slice_u8, |
| ... | @@ -1559,6 +1650,7 @@ pub const Type = extern union { | ... | @@ -1559,6 +1650,7 @@ pub const Type = extern union { |
| 1559 | .function, | 1650 | .function, |
| 1560 | .array, | 1651 | .array, |
| 1561 | .single_const_pointer, | 1652 | .single_const_pointer, |
| 1653 | .single_mut_pointer, | ||
| 1562 | .single_const_pointer_to_comptime_int, | 1654 | .single_const_pointer_to_comptime_int, |
| 1563 | .array_u8_sentinel_0, | 1655 | .array_u8_sentinel_0, |
| 1564 | .const_slice_u8, | 1656 | .const_slice_u8, |
| ... | @@ -1566,7 +1658,7 @@ pub const Type = extern union { | ... | @@ -1566,7 +1658,7 @@ pub const Type = extern union { |
| 1566 | }; | 1658 | }; |
| 1567 | } | 1659 | } |
| 1568 | 1660 | ||
| 1569 | pub fn onePossibleValue(self: Type) bool { | 1661 | pub fn onePossibleValue(self: Type) ?Value { |
| 1570 | var ty = self; | 1662 | var ty = self; |
| 1571 | while (true) switch (ty.tag()) { | 1663 | while (true) switch (ty.tag()) { |
| 1572 | .f16, | 1664 | .f16, |
| ... | @@ -1605,21 +1697,32 @@ pub const Type = extern union { | ... | @@ -1605,21 +1697,32 @@ pub const Type = extern union { |
| 1605 | .single_const_pointer_to_comptime_int, | 1697 | .single_const_pointer_to_comptime_int, |
| 1606 | .array_u8_sentinel_0, | 1698 | .array_u8_sentinel_0, |
| 1607 | .const_slice_u8, | 1699 | .const_slice_u8, |
| 1608 | => return false, | ||
| 1609 | |||
| 1610 | .c_void, | 1700 | .c_void, |
| 1611 | .void, | 1701 | => return null, |
| 1612 | .noreturn, | 1702 | |
| 1613 | .@"null", | 1703 | .void => return Value.initTag(.void_value), |
| 1614 | .@"undefined", | 1704 | .noreturn => return Value.initTag(.unreachable_value), |
| 1615 | => return true, | 1705 | .@"null" => return Value.initTag(.null_value), |
| 1706 | .@"undefined" => return Value.initTag(.undef), | ||
| 1616 | 1707 | ||
| 1617 | .int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0, | 1708 | .int_unsigned => { |
| 1618 | .int_signed => return ty.cast(Payload.IntSigned).?.bits == 0, | 1709 | if (ty.cast(Payload.IntUnsigned).?.bits == 0) { |
| 1710 | return Value.initTag(.zero); | ||
| 1711 | } else { | ||
| 1712 | return null; | ||
| 1713 | } | ||
| 1714 | }, | ||
| 1715 | .int_signed => { | ||
| 1716 | if (ty.cast(Payload.IntSigned).?.bits == 0) { | ||
| 1717 | return Value.initTag(.zero); | ||
| 1718 | } else { | ||
| 1719 | return null; | ||
| 1720 | } | ||
| 1721 | }, | ||
| 1619 | .array => { | 1722 | .array => { |
| 1620 | const array = ty.cast(Payload.Array).?; | 1723 | const array = ty.cast(Payload.Array).?; |
| 1621 | if (array.len == 0) | 1724 | if (array.len == 0) |
| 1622 | return true; | 1725 | return Value.initTag(.empty_array); |
| 1623 | ty = array.elem_type; | 1726 | ty = array.elem_type; |
| 1624 | continue; | 1727 | continue; |
| 1625 | }, | 1728 | }, |
| ... | @@ -1628,6 +1731,11 @@ pub const Type = extern union { | ... | @@ -1628,6 +1731,11 @@ pub const Type = extern union { |
| 1628 | ty = ptr.pointee_type; | 1731 | ty = ptr.pointee_type; |
| 1629 | continue; | 1732 | continue; |
| 1630 | }, | 1733 | }, |
| 1734 | .single_mut_pointer => { | ||
| 1735 | const ptr = ty.cast(Payload.SingleMutPointer).?; | ||
| 1736 | ty = ptr.pointee_type; | ||
| 1737 | continue; | ||
| 1738 | }, | ||
| 1631 | }; | 1739 | }; |
| 1632 | } | 1740 | } |
| 1633 | 1741 | ||
| ... | @@ -1678,6 +1786,7 @@ pub const Type = extern union { | ... | @@ -1678,6 +1786,7 @@ pub const Type = extern union { |
| 1678 | .int_signed, | 1786 | .int_signed, |
| 1679 | .array, | 1787 | .array, |
| 1680 | .single_const_pointer, | 1788 | .single_const_pointer, |
| 1789 | .single_mut_pointer, | ||
| 1681 | => return false, | 1790 | => return false, |
| 1682 | }; | 1791 | }; |
| 1683 | } | 1792 | } |
| ... | @@ -1734,6 +1843,7 @@ pub const Type = extern union { | ... | @@ -1734,6 +1843,7 @@ pub const Type = extern union { |
| 1734 | array_u8_sentinel_0, | 1843 | array_u8_sentinel_0, |
| 1735 | array, | 1844 | array, |
| 1736 | single_const_pointer, | 1845 | single_const_pointer, |
| 1846 | single_mut_pointer, | ||
| 1737 | int_signed, | 1847 | int_signed, |
| 1738 | int_unsigned, | 1848 | int_unsigned, |
| 1739 | function, | 1849 | function, |
| ... | @@ -1764,6 +1874,12 @@ pub const Type = extern union { | ... | @@ -1764,6 +1874,12 @@ pub const Type = extern union { |
| 1764 | pointee_type: Type, | 1874 | pointee_type: Type, |
| 1765 | }; | 1875 | }; |
| 1766 | 1876 | ||
| 1877 | pub const SingleMutPointer = struct { | ||
| 1878 | base: Payload = Payload{ .tag = .single_mut_pointer }, | ||
| 1879 | |||
| 1880 | pointee_type: Type, | ||
| 1881 | }; | ||
| 1882 | |||
| 1767 | pub const IntSigned = struct { | 1883 | pub const IntSigned = struct { |
| 1768 | base: Payload = Payload{ .tag = .int_signed }, | 1884 | base: Payload = Payload{ .tag = .int_signed }, |
| 1769 | 1885 |
src-self-hosted/value.zig+51-24| ... | @@ -63,7 +63,9 @@ pub const Value = extern union { | ... | @@ -63,7 +63,9 @@ pub const Value = extern union { |
| 63 | 63 | ||
| 64 | undef, | 64 | undef, |
| 65 | zero, | 65 | zero, |
| 66 | the_one_possible_value, // when the type only has one possible value | 66 | void_value, |
| 67 | unreachable_value, | ||
| 68 | empty_array, | ||
| 67 | null_value, | 69 | null_value, |
| 68 | bool_true, | 70 | bool_true, |
| 69 | bool_false, // See last_no_payload_tag below. | 71 | bool_false, // See last_no_payload_tag below. |
| ... | @@ -164,7 +166,9 @@ pub const Value = extern union { | ... | @@ -164,7 +166,9 @@ pub const Value = extern union { |
| 164 | .const_slice_u8_type, | 166 | .const_slice_u8_type, |
| 165 | .undef, | 167 | .undef, |
| 166 | .zero, | 168 | .zero, |
| 167 | .the_one_possible_value, | 169 | .void_value, |
| 170 | .unreachable_value, | ||
| 171 | .empty_array, | ||
| 168 | .null_value, | 172 | .null_value, |
| 169 | .bool_true, | 173 | .bool_true, |
| 170 | .bool_false, | 174 | .bool_false, |
| ... | @@ -285,7 +289,8 @@ pub const Value = extern union { | ... | @@ -285,7 +289,8 @@ pub const Value = extern union { |
| 285 | .null_value => return out_stream.writeAll("null"), | 289 | .null_value => return out_stream.writeAll("null"), |
| 286 | .undef => return out_stream.writeAll("undefined"), | 290 | .undef => return out_stream.writeAll("undefined"), |
| 287 | .zero => return out_stream.writeAll("0"), | 291 | .zero => return out_stream.writeAll("0"), |
| 288 | .the_one_possible_value => return out_stream.writeAll("(one possible value)"), | 292 | .void_value => return out_stream.writeAll("{}"), |
| 293 | .unreachable_value => return out_stream.writeAll("unreachable"), | ||
| 289 | .bool_true => return out_stream.writeAll("true"), | 294 | .bool_true => return out_stream.writeAll("true"), |
| 290 | .bool_false => return out_stream.writeAll("false"), | 295 | .bool_false => return out_stream.writeAll("false"), |
| 291 | .ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream), | 296 | .ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream), |
| ... | @@ -312,6 +317,7 @@ pub const Value = extern union { | ... | @@ -312,6 +317,7 @@ pub const Value = extern union { |
| 312 | try out_stream.print("&[{}] ", .{elem_ptr.index}); | 317 | try out_stream.print("&[{}] ", .{elem_ptr.index}); |
| 313 | val = elem_ptr.array_ptr; | 318 | val = elem_ptr.array_ptr; |
| 314 | }, | 319 | }, |
| 320 | .empty_array => return out_stream.writeAll(".{}"), | ||
| 315 | .bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream), | 321 | .bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream), |
| 316 | .repeated => { | 322 | .repeated => { |
| 317 | try out_stream.writeAll("(repeated) "); | 323 | try out_stream.writeAll("(repeated) "); |
| ... | @@ -388,7 +394,9 @@ pub const Value = extern union { | ... | @@ -388,7 +394,9 @@ pub const Value = extern union { |
| 388 | 394 | ||
| 389 | .undef, | 395 | .undef, |
| 390 | .zero, | 396 | .zero, |
| 391 | .the_one_possible_value, | 397 | .void_value, |
| 398 | .unreachable_value, | ||
| 399 | .empty_array, | ||
| 392 | .bool_true, | 400 | .bool_true, |
| 393 | .bool_false, | 401 | .bool_false, |
| 394 | .null_value, | 402 | .null_value, |
| ... | @@ -460,15 +468,18 @@ pub const Value = extern union { | ... | @@ -460,15 +468,18 @@ pub const Value = extern union { |
| 460 | .decl_ref, | 468 | .decl_ref, |
| 461 | .elem_ptr, | 469 | .elem_ptr, |
| 462 | .bytes, | 470 | .bytes, |
| 463 | .undef, | ||
| 464 | .repeated, | 471 | .repeated, |
| 465 | .float_16, | 472 | .float_16, |
| 466 | .float_32, | 473 | .float_32, |
| 467 | .float_64, | 474 | .float_64, |
| 468 | .float_128, | 475 | .float_128, |
| 476 | .void_value, | ||
| 477 | .unreachable_value, | ||
| 478 | .empty_array, | ||
| 469 | => unreachable, | 479 | => unreachable, |
| 470 | 480 | ||
| 471 | .the_one_possible_value, // An integer with one possible value is always zero. | 481 | .undef => unreachable, |
| 482 | |||
| 472 | .zero, | 483 | .zero, |
| 473 | .bool_false, | 484 | .bool_false, |
| 474 | => return BigIntMutable.init(&space.limbs, 0).toConst(), | 485 | => return BigIntMutable.init(&space.limbs, 0).toConst(), |
| ... | @@ -532,16 +543,19 @@ pub const Value = extern union { | ... | @@ -532,16 +543,19 @@ pub const Value = extern union { |
| 532 | .decl_ref, | 543 | .decl_ref, |
| 533 | .elem_ptr, | 544 | .elem_ptr, |
| 534 | .bytes, | 545 | .bytes, |
| 535 | .undef, | ||
| 536 | .repeated, | 546 | .repeated, |
| 537 | .float_16, | 547 | .float_16, |
| 538 | .float_32, | 548 | .float_32, |
| 539 | .float_64, | 549 | .float_64, |
| 540 | .float_128, | 550 | .float_128, |
| 551 | .void_value, | ||
| 552 | .unreachable_value, | ||
| 553 | .empty_array, | ||
| 541 | => unreachable, | 554 | => unreachable, |
| 542 | 555 | ||
| 556 | .undef => unreachable, | ||
| 557 | |||
| 543 | .zero, | 558 | .zero, |
| 544 | .the_one_possible_value, // an integer with one possible value is always zero | ||
| 545 | .bool_false, | 559 | .bool_false, |
| 546 | => return 0, | 560 | => return 0, |
| 547 | 561 | ||
| ... | @@ -570,10 +584,9 @@ pub const Value = extern union { | ... | @@ -570,10 +584,9 @@ pub const Value = extern union { |
| 570 | .float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val), | 584 | .float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val), |
| 571 | .float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val), | 585 | .float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val), |
| 572 | 586 | ||
| 573 | .zero, .the_one_possible_value => 0, | 587 | .zero => 0, |
| 574 | .int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int), | 588 | .int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int), |
| 575 | // .int_i64 => @intToFloat(f128, self.cast(Payload.Int_i64).?.int), | 589 | .int_i64 => @intToFloat(T, self.cast(Payload.Int_i64).?.int), |
| 576 | .int_i64 => @panic("TODO lld: error: undefined symbol: __floatditf"), | ||
| 577 | 590 | ||
| 578 | .int_big_positive, .int_big_negative => @panic("big int to f128"), | 591 | .int_big_positive, .int_big_negative => @panic("big int to f128"), |
| 579 | else => unreachable, | 592 | else => unreachable, |
| ... | @@ -637,9 +650,11 @@ pub const Value = extern union { | ... | @@ -637,9 +650,11 @@ pub const Value = extern union { |
| 637 | .float_32, | 650 | .float_32, |
| 638 | .float_64, | 651 | .float_64, |
| 639 | .float_128, | 652 | .float_128, |
| 653 | .void_value, | ||
| 654 | .unreachable_value, | ||
| 655 | .empty_array, | ||
| 640 | => unreachable, | 656 | => unreachable, |
| 641 | 657 | ||
| 642 | .the_one_possible_value, // an integer with one possible value is always zero | ||
| 643 | .zero, | 658 | .zero, |
| 644 | .bool_false, | 659 | .bool_false, |
| 645 | => return 0, | 660 | => return 0, |
| ... | @@ -714,11 +729,13 @@ pub const Value = extern union { | ... | @@ -714,11 +729,13 @@ pub const Value = extern union { |
| 714 | .float_32, | 729 | .float_32, |
| 715 | .float_64, | 730 | .float_64, |
| 716 | .float_128, | 731 | .float_128, |
| 732 | .void_value, | ||
| 733 | .unreachable_value, | ||
| 734 | .empty_array, | ||
| 717 | => unreachable, | 735 | => unreachable, |
| 718 | 736 | ||
| 719 | .zero, | 737 | .zero, |
| 720 | .undef, | 738 | .undef, |
| 721 | .the_one_possible_value, // an integer with one possible value is always zero | ||
| 722 | .bool_false, | 739 | .bool_false, |
| 723 | => return true, | 740 | => return true, |
| 724 | 741 | ||
| ... | @@ -797,13 +814,13 @@ pub const Value = extern union { | ... | @@ -797,13 +814,13 @@ pub const Value = extern union { |
| 797 | // return Value.initPayload(&res_payload.base).copy(allocator); | 814 | // return Value.initPayload(&res_payload.base).copy(allocator); |
| 798 | }, | 815 | }, |
| 799 | 32 => { | 816 | 32 => { |
| 800 | var res_payload = Value.Payload.Float_32{.val = self.toFloat(f32)}; | 817 | var res_payload = Value.Payload.Float_32{ .val = self.toFloat(f32) }; |
| 801 | if (!self.eql(Value.initPayload(&res_payload.base))) | 818 | if (!self.eql(Value.initPayload(&res_payload.base))) |
| 802 | return error.Overflow; | 819 | return error.Overflow; |
| 803 | return Value.initPayload(&res_payload.base).copy(allocator); | 820 | return Value.initPayload(&res_payload.base).copy(allocator); |
| 804 | }, | 821 | }, |
| 805 | 64 => { | 822 | 64 => { |
| 806 | var res_payload = Value.Payload.Float_64{.val = self.toFloat(f64)}; | 823 | var res_payload = Value.Payload.Float_64{ .val = self.toFloat(f64) }; |
| 807 | if (!self.eql(Value.initPayload(&res_payload.base))) | 824 | if (!self.eql(Value.initPayload(&res_payload.base))) |
| 808 | return error.Overflow; | 825 | return error.Overflow; |
| 809 | return Value.initPayload(&res_payload.base).copy(allocator); | 826 | return Value.initPayload(&res_payload.base).copy(allocator); |
| ... | @@ -875,7 +892,9 @@ pub const Value = extern union { | ... | @@ -875,7 +892,9 @@ pub const Value = extern union { |
| 875 | .int_i64, | 892 | .int_i64, |
| 876 | .int_big_positive, | 893 | .int_big_positive, |
| 877 | .int_big_negative, | 894 | .int_big_negative, |
| 878 | .the_one_possible_value, | 895 | .empty_array, |
| 896 | .void_value, | ||
| 897 | .unreachable_value, | ||
| 879 | => unreachable, | 898 | => unreachable, |
| 880 | 899 | ||
| 881 | .zero => false, | 900 | .zero => false, |
| ... | @@ -939,10 +958,12 @@ pub const Value = extern union { | ... | @@ -939,10 +958,12 @@ pub const Value = extern union { |
| 939 | .bytes, | 958 | .bytes, |
| 940 | .repeated, | 959 | .repeated, |
| 941 | .undef, | 960 | .undef, |
| 961 | .void_value, | ||
| 962 | .unreachable_value, | ||
| 963 | .empty_array, | ||
| 942 | => unreachable, | 964 | => unreachable, |
| 943 | 965 | ||
| 944 | .zero, | 966 | .zero, |
| 945 | .the_one_possible_value, // an integer with one possible value is always zero | ||
| 946 | .bool_false, | 967 | .bool_false, |
| 947 | => .eq, | 968 | => .eq, |
| 948 | 969 | ||
| ... | @@ -964,8 +985,8 @@ pub const Value = extern union { | ... | @@ -964,8 +985,8 @@ pub const Value = extern union { |
| 964 | pub fn order(lhs: Value, rhs: Value) std.math.Order { | 985 | pub fn order(lhs: Value, rhs: Value) std.math.Order { |
| 965 | const lhs_tag = lhs.tag(); | 986 | const lhs_tag = lhs.tag(); |
| 966 | const rhs_tag = rhs.tag(); | 987 | const rhs_tag = rhs.tag(); |
| 967 | const lhs_is_zero = lhs_tag == .zero or lhs_tag == .the_one_possible_value; | 988 | const lhs_is_zero = lhs_tag == .zero; |
| 968 | const rhs_is_zero = rhs_tag == .zero or rhs_tag == .the_one_possible_value; | 989 | const rhs_is_zero = rhs_tag == .zero; |
| 969 | if (lhs_is_zero) return rhs.orderAgainstZero().invert(); | 990 | if (lhs_is_zero) return rhs.orderAgainstZero().invert(); |
| 970 | if (rhs_is_zero) return lhs.orderAgainstZero(); | 991 | if (rhs_is_zero) return lhs.orderAgainstZero(); |
| 971 | 992 | ||
| ... | @@ -1071,9 +1092,11 @@ pub const Value = extern union { | ... | @@ -1071,9 +1092,11 @@ pub const Value = extern union { |
| 1071 | .float_32, | 1092 | .float_32, |
| 1072 | .float_64, | 1093 | .float_64, |
| 1073 | .float_128, | 1094 | .float_128, |
| 1095 | .void_value, | ||
| 1096 | .unreachable_value, | ||
| 1097 | .empty_array, | ||
| 1074 | => unreachable, | 1098 | => unreachable, |
| 1075 | 1099 | ||
| 1076 | .the_one_possible_value => Value.initTag(.the_one_possible_value), | ||
| 1077 | .ref_val => self.cast(Payload.RefVal).?.val, | 1100 | .ref_val => self.cast(Payload.RefVal).?.val, |
| 1078 | .decl_ref => self.cast(Payload.DeclRef).?.decl.value(), | 1101 | .decl_ref => self.cast(Payload.DeclRef).?.decl.value(), |
| 1079 | .elem_ptr => { | 1102 | .elem_ptr => { |
| ... | @@ -1130,7 +1153,6 @@ pub const Value = extern union { | ... | @@ -1130,7 +1153,6 @@ pub const Value = extern union { |
| 1130 | .single_const_pointer_to_comptime_int_type, | 1153 | .single_const_pointer_to_comptime_int_type, |
| 1131 | .const_slice_u8_type, | 1154 | .const_slice_u8_type, |
| 1132 | .zero, | 1155 | .zero, |
| 1133 | .the_one_possible_value, | ||
| 1134 | .bool_true, | 1156 | .bool_true, |
| 1135 | .bool_false, | 1157 | .bool_false, |
| 1136 | .null_value, | 1158 | .null_value, |
| ... | @@ -1147,8 +1169,12 @@ pub const Value = extern union { | ... | @@ -1147,8 +1169,12 @@ pub const Value = extern union { |
| 1147 | .float_32, | 1169 | .float_32, |
| 1148 | .float_64, | 1170 | .float_64, |
| 1149 | .float_128, | 1171 | .float_128, |
| 1172 | .void_value, | ||
| 1173 | .unreachable_value, | ||
| 1150 | => unreachable, | 1174 | => unreachable, |
| 1151 | 1175 | ||
| 1176 | .empty_array => unreachable, // out of bounds array index | ||
| 1177 | |||
| 1152 | .bytes => { | 1178 | .bytes => { |
| 1153 | const int_payload = try allocator.create(Payload.Int_u64); | 1179 | const int_payload = try allocator.create(Payload.Int_u64); |
| 1154 | int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] }; | 1180 | int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] }; |
| ... | @@ -1175,8 +1201,7 @@ pub const Value = extern union { | ... | @@ -1175,8 +1201,7 @@ pub const Value = extern union { |
| 1175 | return self.tag() == .undef; | 1201 | return self.tag() == .undef; |
| 1176 | } | 1202 | } |
| 1177 | 1203 | ||
| 1178 | /// Valid for all types. Asserts the value is not undefined. | 1204 | /// Valid for all types. Asserts the value is not undefined and not unreachable. |
| 1179 | /// `.the_one_possible_value` is reported as not null. | ||
| 1180 | pub fn isNull(self: Value) bool { | 1205 | pub fn isNull(self: Value) bool { |
| 1181 | return switch (self.tag()) { | 1206 | return switch (self.tag()) { |
| 1182 | .ty, | 1207 | .ty, |
| ... | @@ -1221,7 +1246,7 @@ pub const Value = extern union { | ... | @@ -1221,7 +1246,7 @@ pub const Value = extern union { |
| 1221 | .single_const_pointer_to_comptime_int_type, | 1246 | .single_const_pointer_to_comptime_int_type, |
| 1222 | .const_slice_u8_type, | 1247 | .const_slice_u8_type, |
| 1223 | .zero, | 1248 | .zero, |
| 1224 | .the_one_possible_value, | 1249 | .empty_array, |
| 1225 | .bool_true, | 1250 | .bool_true, |
| 1226 | .bool_false, | 1251 | .bool_false, |
| 1227 | .function, | 1252 | .function, |
| ... | @@ -1238,9 +1263,11 @@ pub const Value = extern union { | ... | @@ -1238,9 +1263,11 @@ pub const Value = extern union { |
| 1238 | .float_32, | 1263 | .float_32, |
| 1239 | .float_64, | 1264 | .float_64, |
| 1240 | .float_128, | 1265 | .float_128, |
| 1266 | .void_value, | ||
| 1241 | => false, | 1267 | => false, |
| 1242 | 1268 | ||
| 1243 | .undef => unreachable, | 1269 | .undef => unreachable, |
| 1270 | .unreachable_value => unreachable, | ||
| 1244 | .null_value => true, | 1271 | .null_value => true, |
| 1245 | }; | 1272 | }; |
| 1246 | } | 1273 | } |
src-self-hosted/zir.zig+246-77| ... | @@ -34,25 +34,63 @@ pub const Inst = struct { | ... | @@ -34,25 +34,63 @@ pub const Inst = struct { |
| 34 | 34 | ||
| 35 | /// These names are used directly as the instruction names in the text format. | 35 | /// These names are used directly as the instruction names in the text format. |
| 36 | pub const Tag = enum { | 36 | pub const Tag = enum { |
| 37 | /// Arithmetic addition, asserts no integer overflow. | ||
| 38 | add, | ||
| 39 | /// Twos complement wrapping integer addition. | ||
| 40 | addwrap, | ||
| 37 | /// Allocates stack local memory. Its lifetime ends when the block ends that contains | 41 | /// Allocates stack local memory. Its lifetime ends when the block ends that contains |
| 38 | /// this instruction. | 42 | /// this instruction. The operand is the type of the allocated object. |
| 39 | alloc, | 43 | alloc, |
| 40 | /// Same as `alloc` except the type is inferred. | 44 | /// Same as `alloc` except the type is inferred. |
| 41 | alloc_inferred, | 45 | alloc_inferred, |
| 46 | /// Array concatenation. `a ++ b` | ||
| 47 | array_cat, | ||
| 48 | /// Array multiplication `a ** b` | ||
| 49 | array_mul, | ||
| 42 | /// Function parameter value. These must be first in a function's main block, | 50 | /// Function parameter value. These must be first in a function's main block, |
| 43 | /// in respective order with the parameters. | 51 | /// in respective order with the parameters. |
| 44 | arg, | 52 | arg, |
| 53 | /// Type coercion. | ||
| 54 | as, | ||
| 55 | /// Inline assembly. | ||
| 56 | @"asm", | ||
| 57 | /// Bitwise AND. `&` | ||
| 58 | bitand, | ||
| 59 | /// TODO delete this instruction, it has no purpose. | ||
| 60 | bitcast, | ||
| 61 | /// An arbitrary typed pointer, which is to be used as an L-Value, is pointer-casted | ||
| 62 | /// to a new L-Value. The destination type is given by LHS. The cast is to be evaluated | ||
| 63 | /// as if it were a bit-cast operation from the operand pointer element type to the | ||
| 64 | /// provided destination type. | ||
| 65 | bitcast_lvalue, | ||
| 45 | /// A typed result location pointer is bitcasted to a new result location pointer. | 66 | /// A typed result location pointer is bitcasted to a new result location pointer. |
| 46 | /// The new result location pointer has an inferred type. | 67 | /// The new result location pointer has an inferred type. |
| 47 | bitcast_result_ptr, | 68 | bitcast_result_ptr, |
| 69 | /// Bitwise OR. `|` | ||
| 70 | bitor, | ||
| 48 | /// A labeled block of code, which can return a value. | 71 | /// A labeled block of code, which can return a value. |
| 49 | block, | 72 | block, |
| 73 | /// Boolean NOT. See also `bitnot`. | ||
| 74 | boolnot, | ||
| 50 | /// Return a value from a `Block`. | 75 | /// Return a value from a `Block`. |
| 51 | @"break", | 76 | @"break", |
| 52 | breakpoint, | 77 | breakpoint, |
| 53 | /// Same as `break` but without an operand; the operand is assumed to be the void value. | 78 | /// Same as `break` but without an operand; the operand is assumed to be the void value. |
| 54 | breakvoid, | 79 | breakvoid, |
| 80 | /// Function call. | ||
| 55 | call, | 81 | call, |
| 82 | /// `<` | ||
| 83 | cmp_lt, | ||
| 84 | /// `<=` | ||
| 85 | cmp_lte, | ||
| 86 | /// `==` | ||
| 87 | cmp_eq, | ||
| 88 | /// `>=` | ||
| 89 | cmp_gte, | ||
| 90 | /// `>` | ||
| 91 | cmp_gt, | ||
| 92 | /// `!=` | ||
| 93 | cmp_neq, | ||
| 56 | /// Coerces a result location pointer to a new element type. It is evaluated "backwards"- | 94 | /// Coerces a result location pointer to a new element type. It is evaluated "backwards"- |
| 57 | /// as type coercion from the new element type to the old element type. | 95 | /// as type coercion from the new element type to the old element type. |
| 58 | /// LHS is destination element type, RHS is result pointer. | 96 | /// LHS is destination element type, RHS is result pointer. |
| ... | @@ -65,8 +103,12 @@ pub const Inst = struct { | ... | @@ -65,8 +103,12 @@ pub const Inst = struct { |
| 65 | coerce_to_ptr_elem, | 103 | coerce_to_ptr_elem, |
| 66 | /// Emit an error message and fail compilation. | 104 | /// Emit an error message and fail compilation. |
| 67 | compileerror, | 105 | compileerror, |
| 106 | /// Conditional branch. Splits control flow based on a boolean condition value. | ||
| 107 | condbr, | ||
| 68 | /// Special case, has no textual representation. | 108 | /// Special case, has no textual representation. |
| 69 | @"const", | 109 | @"const", |
| 110 | /// Declares the beginning of a statement. Used for debug info. | ||
| 111 | dbg_stmt, | ||
| 70 | /// Represents a pointer to a global decl by name. | 112 | /// Represents a pointer to a global decl by name. |
| 71 | declref, | 113 | declref, |
| 72 | /// Represents a pointer to a global decl by string name. | 114 | /// Represents a pointer to a global decl by string name. |
| ... | @@ -76,61 +118,108 @@ pub const Inst = struct { | ... | @@ -76,61 +118,108 @@ pub const Inst = struct { |
| 76 | declval, | 118 | declval, |
| 77 | /// Same as declval but the parameter is a `*Module.Decl` rather than a name. | 119 | /// Same as declval but the parameter is a `*Module.Decl` rather than a name. |
| 78 | declval_in_module, | 120 | declval_in_module, |
| 121 | /// Load the value from a pointer. | ||
| 122 | deref, | ||
| 123 | /// Arithmetic division. Asserts no integer overflow. | ||
| 124 | div, | ||
| 125 | /// Given a pointer to an array, slice, or pointer, returns a pointer to the element at | ||
| 126 | /// the provided index. | ||
| 127 | elemptr, | ||
| 79 | /// Emits a compile error if the operand is not `void`. | 128 | /// Emits a compile error if the operand is not `void`. |
| 80 | ensure_result_used, | 129 | ensure_result_used, |
| 81 | /// Emits a compile error if an error is ignored. | 130 | /// Emits a compile error if an error is ignored. |
| 82 | ensure_result_non_error, | 131 | ensure_result_non_error, |
| 83 | boolnot, | 132 | /// Export the provided Decl as the provided name in the compilation's output object file. |
| 84 | /// Obtains a pointer to the return value. | 133 | @"export", |
| 85 | ret_ptr, | 134 | /// Given a pointer to a struct or object that contains virtual fields, returns a pointer |
| 86 | /// Obtains the return type of the in-scope function. | 135 | /// to the named field. |
| 87 | ret_type, | ||
| 88 | /// Write a value to a pointer. | ||
| 89 | store, | ||
| 90 | /// String Literal. Makes an anonymous Decl and then takes a pointer to it. | ||
| 91 | str, | ||
| 92 | int, | ||
| 93 | inttype, | ||
| 94 | ptrtoint, | ||
| 95 | fieldptr, | 136 | fieldptr, |
| 96 | deref, | 137 | /// Convert a larger float type to any other float type, possibly causing a loss of precision. |
| 97 | as, | 138 | floatcast, |
| 98 | @"asm", | 139 | /// Declare a function body. |
| 99 | @"unreachable", | ||
| 100 | @"return", | ||
| 101 | returnvoid, | ||
| 102 | @"fn", | 140 | @"fn", |
| 141 | /// Returns a function type. | ||
| 103 | fntype, | 142 | fntype, |
| 104 | @"export", | 143 | /// Integer literal. |
| 144 | int, | ||
| 145 | /// Convert an integer value to another integer type, asserting that the destination type | ||
| 146 | /// can hold the same mathematical value. | ||
| 147 | intcast, | ||
| 148 | /// Make an integer type out of signedness and bit count. | ||
| 149 | inttype, | ||
| 150 | /// Return a boolean false if an optional is null. `x != null` | ||
| 151 | isnonnull, | ||
| 152 | /// Return a boolean true if an optional is null. `x == null` | ||
| 153 | isnull, | ||
| 154 | /// Ambiguously remainder division or modulus. If the computation would possibly have | ||
| 155 | /// a different value depending on whether the operation is remainder division or modulus, | ||
| 156 | /// a compile error is emitted. Otherwise the computation is performed. | ||
| 157 | mod_rem, | ||
| 158 | /// Arithmetic multiplication. Asserts no integer overflow. | ||
| 159 | mul, | ||
| 160 | /// Twos complement wrapping integer multiplication. | ||
| 161 | mulwrap, | ||
| 105 | /// Given a reference to a function and a parameter index, returns the | 162 | /// Given a reference to a function and a parameter index, returns the |
| 106 | /// type of the parameter. TODO what happens when the parameter is `anytype`? | 163 | /// type of the parameter. TODO what happens when the parameter is `anytype`? |
| 107 | param_type, | 164 | param_type, |
| 165 | /// An alternative to using `const` for simple primitive values such as `true` or `u8`. | ||
| 166 | /// TODO flatten so that each primitive has its own ZIR Inst Tag. | ||
| 108 | primitive, | 167 | primitive, |
| 109 | intcast, | 168 | /// Convert a pointer to a `usize` integer. |
| 110 | bitcast, | 169 | ptrtoint, |
| 111 | floatcast, | 170 | /// Turns an R-Value into a const L-Value. In other words, it takes a value, |
| 112 | elemptr, | 171 | /// stores it in a memory location, and returns a const pointer to it. If the value |
| 113 | add, | 172 | /// is `comptime`, the memory location is global static constant data. Otherwise, |
| 173 | /// the memory location is in the stack frame, local to the scope containing the | ||
| 174 | /// instruction. | ||
| 175 | ref, | ||
| 176 | /// Obtains a pointer to the return value. | ||
| 177 | ret_ptr, | ||
| 178 | /// Obtains the return type of the in-scope function. | ||
| 179 | ret_type, | ||
| 180 | /// Sends control flow back to the function's callee. Takes an operand as the return value. | ||
| 181 | @"return", | ||
| 182 | /// Same as `return` but there is no operand; the operand is implicitly the void value. | ||
| 183 | returnvoid, | ||
| 184 | /// Integer shift-left. Zeroes are shifted in from the right hand side. | ||
| 185 | shl, | ||
| 186 | /// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type. | ||
| 187 | shr, | ||
| 188 | /// Create a const pointer type based on the element type. `*const T` | ||
| 189 | single_const_ptr_type, | ||
| 190 | /// Create a mutable pointer type based on the element type. `*T` | ||
| 191 | single_mut_ptr_type, | ||
| 192 | /// Write a value to a pointer. For loading, see `deref`. | ||
| 193 | store, | ||
| 194 | /// String Literal. Makes an anonymous Decl and then takes a pointer to it. | ||
| 195 | str, | ||
| 196 | /// Arithmetic subtraction. Asserts no integer overflow. | ||
| 114 | sub, | 197 | sub, |
| 115 | cmp_lt, | 198 | /// Twos complement wrapping integer subtraction. |
| 116 | cmp_lte, | 199 | subwrap, |
| 117 | cmp_eq, | 200 | /// Returns the type of a value. |
| 118 | cmp_gte, | 201 | typeof, |
| 119 | cmp_gt, | 202 | /// Asserts control-flow will not reach this instruction. Not safety checked - the compiler |
| 120 | cmp_neq, | 203 | /// will assume the correctness of this instruction. |
| 121 | condbr, | 204 | unreach_nocheck, |
| 122 | isnull, | 205 | /// Asserts control-flow will not reach this instruction. In safety-checked modes, |
| 123 | isnonnull, | 206 | /// this will generate a call to the panic function unless it can be proven unreachable |
| 207 | /// by the compiler. | ||
| 208 | @"unreachable", | ||
| 209 | /// Bitwise XOR. `^` | ||
| 210 | xor, | ||
| 124 | 211 | ||
| 125 | pub fn Type(tag: Tag) type { | 212 | pub fn Type(tag: Tag) type { |
| 126 | return switch (tag) { | 213 | return switch (tag) { |
| 127 | .arg, | 214 | .arg, |
| 128 | .breakpoint, | 215 | .breakpoint, |
| 129 | .@"unreachable", | 216 | .dbg_stmt, |
| 130 | .returnvoid, | 217 | .returnvoid, |
| 131 | .alloc_inferred, | 218 | .alloc_inferred, |
| 132 | .ret_ptr, | 219 | .ret_ptr, |
| 133 | .ret_type, | 220 | .ret_type, |
| 221 | .unreach_nocheck, | ||
| 222 | .@"unreachable", | ||
| 134 | => NoOp, | 223 | => NoOp, |
| 135 | 224 | ||
| 136 | .boolnot, | 225 | .boolnot, |
| ... | @@ -143,10 +232,28 @@ pub const Inst = struct { | ... | @@ -143,10 +232,28 @@ pub const Inst = struct { |
| 143 | .ensure_result_used, | 232 | .ensure_result_used, |
| 144 | .ensure_result_non_error, | 233 | .ensure_result_non_error, |
| 145 | .bitcast_result_ptr, | 234 | .bitcast_result_ptr, |
| 235 | .ref, | ||
| 236 | .bitcast_lvalue, | ||
| 237 | .typeof, | ||
| 238 | .single_const_ptr_type, | ||
| 239 | .single_mut_ptr_type, | ||
| 146 | => UnOp, | 240 | => UnOp, |
| 147 | 241 | ||
| 148 | .add, | 242 | .add, |
| 243 | .addwrap, | ||
| 244 | .array_cat, | ||
| 245 | .array_mul, | ||
| 246 | .bitand, | ||
| 247 | .bitor, | ||
| 248 | .div, | ||
| 249 | .mod_rem, | ||
| 250 | .mul, | ||
| 251 | .mulwrap, | ||
| 252 | .shl, | ||
| 253 | .shr, | ||
| 254 | .store, | ||
| 149 | .sub, | 255 | .sub, |
| 256 | .subwrap, | ||
| 150 | .cmp_lt, | 257 | .cmp_lt, |
| 151 | .cmp_lte, | 258 | .cmp_lte, |
| 152 | .cmp_eq, | 259 | .cmp_eq, |
| ... | @@ -158,6 +265,7 @@ pub const Inst = struct { | ... | @@ -158,6 +265,7 @@ pub const Inst = struct { |
| 158 | .intcast, | 265 | .intcast, |
| 159 | .bitcast, | 266 | .bitcast, |
| 160 | .coerce_result_ptr, | 267 | .coerce_result_ptr, |
| 268 | .xor, | ||
| 161 | => BinOp, | 269 | => BinOp, |
| 162 | 270 | ||
| 163 | .block => Block, | 271 | .block => Block, |
| ... | @@ -172,7 +280,6 @@ pub const Inst = struct { | ... | @@ -172,7 +280,6 @@ pub const Inst = struct { |
| 172 | .coerce_result_block_ptr => CoerceResultBlockPtr, | 280 | .coerce_result_block_ptr => CoerceResultBlockPtr, |
| 173 | .compileerror => CompileError, | 281 | .compileerror => CompileError, |
| 174 | .@"const" => Const, | 282 | .@"const" => Const, |
| 175 | .store => Store, | ||
| 176 | .str => Str, | 283 | .str => Str, |
| 177 | .int => Int, | 284 | .int => Int, |
| 178 | .inttype => IntType, | 285 | .inttype => IntType, |
| ... | @@ -192,63 +299,83 @@ pub const Inst = struct { | ... | @@ -192,63 +299,83 @@ pub const Inst = struct { |
| 192 | /// Function calls do not count. | 299 | /// Function calls do not count. |
| 193 | pub fn isNoReturn(tag: Tag) bool { | 300 | pub fn isNoReturn(tag: Tag) bool { |
| 194 | return switch (tag) { | 301 | return switch (tag) { |
| 302 | .add, | ||
| 303 | .addwrap, | ||
| 195 | .alloc, | 304 | .alloc, |
| 196 | .alloc_inferred, | 305 | .alloc_inferred, |
| 306 | .array_cat, | ||
| 307 | .array_mul, | ||
| 197 | .arg, | 308 | .arg, |
| 309 | .as, | ||
| 310 | .@"asm", | ||
| 311 | .bitand, | ||
| 312 | .bitcast, | ||
| 313 | .bitcast_lvalue, | ||
| 198 | .bitcast_result_ptr, | 314 | .bitcast_result_ptr, |
| 315 | .bitor, | ||
| 199 | .block, | 316 | .block, |
| 317 | .boolnot, | ||
| 200 | .breakpoint, | 318 | .breakpoint, |
| 201 | .call, | 319 | .call, |
| 320 | .cmp_lt, | ||
| 321 | .cmp_lte, | ||
| 322 | .cmp_eq, | ||
| 323 | .cmp_gte, | ||
| 324 | .cmp_gt, | ||
| 325 | .cmp_neq, | ||
| 202 | .coerce_result_ptr, | 326 | .coerce_result_ptr, |
| 203 | .coerce_result_block_ptr, | 327 | .coerce_result_block_ptr, |
| 204 | .coerce_to_ptr_elem, | 328 | .coerce_to_ptr_elem, |
| 205 | .@"const", | 329 | .@"const", |
| 330 | .dbg_stmt, | ||
| 206 | .declref, | 331 | .declref, |
| 207 | .declref_str, | 332 | .declref_str, |
| 208 | .declval, | 333 | .declval, |
| 209 | .declval_in_module, | 334 | .declval_in_module, |
| 335 | .deref, | ||
| 336 | .div, | ||
| 337 | .elemptr, | ||
| 210 | .ensure_result_used, | 338 | .ensure_result_used, |
| 211 | .ensure_result_non_error, | 339 | .ensure_result_non_error, |
| 212 | .ret_ptr, | 340 | .@"export", |
| 213 | .ret_type, | 341 | .floatcast, |
| 214 | .store, | ||
| 215 | .str, | ||
| 216 | .int, | ||
| 217 | .inttype, | ||
| 218 | .ptrtoint, | ||
| 219 | .fieldptr, | 342 | .fieldptr, |
| 220 | .deref, | ||
| 221 | .as, | ||
| 222 | .@"asm", | ||
| 223 | .@"fn", | 343 | .@"fn", |
| 224 | .fntype, | 344 | .fntype, |
| 225 | .@"export", | 345 | .int, |
| 346 | .intcast, | ||
| 347 | .inttype, | ||
| 348 | .isnonnull, | ||
| 349 | .isnull, | ||
| 350 | .mod_rem, | ||
| 351 | .mul, | ||
| 352 | .mulwrap, | ||
| 226 | .param_type, | 353 | .param_type, |
| 227 | .primitive, | 354 | .primitive, |
| 228 | .intcast, | 355 | .ptrtoint, |
| 229 | .bitcast, | 356 | .ref, |
| 230 | .floatcast, | 357 | .ret_ptr, |
| 231 | .elemptr, | 358 | .ret_type, |
| 232 | .add, | 359 | .shl, |
| 360 | .shr, | ||
| 361 | .single_const_ptr_type, | ||
| 362 | .single_mut_ptr_type, | ||
| 363 | .store, | ||
| 364 | .str, | ||
| 233 | .sub, | 365 | .sub, |
| 234 | .cmp_lt, | 366 | .subwrap, |
| 235 | .cmp_lte, | 367 | .typeof, |
| 236 | .cmp_eq, | 368 | .xor, |
| 237 | .cmp_gte, | ||
| 238 | .cmp_gt, | ||
| 239 | .cmp_neq, | ||
| 240 | .isnull, | ||
| 241 | .isnonnull, | ||
| 242 | .boolnot, | ||
| 243 | => false, | 369 | => false, |
| 244 | 370 | ||
| 245 | .condbr, | ||
| 246 | .@"unreachable", | ||
| 247 | .@"return", | ||
| 248 | .returnvoid, | ||
| 249 | .@"break", | 371 | .@"break", |
| 250 | .breakvoid, | 372 | .breakvoid, |
| 373 | .condbr, | ||
| 251 | .compileerror, | 374 | .compileerror, |
| 375 | .@"return", | ||
| 376 | .returnvoid, | ||
| 377 | .unreach_nocheck, | ||
| 378 | .@"unreachable", | ||
| 252 | => true, | 379 | => true, |
| 253 | }; | 380 | }; |
| 254 | } | 381 | } |
| ... | @@ -430,17 +557,6 @@ pub const Inst = struct { | ... | @@ -430,17 +557,6 @@ pub const Inst = struct { |
| 430 | kw_args: struct {}, | 557 | kw_args: struct {}, |
| 431 | }; | 558 | }; |
| 432 | 559 | ||
| 433 | pub const Store = struct { | ||
| 434 | pub const base_tag = Tag.store; | ||
| 435 | base: Inst, | ||
| 436 | |||
| 437 | positionals: struct { | ||
| 438 | ptr: *Inst, | ||
| 439 | value: *Inst, | ||
| 440 | }, | ||
| 441 | kw_args: struct {}, | ||
| 442 | }; | ||
| 443 | |||
| 444 | pub const Str = struct { | 560 | pub const Str = struct { |
| 445 | pub const base_tag = Tag.str; | 561 | pub const base_tag = Tag.str; |
| 446 | base: Inst, | 562 | base: Inst, |
| ... | @@ -630,7 +746,7 @@ pub const Inst = struct { | ... | @@ -630,7 +746,7 @@ pub const Inst = struct { |
| 630 | .@"false" => .{ .ty = Type.initTag(.bool), .val = Value.initTag(.bool_false) }, | 746 | .@"false" => .{ .ty = Type.initTag(.bool), .val = Value.initTag(.bool_false) }, |
| 631 | .@"null" => .{ .ty = Type.initTag(.@"null"), .val = Value.initTag(.null_value) }, | 747 | .@"null" => .{ .ty = Type.initTag(.@"null"), .val = Value.initTag(.null_value) }, |
| 632 | .@"undefined" => .{ .ty = Type.initTag(.@"undefined"), .val = Value.initTag(.undef) }, | 748 | .@"undefined" => .{ .ty = Type.initTag(.@"undefined"), .val = Value.initTag(.undef) }, |
| 633 | .void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.the_one_possible_value) }, | 749 | .void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.void_value) }, |
| 634 | }; | 750 | }; |
| 635 | } | 751 | } |
| 636 | }; | 752 | }; |
| ... | @@ -1486,6 +1602,21 @@ const EmitZIR = struct { | ... | @@ -1486,6 +1602,21 @@ const EmitZIR = struct { |
| 1486 | const decl = decl_ref.decl; | 1602 | const decl = decl_ref.decl; |
| 1487 | return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl)); | 1603 | return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl)); |
| 1488 | } | 1604 | } |
| 1605 | if (typed_value.val.isUndef()) { | ||
| 1606 | const as_inst = try self.arena.allocator.create(Inst.BinOp); | ||
| 1607 | as_inst.* = .{ | ||
| 1608 | .base = .{ | ||
| 1609 | .tag = .as, | ||
| 1610 | .src = src, | ||
| 1611 | }, | ||
| 1612 | .positionals = .{ | ||
| 1613 | .lhs = (try self.emitType(src, typed_value.ty)).inst, | ||
| 1614 | .rhs = (try self.emitPrimitive(src, .@"undefined")).inst, | ||
| 1615 | }, | ||
| 1616 | .kw_args = .{}, | ||
| 1617 | }; | ||
| 1618 | return self.emitUnnamedDecl(&as_inst.base); | ||
| 1619 | } | ||
| 1489 | switch (typed_value.ty.zigTypeTag()) { | 1620 | switch (typed_value.ty.zigTypeTag()) { |
| 1490 | .Pointer => { | 1621 | .Pointer => { |
| 1491 | const ptr_elem_type = typed_value.ty.elemType(); | 1622 | const ptr_elem_type = typed_value.ty.elemType(); |
| ... | @@ -1716,15 +1847,19 @@ const EmitZIR = struct { | ... | @@ -1716,15 +1847,19 @@ const EmitZIR = struct { |
| 1716 | .breakpoint => try self.emitNoOp(inst.src, .breakpoint), | 1847 | .breakpoint => try self.emitNoOp(inst.src, .breakpoint), |
| 1717 | .unreach => try self.emitNoOp(inst.src, .@"unreachable"), | 1848 | .unreach => try self.emitNoOp(inst.src, .@"unreachable"), |
| 1718 | .retvoid => try self.emitNoOp(inst.src, .returnvoid), | 1849 | .retvoid => try self.emitNoOp(inst.src, .returnvoid), |
| 1850 | .dbg_stmt => try self.emitNoOp(inst.src, .dbg_stmt), | ||
| 1719 | 1851 | ||
| 1720 | .not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot), | 1852 | .not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot), |
| 1721 | .ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"), | 1853 | .ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"), |
| 1722 | .ptrtoint => try self.emitUnOp(inst.src, new_body, inst.castTag(.ptrtoint).?, .ptrtoint), | 1854 | .ptrtoint => try self.emitUnOp(inst.src, new_body, inst.castTag(.ptrtoint).?, .ptrtoint), |
| 1723 | .isnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnull).?, .isnull), | 1855 | .isnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnull).?, .isnull), |
| 1724 | .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull), | 1856 | .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull), |
| 1857 | .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref), | ||
| 1858 | .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref), | ||
| 1725 | 1859 | ||
| 1726 | .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add), | 1860 | .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add), |
| 1727 | .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub), | 1861 | .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub), |
| 1862 | .store => try self.emitBinOp(inst.src, new_body, inst.castTag(.store).?, .store), | ||
| 1728 | .cmp_lt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lt).?, .cmp_lt), | 1863 | .cmp_lt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lt).?, .cmp_lt), |
| 1729 | .cmp_lte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lte).?, .cmp_lte), | 1864 | .cmp_lte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lte).?, .cmp_lte), |
| 1730 | .cmp_eq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_eq).?, .cmp_eq), | 1865 | .cmp_eq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_eq).?, .cmp_eq), |
| ... | @@ -1736,6 +1871,21 @@ const EmitZIR = struct { | ... | @@ -1736,6 +1871,21 @@ const EmitZIR = struct { |
| 1736 | .intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast), | 1871 | .intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast), |
| 1737 | .floatcast => try self.emitCast(inst.src, new_body, inst.castTag(.floatcast).?, .floatcast), | 1872 | .floatcast => try self.emitCast(inst.src, new_body, inst.castTag(.floatcast).?, .floatcast), |
| 1738 | 1873 | ||
| 1874 | .alloc => blk: { | ||
| 1875 | const new_inst = try self.arena.allocator.create(Inst.UnOp); | ||
| 1876 | new_inst.* = .{ | ||
| 1877 | .base = .{ | ||
| 1878 | .src = inst.src, | ||
| 1879 | .tag = .alloc, | ||
| 1880 | }, | ||
| 1881 | .positionals = .{ | ||
| 1882 | .operand = (try self.emitType(inst.src, inst.ty)).inst, | ||
| 1883 | }, | ||
| 1884 | .kw_args = .{}, | ||
| 1885 | }; | ||
| 1886 | break :blk &new_inst.base; | ||
| 1887 | }, | ||
| 1888 | |||
| 1739 | .block => blk: { | 1889 | .block => blk: { |
| 1740 | const old_inst = inst.castTag(.block).?; | 1890 | const old_inst = inst.castTag(.block).?; |
| 1741 | const new_inst = try self.arena.allocator.create(Inst.Block); | 1891 | const new_inst = try self.arena.allocator.create(Inst.Block); |
| ... | @@ -1973,6 +2123,25 @@ const EmitZIR = struct { | ... | @@ -1973,6 +2123,25 @@ const EmitZIR = struct { |
| 1973 | }; | 2123 | }; |
| 1974 | return self.emitUnnamedDecl(&inttype_inst.base); | 2124 | return self.emitUnnamedDecl(&inttype_inst.base); |
| 1975 | }, | 2125 | }, |
| 2126 | .Pointer => { | ||
| 2127 | if (ty.isSinglePointer()) { | ||
| 2128 | const inst = try self.arena.allocator.create(Inst.UnOp); | ||
| 2129 | const tag: Inst.Tag = if (ty.isConstPtr()) .single_const_ptr_type else .single_mut_ptr_type; | ||
| 2130 | inst.* = .{ | ||
| 2131 | .base = .{ | ||
| 2132 | .src = src, | ||
| 2133 | .tag = tag, | ||
| 2134 | }, | ||
| 2135 | .positionals = .{ | ||
| 2136 | .operand = (try self.emitType(src, ty.elemType())).inst, | ||
| 2137 | }, | ||
| 2138 | .kw_args = .{}, | ||
| 2139 | }; | ||
| 2140 | return self.emitUnnamedDecl(&inst.base); | ||
| 2141 | } else { | ||
| 2142 | std.debug.panic("TODO implement emitType for {}", .{ty}); | ||
| 2143 | } | ||
| 2144 | }, | ||
| 1976 | else => std.debug.panic("TODO implement emitType for {}", .{ty}), | 2145 | else => std.debug.panic("TODO implement emitType for {}", .{ty}), |
| 1977 | }, | 2146 | }, |
| 1978 | } | 2147 | } |
src-self-hosted/zir_sema.zig created+1160| ... | @@ -0,0 +1,1160 @@ | ||
| 1 | //! Semantic analysis of ZIR instructions. | ||
| 2 | //! This file operates on a `Module` instance, transforming untyped ZIR | ||
| 3 | //! instructions into semantically-analyzed IR instructions. It does type | ||
| 4 | //! checking, comptime control flow, and safety-check generation. This is the | ||
| 5 | //! the heart of the Zig compiler. | ||
| 6 | //! When deciding if something goes into this file or into Module, here is a | ||
| 7 | //! guiding principle: if it has to do with (untyped) ZIR instructions, it goes | ||
| 8 | //! here. If the analysis operates on typed IR instructions, it goes in Module. | ||
| 9 | |||
| 10 | const std = @import("std"); | ||
| 11 | const mem = std.mem; | ||
| 12 | const Allocator = std.mem.Allocator; | ||
| 13 | const Value = @import("value.zig").Value; | ||
| 14 | const Type = @import("type.zig").Type; | ||
| 15 | const TypedValue = @import("TypedValue.zig"); | ||
| 16 | const assert = std.debug.assert; | ||
| 17 | const ir = @import("ir.zig"); | ||
| 18 | const zir = @import("zir.zig"); | ||
| 19 | const Module = @import("Module.zig"); | ||
| 20 | const Inst = ir.Inst; | ||
| 21 | const Body = ir.Body; | ||
| 22 | const trace = @import("tracy.zig").trace; | ||
| 23 | const Scope = Module.Scope; | ||
| 24 | const InnerError = Module.InnerError; | ||
| 25 | const Decl = Module.Decl; | ||
| 26 | |||
| 27 | pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { | ||
| 28 | switch (old_inst.tag) { | ||
| 29 | .alloc => return analyzeInstAlloc(mod, scope, old_inst.castTag(.alloc).?), | ||
| 30 | .alloc_inferred => return analyzeInstAllocInferred(mod, scope, old_inst.castTag(.alloc_inferred).?), | ||
| 31 | .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?), | ||
| 32 | .bitcast_lvalue => return analyzeInstBitCastLValue(mod, scope, old_inst.castTag(.bitcast_lvalue).?), | ||
| 33 | .bitcast_result_ptr => return analyzeInstBitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?), | ||
| 34 | .block => return analyzeInstBlock(mod, scope, old_inst.castTag(.block).?), | ||
| 35 | .@"break" => return analyzeInstBreak(mod, scope, old_inst.castTag(.@"break").?), | ||
| 36 | .breakpoint => return analyzeInstBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?), | ||
| 37 | .breakvoid => return analyzeInstBreakVoid(mod, scope, old_inst.castTag(.breakvoid).?), | ||
| 38 | .call => return analyzeInstCall(mod, scope, old_inst.castTag(.call).?), | ||
| 39 | .coerce_result_block_ptr => return analyzeInstCoerceResultBlockPtr(mod, scope, old_inst.castTag(.coerce_result_block_ptr).?), | ||
| 40 | .coerce_result_ptr => return analyzeInstCoerceResultPtr(mod, scope, old_inst.castTag(.coerce_result_ptr).?), | ||
| 41 | .coerce_to_ptr_elem => return analyzeInstCoerceToPtrElem(mod, scope, old_inst.castTag(.coerce_to_ptr_elem).?), | ||
| 42 | .compileerror => return analyzeInstCompileError(mod, scope, old_inst.castTag(.compileerror).?), | ||
| 43 | .@"const" => return analyzeInstConst(mod, scope, old_inst.castTag(.@"const").?), | ||
| 44 | .dbg_stmt => return analyzeInstDbgStmt(mod, scope, old_inst.castTag(.dbg_stmt).?), | ||
| 45 | .declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?), | ||
| 46 | .declref_str => return analyzeInstDeclRefStr(mod, scope, old_inst.castTag(.declref_str).?), | ||
| 47 | .declval => return analyzeInstDeclVal(mod, scope, old_inst.castTag(.declval).?), | ||
| 48 | .declval_in_module => return analyzeInstDeclValInModule(mod, scope, old_inst.castTag(.declval_in_module).?), | ||
| 49 | .ensure_result_used => return analyzeInstEnsureResultUsed(mod, scope, old_inst.castTag(.ensure_result_used).?), | ||
| 50 | .ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?), | ||
| 51 | .ref => return analyzeInstRef(mod, scope, old_inst.castTag(.ref).?), | ||
| 52 | .ret_ptr => return analyzeInstRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?), | ||
| 53 | .ret_type => return analyzeInstRetType(mod, scope, old_inst.castTag(.ret_type).?), | ||
| 54 | .single_const_ptr_type => return analyzeInstSingleConstPtrType(mod, scope, old_inst.castTag(.single_const_ptr_type).?), | ||
| 55 | .single_mut_ptr_type => return analyzeInstSingleMutPtrType(mod, scope, old_inst.castTag(.single_mut_ptr_type).?), | ||
| 56 | .store => return analyzeInstStore(mod, scope, old_inst.castTag(.store).?), | ||
| 57 | .str => return analyzeInstStr(mod, scope, old_inst.castTag(.str).?), | ||
| 58 | .int => { | ||
| 59 | const big_int = old_inst.castTag(.int).?.positionals.int; | ||
| 60 | return mod.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int); | ||
| 61 | }, | ||
| 62 | .inttype => return analyzeInstIntType(mod, scope, old_inst.castTag(.inttype).?), | ||
| 63 | .param_type => return analyzeInstParamType(mod, scope, old_inst.castTag(.param_type).?), | ||
| 64 | .ptrtoint => return analyzeInstPtrToInt(mod, scope, old_inst.castTag(.ptrtoint).?), | ||
| 65 | .fieldptr => return analyzeInstFieldPtr(mod, scope, old_inst.castTag(.fieldptr).?), | ||
| 66 | .deref => return analyzeInstDeref(mod, scope, old_inst.castTag(.deref).?), | ||
| 67 | .as => return analyzeInstAs(mod, scope, old_inst.castTag(.as).?), | ||
| 68 | .@"asm" => return analyzeInstAsm(mod, scope, old_inst.castTag(.@"asm").?), | ||
| 69 | .@"unreachable" => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.@"unreachable").?), | ||
| 70 | .unreach_nocheck => return analyzeInstUnreachNoChk(mod, scope, old_inst.castTag(.unreach_nocheck).?), | ||
| 71 | .@"return" => return analyzeInstRet(mod, scope, old_inst.castTag(.@"return").?), | ||
| 72 | .returnvoid => return analyzeInstRetVoid(mod, scope, old_inst.castTag(.returnvoid).?), | ||
| 73 | .@"fn" => return analyzeInstFn(mod, scope, old_inst.castTag(.@"fn").?), | ||
| 74 | .@"export" => return analyzeInstExport(mod, scope, old_inst.castTag(.@"export").?), | ||
| 75 | .primitive => return analyzeInstPrimitive(mod, scope, old_inst.castTag(.primitive).?), | ||
| 76 | .fntype => return analyzeInstFnType(mod, scope, old_inst.castTag(.fntype).?), | ||
| 77 | .intcast => return analyzeInstIntCast(mod, scope, old_inst.castTag(.intcast).?), | ||
| 78 | .bitcast => return analyzeInstBitCast(mod, scope, old_inst.castTag(.bitcast).?), | ||
| 79 | .floatcast => return analyzeInstFloatCast(mod, scope, old_inst.castTag(.floatcast).?), | ||
| 80 | .elemptr => return analyzeInstElemPtr(mod, scope, old_inst.castTag(.elemptr).?), | ||
| 81 | .add => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.add).?), | ||
| 82 | .addwrap => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.addwrap).?), | ||
| 83 | .sub => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.sub).?), | ||
| 84 | .subwrap => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.subwrap).?), | ||
| 85 | .mul => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.mul).?), | ||
| 86 | .mulwrap => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.mulwrap).?), | ||
| 87 | .div => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.div).?), | ||
| 88 | .mod_rem => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.mod_rem).?), | ||
| 89 | .array_cat => return analyzeInstArrayCat(mod, scope, old_inst.castTag(.array_cat).?), | ||
| 90 | .array_mul => return analyzeInstArrayMul(mod, scope, old_inst.castTag(.array_mul).?), | ||
| 91 | .bitand => return analyzeInstBitwise(mod, scope, old_inst.castTag(.bitand).?), | ||
| 92 | .bitor => return analyzeInstBitwise(mod, scope, old_inst.castTag(.bitor).?), | ||
| 93 | .xor => return analyzeInstBitwise(mod, scope, old_inst.castTag(.xor).?), | ||
| 94 | .shl => return analyzeInstShl(mod, scope, old_inst.castTag(.shl).?), | ||
| 95 | .shr => return analyzeInstShr(mod, scope, old_inst.castTag(.shr).?), | ||
| 96 | .cmp_lt => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_lt).?, .lt), | ||
| 97 | .cmp_lte => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_lte).?, .lte), | ||
| 98 | .cmp_eq => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_eq).?, .eq), | ||
| 99 | .cmp_gte => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_gte).?, .gte), | ||
| 100 | .cmp_gt => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_gt).?, .gt), | ||
| 101 | .cmp_neq => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_neq).?, .neq), | ||
| 102 | .condbr => return analyzeInstCondBr(mod, scope, old_inst.castTag(.condbr).?), | ||
| 103 | .isnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnull).?, true), | ||
| 104 | .isnonnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnonnull).?, false), | ||
| 105 | .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?), | ||
| 106 | .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?), | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void { | ||
| 111 | for (body.instructions) |src_inst| { | ||
| 112 | src_inst.analyzed_inst = try analyzeInst(mod, scope, src_inst); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | pub fn analyzeBodyValueAsType(mod: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type { | ||
| 117 | try analyzeBody(mod, &block_scope.base, body); | ||
| 118 | for (block_scope.instructions.items) |inst| { | ||
| 119 | if (inst.castTag(.ret)) |ret| { | ||
| 120 | const val = try mod.resolveConstValue(&block_scope.base, ret.operand); | ||
| 121 | return val.toType(); | ||
| 122 | } else { | ||
| 123 | return mod.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{}); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | unreachable; | ||
| 127 | } | ||
| 128 | |||
| 129 | pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { | ||
| 130 | var decl_scope: Scope.DeclAnalysis = .{ | ||
| 131 | .decl = decl, | ||
| 132 | .arena = std.heap.ArenaAllocator.init(mod.gpa), | ||
| 133 | }; | ||
| 134 | errdefer decl_scope.arena.deinit(); | ||
| 135 | |||
| 136 | decl.analysis = .in_progress; | ||
| 137 | |||
| 138 | const typed_value = try analyzeConstInst(mod, &decl_scope.base, src_decl.inst); | ||
| 139 | const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); | ||
| 140 | |||
| 141 | var prev_type_has_bits = false; | ||
| 142 | var type_changed = true; | ||
| 143 | |||
| 144 | if (decl.typedValueManaged()) |tvm| { | ||
| 145 | prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); | ||
| 146 | type_changed = !tvm.typed_value.ty.eql(typed_value.ty); | ||
| 147 | |||
| 148 | tvm.deinit(mod.gpa); | ||
| 149 | } | ||
| 150 | |||
| 151 | arena_state.* = decl_scope.arena.state; | ||
| 152 | decl.typed_value = .{ | ||
| 153 | .most_recent = .{ | ||
| 154 | .typed_value = typed_value, | ||
| 155 | .arena = arena_state, | ||
| 156 | }, | ||
| 157 | }; | ||
| 158 | decl.analysis = .complete; | ||
| 159 | decl.generation = mod.generation; | ||
| 160 | if (typed_value.ty.hasCodeGenBits()) { | ||
| 161 | // We don't fully codegen the decl until later, but we do need to reserve a global | ||
| 162 | // offset table index for it. This allows us to codegen decls out of dependency order, | ||
| 163 | // increasing how many computations can be done in parallel. | ||
| 164 | try mod.bin_file.allocateDeclIndexes(decl); | ||
| 165 | try mod.work_queue.writeItem(.{ .codegen_decl = decl }); | ||
| 166 | } else if (prev_type_has_bits) { | ||
| 167 | mod.bin_file.freeDecl(decl); | ||
| 168 | } | ||
| 169 | |||
| 170 | return type_changed; | ||
| 171 | } | ||
| 172 | |||
| 173 | pub fn resolveZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { | ||
| 174 | const zir_module = mod.root_scope.cast(Scope.ZIRModule).?; | ||
| 175 | const entry = zir_module.contents.module.findDecl(src_decl.name).?; | ||
| 176 | return resolveZirDeclHavingIndex(mod, scope, src_decl, entry.index); | ||
| 177 | } | ||
| 178 | |||
| 179 | fn resolveZirDeclHavingIndex(mod: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl { | ||
| 180 | const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name); | ||
| 181 | const decl = mod.decl_table.get(name_hash).?; | ||
| 182 | decl.src_index = src_index; | ||
| 183 | try mod.ensureDeclAnalyzed(decl); | ||
| 184 | return decl; | ||
| 185 | } | ||
| 186 | |||
| 187 | /// Declares a dependency on the decl. | ||
| 188 | fn resolveCompleteZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { | ||
| 189 | const decl = try resolveZirDecl(mod, scope, src_decl); | ||
| 190 | switch (decl.analysis) { | ||
| 191 | .unreferenced => unreachable, | ||
| 192 | .in_progress => unreachable, | ||
| 193 | .outdated => unreachable, | ||
| 194 | |||
| 195 | .dependency_failure, | ||
| 196 | .sema_failure, | ||
| 197 | .sema_failure_retryable, | ||
| 198 | .codegen_failure, | ||
| 199 | .codegen_failure_retryable, | ||
| 200 | => return error.AnalysisFail, | ||
| 201 | |||
| 202 | .complete => {}, | ||
| 203 | } | ||
| 204 | return decl; | ||
| 205 | } | ||
| 206 | |||
| 207 | /// TODO Look into removing this function. The body is only needed for .zir files, not .zig files. | ||
| 208 | pub fn resolveInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { | ||
| 209 | if (old_inst.analyzed_inst) |inst| return inst; | ||
| 210 | |||
| 211 | // If this assert trips, the instruction that was referenced did not get properly | ||
| 212 | // analyzed before it was referenced. | ||
| 213 | const zir_module = scope.namespace().cast(Scope.ZIRModule).?; | ||
| 214 | const entry = if (old_inst.cast(zir.Inst.DeclVal)) |declval| blk: { | ||
| 215 | const decl_name = declval.positionals.name; | ||
| 216 | const entry = zir_module.contents.module.findDecl(decl_name) orelse | ||
| 217 | return mod.fail(scope, old_inst.src, "decl '{}' not found", .{decl_name}); | ||
| 218 | break :blk entry; | ||
| 219 | } else blk: { | ||
| 220 | // If this assert trips, the instruction that was referenced did not get | ||
| 221 | // properly analyzed by a previous instruction analysis before it was | ||
| 222 | // referenced by the current one. | ||
| 223 | break :blk zir_module.contents.module.findInstDecl(old_inst).?; | ||
| 224 | }; | ||
| 225 | const decl = try resolveCompleteZirDecl(mod, scope, entry.decl); | ||
| 226 | const decl_ref = try mod.analyzeDeclRef(scope, old_inst.src, decl); | ||
| 227 | // Note: it would be tempting here to store the result into old_inst.analyzed_inst field, | ||
| 228 | // but this would prevent the analyzeDeclRef from happening, which is needed to properly | ||
| 229 | // detect Decl dependencies and dependency failures on updates. | ||
| 230 | return mod.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); | ||
| 231 | } | ||
| 232 | |||
| 233 | fn resolveConstString(mod: *Module, scope: *Scope, old_inst: *zir.Inst) ![]u8 { | ||
| 234 | const new_inst = try resolveInst(mod, scope, old_inst); | ||
| 235 | const wanted_type = Type.initTag(.const_slice_u8); | ||
| 236 | const coerced_inst = try mod.coerce(scope, wanted_type, new_inst); | ||
| 237 | const val = try mod.resolveConstValue(scope, coerced_inst); | ||
| 238 | return val.toAllocatedBytes(scope.arena()); | ||
| 239 | } | ||
| 240 | |||
| 241 | fn resolveType(mod: *Module, scope: *Scope, old_inst: *zir.Inst) !Type { | ||
| 242 | const new_inst = try resolveInst(mod, scope, old_inst); | ||
| 243 | const wanted_type = Type.initTag(.@"type"); | ||
| 244 | const coerced_inst = try mod.coerce(scope, wanted_type, new_inst); | ||
| 245 | const val = try mod.resolveConstValue(scope, coerced_inst); | ||
| 246 | return val.toType(); | ||
| 247 | } | ||
| 248 | |||
| 249 | pub fn resolveInstConst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { | ||
| 250 | const new_inst = try resolveInst(mod, scope, old_inst); | ||
| 251 | const val = try mod.resolveConstValue(scope, new_inst); | ||
| 252 | return TypedValue{ | ||
| 253 | .ty = new_inst.ty, | ||
| 254 | .val = val, | ||
| 255 | }; | ||
| 256 | } | ||
| 257 | |||
| 258 | fn analyzeInstConst(mod: *Module, scope: *Scope, const_inst: *zir.Inst.Const) InnerError!*Inst { | ||
| 259 | // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions | ||
| 260 | // after analysis. | ||
| 261 | const typed_value_copy = try const_inst.positionals.typed_value.copy(scope.arena()); | ||
| 262 | return mod.constInst(scope, const_inst.base.src, typed_value_copy); | ||
| 263 | } | ||
| 264 | |||
| 265 | fn analyzeConstInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { | ||
| 266 | const new_inst = try analyzeInst(mod, scope, old_inst); | ||
| 267 | return TypedValue{ | ||
| 268 | .ty = new_inst.ty, | ||
| 269 | .val = try mod.resolveConstValue(scope, new_inst), | ||
| 270 | }; | ||
| 271 | } | ||
| 272 | |||
| 273 | fn analyzeInstCoerceResultBlockPtr( | ||
| 274 | mod: *Module, | ||
| 275 | scope: *Scope, | ||
| 276 | inst: *zir.Inst.CoerceResultBlockPtr, | ||
| 277 | ) InnerError!*Inst { | ||
| 278 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultBlockPtr", .{}); | ||
| 279 | } | ||
| 280 | |||
| 281 | fn analyzeInstBitCastLValue(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 282 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastLValue", .{}); | ||
| 283 | } | ||
| 284 | |||
| 285 | fn analyzeInstBitCastResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 286 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastResultPtr", .{}); | ||
| 287 | } | ||
| 288 | |||
| 289 | fn analyzeInstCoerceResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 290 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultPtr", .{}); | ||
| 291 | } | ||
| 292 | |||
| 293 | /// Equivalent to `as(ptr_child_type(typeof(ptr)), value)`. | ||
| 294 | fn analyzeInstCoerceToPtrElem(mod: *Module, scope: *Scope, inst: *zir.Inst.CoerceToPtrElem) InnerError!*Inst { | ||
| 295 | const ptr = try resolveInst(mod, scope, inst.positionals.ptr); | ||
| 296 | const operand = try resolveInst(mod, scope, inst.positionals.value); | ||
| 297 | return mod.coerce(scope, ptr.ty.elemType(), operand); | ||
| 298 | } | ||
| 299 | |||
| 300 | fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 301 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstRetPtr", .{}); | ||
| 302 | } | ||
| 303 | |||
| 304 | fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 305 | const operand = try resolveInst(mod, scope, inst.positionals.operand); | ||
| 306 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 307 | const ptr_type = try mod.singleConstPtrType(scope, inst.base.src, operand.ty); | ||
| 308 | return mod.addUnOp(b, inst.base.src, ptr_type, .ref, operand); | ||
| 309 | } | ||
| 310 | |||
| 311 | fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 312 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 313 | const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; | ||
| 314 | const ret_type = fn_ty.fnReturnType(); | ||
| 315 | return mod.constType(scope, inst.base.src, ret_type); | ||
| 316 | } | ||
| 317 | |||
| 318 | fn analyzeInstEnsureResultUsed(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 319 | const operand = try resolveInst(mod, scope, inst.positionals.operand); | ||
| 320 | switch (operand.ty.zigTypeTag()) { | ||
| 321 | .Void, .NoReturn => return mod.constVoid(scope, operand.src), | ||
| 322 | else => return mod.fail(scope, operand.src, "expression value is ignored", .{}), | ||
| 323 | } | ||
| 324 | } | ||
| 325 | |||
| 326 | fn analyzeInstEnsureResultNonError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 327 | const operand = try resolveInst(mod, scope, inst.positionals.operand); | ||
| 328 | switch (operand.ty.zigTypeTag()) { | ||
| 329 | .ErrorSet, .ErrorUnion => return mod.fail(scope, operand.src, "error is discarded", .{}), | ||
| 330 | else => return mod.constVoid(scope, operand.src), | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | fn analyzeInstAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 335 | const var_type = try resolveType(mod, scope, inst.positionals.operand); | ||
| 336 | const ptr_type = try mod.singleMutPtrType(scope, inst.base.src, var_type); | ||
| 337 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 338 | return mod.addNoOp(b, inst.base.src, ptr_type, .alloc); | ||
| 339 | } | ||
| 340 | |||
| 341 | fn analyzeInstAllocInferred(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 342 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{}); | ||
| 343 | } | ||
| 344 | |||
| 345 | fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 346 | const ptr = try resolveInst(mod, scope, inst.positionals.lhs); | ||
| 347 | const value = try resolveInst(mod, scope, inst.positionals.rhs); | ||
| 348 | return mod.storePtr(scope, inst.base.src, ptr, value); | ||
| 349 | } | ||
| 350 | |||
| 351 | fn analyzeInstParamType(mod: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerError!*Inst { | ||
| 352 | const fn_inst = try resolveInst(mod, scope, inst.positionals.func); | ||
| 353 | const arg_index = inst.positionals.arg_index; | ||
| 354 | |||
| 355 | const fn_ty: Type = switch (fn_inst.ty.zigTypeTag()) { | ||
| 356 | .Fn => fn_inst.ty, | ||
| 357 | .BoundFn => { | ||
| 358 | return mod.fail(scope, fn_inst.src, "TODO implement analyzeInstParamType for method call syntax", .{}); | ||
| 359 | }, | ||
| 360 | else => { | ||
| 361 | return mod.fail(scope, fn_inst.src, "expected function, found '{}'", .{fn_inst.ty}); | ||
| 362 | }, | ||
| 363 | }; | ||
| 364 | |||
| 365 | // TODO support C-style var args | ||
| 366 | const param_count = fn_ty.fnParamLen(); | ||
| 367 | if (arg_index >= param_count) { | ||
| 368 | return mod.fail(scope, inst.base.src, "arg index {} out of bounds; '{}' has {} arguments", .{ | ||
| 369 | arg_index, | ||
| 370 | fn_ty, | ||
| 371 | param_count, | ||
| 372 | }); | ||
| 373 | } | ||
| 374 | |||
| 375 | // TODO support generic functions | ||
| 376 | const param_type = fn_ty.fnParamType(arg_index); | ||
| 377 | return mod.constType(scope, inst.base.src, param_type); | ||
| 378 | } | ||
| 379 | |||
| 380 | fn analyzeInstStr(mod: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst { | ||
| 381 | // The bytes references memory inside the ZIR module, which can get deallocated | ||
| 382 | // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. | ||
| 383 | var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa); | ||
| 384 | const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes); | ||
| 385 | |||
| 386 | const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); | ||
| 387 | ty_payload.* = .{ .len = arena_bytes.len }; | ||
| 388 | |||
| 389 | const bytes_payload = try scope.arena().create(Value.Payload.Bytes); | ||
| 390 | bytes_payload.* = .{ .data = arena_bytes }; | ||
| 391 | |||
| 392 | const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{ | ||
| 393 | .ty = Type.initPayload(&ty_payload.base), | ||
| 394 | .val = Value.initPayload(&bytes_payload.base), | ||
| 395 | }); | ||
| 396 | return mod.analyzeDeclRef(scope, str_inst.base.src, new_decl); | ||
| 397 | } | ||
| 398 | |||
| 399 | fn analyzeInstExport(mod: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst { | ||
| 400 | const symbol_name = try resolveConstString(mod, scope, export_inst.positionals.symbol_name); | ||
| 401 | const exported_decl = mod.lookupDeclName(scope, export_inst.positionals.decl_name) orelse | ||
| 402 | return mod.fail(scope, export_inst.base.src, "decl '{}' not found", .{export_inst.positionals.decl_name}); | ||
| 403 | try mod.analyzeExport(scope, export_inst.base.src, symbol_name, exported_decl); | ||
| 404 | return mod.constVoid(scope, export_inst.base.src); | ||
| 405 | } | ||
| 406 | |||
| 407 | fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileError) InnerError!*Inst { | ||
| 408 | return mod.fail(scope, inst.base.src, "{}", .{inst.positionals.msg}); | ||
| 409 | } | ||
| 410 | |||
| 411 | fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 412 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 413 | const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; | ||
| 414 | const param_index = b.instructions.items.len; | ||
| 415 | const param_count = fn_ty.fnParamLen(); | ||
| 416 | if (param_index >= param_count) { | ||
| 417 | return mod.fail(scope, inst.base.src, "parameter index {} outside list of length {}", .{ | ||
| 418 | param_index, | ||
| 419 | param_count, | ||
| 420 | }); | ||
| 421 | } | ||
| 422 | const param_type = fn_ty.fnParamType(param_index); | ||
| 423 | return mod.addNoOp(b, inst.base.src, param_type, .arg); | ||
| 424 | } | ||
| 425 | |||
| 426 | fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst { | ||
| 427 | const parent_block = scope.cast(Scope.Block).?; | ||
| 428 | |||
| 429 | // Reserve space for a Block instruction so that generated Break instructions can | ||
| 430 | // point to it, even if it doesn't end up getting used because the code ends up being | ||
| 431 | // comptime evaluated. | ||
| 432 | const block_inst = try parent_block.arena.create(Inst.Block); | ||
| 433 | block_inst.* = .{ | ||
| 434 | .base = .{ | ||
| 435 | .tag = Inst.Block.base_tag, | ||
| 436 | .ty = undefined, // Set after analysis. | ||
| 437 | .src = inst.base.src, | ||
| 438 | }, | ||
| 439 | .body = undefined, | ||
| 440 | }; | ||
| 441 | |||
| 442 | var child_block: Scope.Block = .{ | ||
| 443 | .parent = parent_block, | ||
| 444 | .func = parent_block.func, | ||
| 445 | .decl = parent_block.decl, | ||
| 446 | .instructions = .{}, | ||
| 447 | .arena = parent_block.arena, | ||
| 448 | // TODO @as here is working around a miscompilation compiler bug :( | ||
| 449 | .label = @as(?Scope.Block.Label, Scope.Block.Label{ | ||
| 450 | .zir_block = inst, | ||
| 451 | .results = .{}, | ||
| 452 | .block_inst = block_inst, | ||
| 453 | }), | ||
| 454 | }; | ||
| 455 | const label = &child_block.label.?; | ||
| 456 | |||
| 457 | defer child_block.instructions.deinit(mod.gpa); | ||
| 458 | defer label.results.deinit(mod.gpa); | ||
| 459 | |||
| 460 | try analyzeBody(mod, &child_block.base, inst.positionals.body); | ||
| 461 | |||
| 462 | // Blocks must terminate with noreturn instruction. | ||
| 463 | assert(child_block.instructions.items.len != 0); | ||
| 464 | assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn()); | ||
| 465 | |||
| 466 | // Need to set the type and emit the Block instruction. This allows machine code generation | ||
| 467 | // to emit a jump instruction to after the block when it encounters the break. | ||
| 468 | try parent_block.instructions.append(mod.gpa, &block_inst.base); | ||
| 469 | block_inst.base.ty = try mod.resolvePeerTypes(scope, label.results.items); | ||
| 470 | block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; | ||
| 471 | return &block_inst.base; | ||
| 472 | } | ||
| 473 | |||
| 474 | fn analyzeInstBreakpoint(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 475 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 476 | return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .breakpoint); | ||
| 477 | } | ||
| 478 | |||
| 479 | fn analyzeInstBreak(mod: *Module, scope: *Scope, inst: *zir.Inst.Break) InnerError!*Inst { | ||
| 480 | const operand = try resolveInst(mod, scope, inst.positionals.operand); | ||
| 481 | const block = inst.positionals.block; | ||
| 482 | return analyzeBreak(mod, scope, inst.base.src, block, operand); | ||
| 483 | } | ||
| 484 | |||
| 485 | fn analyzeInstBreakVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) InnerError!*Inst { | ||
| 486 | const block = inst.positionals.block; | ||
| 487 | const void_inst = try mod.constVoid(scope, inst.base.src); | ||
| 488 | return analyzeBreak(mod, scope, inst.base.src, block, void_inst); | ||
| 489 | } | ||
| 490 | |||
| 491 | fn analyzeInstDbgStmt(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 492 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 493 | return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .dbg_stmt); | ||
| 494 | } | ||
| 495 | |||
| 496 | fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst { | ||
| 497 | const decl_name = try resolveConstString(mod, scope, inst.positionals.name); | ||
| 498 | return mod.analyzeDeclRefByName(scope, inst.base.src, decl_name); | ||
| 499 | } | ||
| 500 | |||
| 501 | fn analyzeInstDeclRef(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst { | ||
| 502 | return mod.analyzeDeclRefByName(scope, inst.base.src, inst.positionals.name); | ||
| 503 | } | ||
| 504 | |||
| 505 | fn analyzeInstDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst { | ||
| 506 | const decl = try analyzeDeclVal(mod, scope, inst); | ||
| 507 | const ptr = try mod.analyzeDeclRef(scope, inst.base.src, decl); | ||
| 508 | return mod.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); | ||
| 509 | } | ||
| 510 | |||
| 511 | fn analyzeInstDeclValInModule(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst { | ||
| 512 | const decl = inst.positionals.decl; | ||
| 513 | const ptr = try mod.analyzeDeclRef(scope, inst.base.src, decl); | ||
| 514 | return mod.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); | ||
| 515 | } | ||
| 516 | |||
| 517 | fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { | ||
| 518 | const func = try resolveInst(mod, scope, inst.positionals.func); | ||
| 519 | if (func.ty.zigTypeTag() != .Fn) | ||
| 520 | return mod.fail(scope, inst.positionals.func.src, "type '{}' not a function", .{func.ty}); | ||
| 521 | |||
| 522 | const cc = func.ty.fnCallingConvention(); | ||
| 523 | if (cc == .Naked) { | ||
| 524 | // TODO add error note: declared here | ||
| 525 | return mod.fail( | ||
| 526 | scope, | ||
| 527 | inst.positionals.func.src, | ||
| 528 | "unable to call function with naked calling convention", | ||
| 529 | .{}, | ||
| 530 | ); | ||
| 531 | } | ||
| 532 | const call_params_len = inst.positionals.args.len; | ||
| 533 | const fn_params_len = func.ty.fnParamLen(); | ||
| 534 | if (func.ty.fnIsVarArgs()) { | ||
| 535 | if (call_params_len < fn_params_len) { | ||
| 536 | // TODO add error note: declared here | ||
| 537 | return mod.fail( | ||
| 538 | scope, | ||
| 539 | inst.positionals.func.src, | ||
| 540 | "expected at least {} arguments, found {}", | ||
| 541 | .{ fn_params_len, call_params_len }, | ||
| 542 | ); | ||
| 543 | } | ||
| 544 | return mod.fail(scope, inst.base.src, "TODO implement support for calling var args functions", .{}); | ||
| 545 | } else if (fn_params_len != call_params_len) { | ||
| 546 | // TODO add error note: declared here | ||
| 547 | return mod.fail( | ||
| 548 | scope, | ||
| 549 | inst.positionals.func.src, | ||
| 550 | "expected {} arguments, found {}", | ||
| 551 | .{ fn_params_len, call_params_len }, | ||
| 552 | ); | ||
| 553 | } | ||
| 554 | |||
| 555 | if (inst.kw_args.modifier == .compile_time) { | ||
| 556 | return mod.fail(scope, inst.base.src, "TODO implement comptime function calls", .{}); | ||
| 557 | } | ||
| 558 | if (inst.kw_args.modifier != .auto) { | ||
| 559 | return mod.fail(scope, inst.base.src, "TODO implement call with modifier {}", .{inst.kw_args.modifier}); | ||
| 560 | } | ||
| 561 | |||
| 562 | // TODO handle function calls of generic functions | ||
| 563 | |||
| 564 | const fn_param_types = try mod.gpa.alloc(Type, fn_params_len); | ||
| 565 | defer mod.gpa.free(fn_param_types); | ||
| 566 | func.ty.fnParamTypes(fn_param_types); | ||
| 567 | |||
| 568 | const casted_args = try scope.arena().alloc(*Inst, fn_params_len); | ||
| 569 | for (inst.positionals.args) |src_arg, i| { | ||
| 570 | const uncasted_arg = try resolveInst(mod, scope, src_arg); | ||
| 571 | casted_args[i] = try mod.coerce(scope, fn_param_types[i], uncasted_arg); | ||
| 572 | } | ||
| 573 | |||
| 574 | const ret_type = func.ty.fnReturnType(); | ||
| 575 | |||
| 576 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 577 | return mod.addCall(b, inst.base.src, ret_type, func, casted_args); | ||
| 578 | } | ||
| 579 | |||
| 580 | fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst { | ||
| 581 | const fn_type = try resolveType(mod, scope, fn_inst.positionals.fn_type); | ||
| 582 | const fn_zir = blk: { | ||
| 583 | var fn_arena = std.heap.ArenaAllocator.init(mod.gpa); | ||
| 584 | errdefer fn_arena.deinit(); | ||
| 585 | |||
| 586 | const fn_zir = try scope.arena().create(Module.Fn.ZIR); | ||
| 587 | fn_zir.* = .{ | ||
| 588 | .body = .{ | ||
| 589 | .instructions = fn_inst.positionals.body.instructions, | ||
| 590 | }, | ||
| 591 | .arena = fn_arena.state, | ||
| 592 | }; | ||
| 593 | break :blk fn_zir; | ||
| 594 | }; | ||
| 595 | const new_func = try scope.arena().create(Module.Fn); | ||
| 596 | new_func.* = .{ | ||
| 597 | .analysis = .{ .queued = fn_zir }, | ||
| 598 | .owner_decl = scope.decl().?, | ||
| 599 | }; | ||
| 600 | const fn_payload = try scope.arena().create(Value.Payload.Function); | ||
| 601 | fn_payload.* = .{ .func = new_func }; | ||
| 602 | return mod.constInst(scope, fn_inst.base.src, .{ | ||
| 603 | .ty = fn_type, | ||
| 604 | .val = Value.initPayload(&fn_payload.base), | ||
| 605 | }); | ||
| 606 | } | ||
| 607 | |||
| 608 | fn analyzeInstIntType(mod: *Module, scope: *Scope, inttype: *zir.Inst.IntType) InnerError!*Inst { | ||
| 609 | return mod.fail(scope, inttype.base.src, "TODO implement inttype", .{}); | ||
| 610 | } | ||
| 611 | |||
| 612 | fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { | ||
| 613 | const return_type = try resolveType(mod, scope, fntype.positionals.return_type); | ||
| 614 | |||
| 615 | // Hot path for some common function types. | ||
| 616 | if (fntype.positionals.param_types.len == 0) { | ||
| 617 | if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Unspecified) { | ||
| 618 | return mod.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args)); | ||
| 619 | } | ||
| 620 | |||
| 621 | if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .Unspecified) { | ||
| 622 | return mod.constType(scope, fntype.base.src, Type.initTag(.fn_void_no_args)); | ||
| 623 | } | ||
| 624 | |||
| 625 | if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Naked) { | ||
| 626 | return mod.constType(scope, fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args)); | ||
| 627 | } | ||
| 628 | |||
| 629 | if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .C) { | ||
| 630 | return mod.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args)); | ||
| 631 | } | ||
| 632 | } | ||
| 633 | |||
| 634 | const arena = scope.arena(); | ||
| 635 | const param_types = try arena.alloc(Type, fntype.positionals.param_types.len); | ||
| 636 | for (fntype.positionals.param_types) |param_type, i| { | ||
| 637 | param_types[i] = try resolveType(mod, scope, param_type); | ||
| 638 | } | ||
| 639 | |||
| 640 | const payload = try arena.create(Type.Payload.Function); | ||
| 641 | payload.* = .{ | ||
| 642 | .cc = fntype.kw_args.cc, | ||
| 643 | .return_type = return_type, | ||
| 644 | .param_types = param_types, | ||
| 645 | }; | ||
| 646 | return mod.constType(scope, fntype.base.src, Type.initPayload(&payload.base)); | ||
| 647 | } | ||
| 648 | |||
| 649 | fn analyzeInstPrimitive(mod: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst { | ||
| 650 | return mod.constInst(scope, primitive.base.src, primitive.positionals.tag.toTypedValue()); | ||
| 651 | } | ||
| 652 | |||
| 653 | fn analyzeInstAs(mod: *Module, scope: *Scope, as: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 654 | const dest_type = try resolveType(mod, scope, as.positionals.lhs); | ||
| 655 | const new_inst = try resolveInst(mod, scope, as.positionals.rhs); | ||
| 656 | return mod.coerce(scope, dest_type, new_inst); | ||
| 657 | } | ||
| 658 | |||
| 659 | fn analyzeInstPtrToInt(mod: *Module, scope: *Scope, ptrtoint: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 660 | const ptr = try resolveInst(mod, scope, ptrtoint.positionals.operand); | ||
| 661 | if (ptr.ty.zigTypeTag() != .Pointer) { | ||
| 662 | return mod.fail(scope, ptrtoint.positionals.operand.src, "expected pointer, found '{}'", .{ptr.ty}); | ||
| 663 | } | ||
| 664 | // TODO handle known-pointer-address | ||
| 665 | const b = try mod.requireRuntimeBlock(scope, ptrtoint.base.src); | ||
| 666 | const ty = Type.initTag(.usize); | ||
| 667 | return mod.addUnOp(b, ptrtoint.base.src, ty, .ptrtoint, ptr); | ||
| 668 | } | ||
| 669 | |||
| 670 | fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr) InnerError!*Inst { | ||
| 671 | const object_ptr = try resolveInst(mod, scope, fieldptr.positionals.object_ptr); | ||
| 672 | const field_name = try resolveConstString(mod, scope, fieldptr.positionals.field_name); | ||
| 673 | |||
| 674 | const elem_ty = switch (object_ptr.ty.zigTypeTag()) { | ||
| 675 | .Pointer => object_ptr.ty.elemType(), | ||
| 676 | else => return mod.fail(scope, fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}), | ||
| 677 | }; | ||
| 678 | switch (elem_ty.zigTypeTag()) { | ||
| 679 | .Array => { | ||
| 680 | if (mem.eql(u8, field_name, "len")) { | ||
| 681 | const len_payload = try scope.arena().create(Value.Payload.Int_u64); | ||
| 682 | len_payload.* = .{ .int = elem_ty.arrayLen() }; | ||
| 683 | |||
| 684 | const ref_payload = try scope.arena().create(Value.Payload.RefVal); | ||
| 685 | ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) }; | ||
| 686 | |||
| 687 | return mod.constInst(scope, fieldptr.base.src, .{ | ||
| 688 | .ty = Type.initTag(.single_const_pointer_to_comptime_int), | ||
| 689 | .val = Value.initPayload(&ref_payload.base), | ||
| 690 | }); | ||
| 691 | } else { | ||
| 692 | return mod.fail( | ||
| 693 | scope, | ||
| 694 | fieldptr.positionals.field_name.src, | ||
| 695 | "no member named '{}' in '{}'", | ||
| 696 | .{ field_name, elem_ty }, | ||
| 697 | ); | ||
| 698 | } | ||
| 699 | }, | ||
| 700 | else => return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}), | ||
| 701 | } | ||
| 702 | } | ||
| 703 | |||
| 704 | fn analyzeInstIntCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 705 | const dest_type = try resolveType(mod, scope, inst.positionals.lhs); | ||
| 706 | const operand = try resolveInst(mod, scope, inst.positionals.rhs); | ||
| 707 | |||
| 708 | const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { | ||
| 709 | .ComptimeInt => true, | ||
| 710 | .Int => false, | ||
| 711 | else => return mod.fail( | ||
| 712 | scope, | ||
| 713 | inst.positionals.lhs.src, | ||
| 714 | "expected integer type, found '{}'", | ||
| 715 | .{ | ||
| 716 | dest_type, | ||
| 717 | }, | ||
| 718 | ), | ||
| 719 | }; | ||
| 720 | |||
| 721 | switch (operand.ty.zigTypeTag()) { | ||
| 722 | .ComptimeInt, .Int => {}, | ||
| 723 | else => return mod.fail( | ||
| 724 | scope, | ||
| 725 | inst.positionals.rhs.src, | ||
| 726 | "expected integer type, found '{}'", | ||
| 727 | .{operand.ty}, | ||
| 728 | ), | ||
| 729 | } | ||
| 730 | |||
| 731 | if (operand.value() != null) { | ||
| 732 | return mod.coerce(scope, dest_type, operand); | ||
| 733 | } else if (dest_is_comptime_int) { | ||
| 734 | return mod.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_int'", .{}); | ||
| 735 | } | ||
| 736 | |||
| 737 | return mod.fail(scope, inst.base.src, "TODO implement analyze widen or shorten int", .{}); | ||
| 738 | } | ||
| 739 | |||
| 740 | fn analyzeInstBitCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 741 | const dest_type = try resolveType(mod, scope, inst.positionals.lhs); | ||
| 742 | const operand = try resolveInst(mod, scope, inst.positionals.rhs); | ||
| 743 | return mod.bitcast(scope, dest_type, operand); | ||
| 744 | } | ||
| 745 | |||
| 746 | fn analyzeInstFloatCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 747 | const dest_type = try resolveType(mod, scope, inst.positionals.lhs); | ||
| 748 | const operand = try resolveInst(mod, scope, inst.positionals.rhs); | ||
| 749 | |||
| 750 | const dest_is_comptime_float = switch (dest_type.zigTypeTag()) { | ||
| 751 | .ComptimeFloat => true, | ||
| 752 | .Float => false, | ||
| 753 | else => return mod.fail( | ||
| 754 | scope, | ||
| 755 | inst.positionals.lhs.src, | ||
| 756 | "expected float type, found '{}'", | ||
| 757 | .{ | ||
| 758 | dest_type, | ||
| 759 | }, | ||
| 760 | ), | ||
| 761 | }; | ||
| 762 | |||
| 763 | switch (operand.ty.zigTypeTag()) { | ||
| 764 | .ComptimeFloat, .Float, .ComptimeInt => {}, | ||
| 765 | else => return mod.fail( | ||
| 766 | scope, | ||
| 767 | inst.positionals.rhs.src, | ||
| 768 | "expected float type, found '{}'", | ||
| 769 | .{operand.ty}, | ||
| 770 | ), | ||
| 771 | } | ||
| 772 | |||
| 773 | if (operand.value() != null) { | ||
| 774 | return mod.coerce(scope, dest_type, operand); | ||
| 775 | } else if (dest_is_comptime_float) { | ||
| 776 | return mod.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_float'", .{}); | ||
| 777 | } | ||
| 778 | |||
| 779 | return mod.fail(scope, inst.base.src, "TODO implement analyze widen or shorten float", .{}); | ||
| 780 | } | ||
| 781 | |||
| 782 | fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) InnerError!*Inst { | ||
| 783 | const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr); | ||
| 784 | const uncasted_index = try resolveInst(mod, scope, inst.positionals.index); | ||
| 785 | const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index); | ||
| 786 | |||
| 787 | if (array_ptr.ty.isSinglePointer() and array_ptr.ty.elemType().zigTypeTag() == .Array) { | ||
| 788 | if (array_ptr.value()) |array_ptr_val| { | ||
| 789 | if (elem_index.value()) |index_val| { | ||
| 790 | // Both array pointer and index are compile-time known. | ||
| 791 | const index_u64 = index_val.toUnsignedInt(); | ||
| 792 | // @intCast here because it would have been impossible to construct a value that | ||
| 793 | // required a larger index. | ||
| 794 | const elem_ptr = try array_ptr_val.elemPtr(scope.arena(), @intCast(usize, index_u64)); | ||
| 795 | |||
| 796 | const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer); | ||
| 797 | type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() }; | ||
| 798 | |||
| 799 | return mod.constInst(scope, inst.base.src, .{ | ||
| 800 | .ty = Type.initPayload(&type_payload.base), | ||
| 801 | .val = elem_ptr, | ||
| 802 | }); | ||
| 803 | } | ||
| 804 | } | ||
| 805 | } | ||
| 806 | |||
| 807 | return mod.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{}); | ||
| 808 | } | ||
| 809 | |||
| 810 | fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 811 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{}); | ||
| 812 | } | ||
| 813 | |||
| 814 | fn analyzeInstShr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 815 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShr", .{}); | ||
| 816 | } | ||
| 817 | |||
| 818 | fn analyzeInstBitwise(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 819 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitwise", .{}); | ||
| 820 | } | ||
| 821 | |||
| 822 | fn analyzeInstArrayCat(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 823 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstArrayCat", .{}); | ||
| 824 | } | ||
| 825 | |||
| 826 | fn analyzeInstArrayMul(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 827 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstArrayMul", .{}); | ||
| 828 | } | ||
| 829 | |||
| 830 | fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | ||
| 831 | const tracy = trace(@src()); | ||
| 832 | defer tracy.end(); | ||
| 833 | |||
| 834 | const lhs = try resolveInst(mod, scope, inst.positionals.lhs); | ||
| 835 | const rhs = try resolveInst(mod, scope, inst.positionals.rhs); | ||
| 836 | |||
| 837 | const instructions = &[_]*Inst{ lhs, rhs }; | ||
| 838 | const resolved_type = try mod.resolvePeerTypes(scope, instructions); | ||
| 839 | const casted_lhs = try mod.coerce(scope, resolved_type, lhs); | ||
| 840 | const casted_rhs = try mod.coerce(scope, resolved_type, rhs); | ||
| 841 | |||
| 842 | const scalar_type = if (resolved_type.zigTypeTag() == .Vector) | ||
| 843 | resolved_type.elemType() | ||
| 844 | else | ||
| 845 | resolved_type; | ||
| 846 | |||
| 847 | const scalar_tag = scalar_type.zigTypeTag(); | ||
| 848 | |||
| 849 | if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) { | ||
| 850 | if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) { | ||
| 851 | return mod.fail(scope, inst.base.src, "vector length mismatch: {} and {}", .{ | ||
| 852 | lhs.ty.arrayLen(), | ||
| 853 | rhs.ty.arrayLen(), | ||
| 854 | }); | ||
| 855 | } | ||
| 856 | return mod.fail(scope, inst.base.src, "TODO implement support for vectors in analyzeInstBinOp", .{}); | ||
| 857 | } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) { | ||
| 858 | return mod.fail(scope, inst.base.src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{ | ||
| 859 | lhs.ty, | ||
| 860 | rhs.ty, | ||
| 861 | }); | ||
| 862 | } | ||
| 863 | |||
| 864 | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; | ||
| 865 | const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; | ||
| 866 | |||
| 867 | if (!is_int and !(is_float and floatOpAllowed(inst.base.tag))) { | ||
| 868 | return mod.fail(scope, inst.base.src, "invalid operands to binary expression: '{}' and '{}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) }); | ||
| 869 | } | ||
| 870 | |||
| 871 | if (casted_lhs.value()) |lhs_val| { | ||
| 872 | if (casted_rhs.value()) |rhs_val| { | ||
| 873 | return analyzeInstComptimeOp(mod, scope, scalar_type, inst, lhs_val, rhs_val); | ||
| 874 | } | ||
| 875 | } | ||
| 876 | |||
| 877 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 878 | const ir_tag = switch (inst.base.tag) { | ||
| 879 | .add => Inst.Tag.add, | ||
| 880 | .sub => Inst.Tag.sub, | ||
| 881 | else => return mod.fail(scope, inst.base.src, "TODO implement arithmetic for operand '{}''", .{@tagName(inst.base.tag)}), | ||
| 882 | }; | ||
| 883 | |||
| 884 | return mod.addBinOp(b, inst.base.src, scalar_type, ir_tag, casted_lhs, casted_rhs); | ||
| 885 | } | ||
| 886 | |||
| 887 | /// Analyzes operands that are known at comptime | ||
| 888 | fn analyzeInstComptimeOp(mod: *Module, scope: *Scope, res_type: Type, inst: *zir.Inst.BinOp, lhs_val: Value, rhs_val: Value) InnerError!*Inst { | ||
| 889 | // incase rhs is 0, simply return lhs without doing any calculations | ||
| 890 | // TODO Once division is implemented we should throw an error when dividing by 0. | ||
| 891 | if (rhs_val.compareWithZero(.eq)) { | ||
| 892 | return mod.constInst(scope, inst.base.src, .{ | ||
| 893 | .ty = res_type, | ||
| 894 | .val = lhs_val, | ||
| 895 | }); | ||
| 896 | } | ||
| 897 | const is_int = res_type.isInt() or res_type.zigTypeTag() == .ComptimeInt; | ||
| 898 | |||
| 899 | const value = try switch (inst.base.tag) { | ||
| 900 | .add => blk: { | ||
| 901 | const val = if (is_int) | ||
| 902 | Module.intAdd(scope.arena(), lhs_val, rhs_val) | ||
| 903 | else | ||
| 904 | mod.floatAdd(scope, res_type, inst.base.src, lhs_val, rhs_val); | ||
| 905 | break :blk val; | ||
| 906 | }, | ||
| 907 | .sub => blk: { | ||
| 908 | const val = if (is_int) | ||
| 909 | Module.intSub(scope.arena(), lhs_val, rhs_val) | ||
| 910 | else | ||
| 911 | mod.floatSub(scope, res_type, inst.base.src, lhs_val, rhs_val); | ||
| 912 | break :blk val; | ||
| 913 | }, | ||
| 914 | else => return mod.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{}'", .{@tagName(inst.base.tag)}), | ||
| 915 | }; | ||
| 916 | |||
| 917 | return mod.constInst(scope, inst.base.src, .{ | ||
| 918 | .ty = res_type, | ||
| 919 | .val = value, | ||
| 920 | }); | ||
| 921 | } | ||
| 922 | |||
| 923 | fn analyzeInstDeref(mod: *Module, scope: *Scope, deref: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 924 | const ptr = try resolveInst(mod, scope, deref.positionals.operand); | ||
| 925 | return mod.analyzeDeref(scope, deref.base.src, ptr, deref.positionals.operand.src); | ||
| 926 | } | ||
| 927 | |||
| 928 | fn analyzeInstAsm(mod: *Module, scope: *Scope, assembly: *zir.Inst.Asm) InnerError!*Inst { | ||
| 929 | const return_type = try resolveType(mod, scope, assembly.positionals.return_type); | ||
| 930 | const asm_source = try resolveConstString(mod, scope, assembly.positionals.asm_source); | ||
| 931 | const output = if (assembly.kw_args.output) |o| try resolveConstString(mod, scope, o) else null; | ||
| 932 | |||
| 933 | const inputs = try scope.arena().alloc([]const u8, assembly.kw_args.inputs.len); | ||
| 934 | const clobbers = try scope.arena().alloc([]const u8, assembly.kw_args.clobbers.len); | ||
| 935 | const args = try scope.arena().alloc(*Inst, assembly.kw_args.args.len); | ||
| 936 | |||
| 937 | for (inputs) |*elem, i| { | ||
| 938 | elem.* = try resolveConstString(mod, scope, assembly.kw_args.inputs[i]); | ||
| 939 | } | ||
| 940 | for (clobbers) |*elem, i| { | ||
| 941 | elem.* = try resolveConstString(mod, scope, assembly.kw_args.clobbers[i]); | ||
| 942 | } | ||
| 943 | for (args) |*elem, i| { | ||
| 944 | const arg = try resolveInst(mod, scope, assembly.kw_args.args[i]); | ||
| 945 | elem.* = try mod.coerce(scope, Type.initTag(.usize), arg); | ||
| 946 | } | ||
| 947 | |||
| 948 | const b = try mod.requireRuntimeBlock(scope, assembly.base.src); | ||
| 949 | const inst = try b.arena.create(Inst.Assembly); | ||
| 950 | inst.* = .{ | ||
| 951 | .base = .{ | ||
| 952 | .tag = .assembly, | ||
| 953 | .ty = return_type, | ||
| 954 | .src = assembly.base.src, | ||
| 955 | }, | ||
| 956 | .asm_source = asm_source, | ||
| 957 | .is_volatile = assembly.kw_args.@"volatile", | ||
| 958 | .output = output, | ||
| 959 | .inputs = inputs, | ||
| 960 | .clobbers = clobbers, | ||
| 961 | .args = args, | ||
| 962 | }; | ||
| 963 | try b.instructions.append(mod.gpa, &inst.base); | ||
| 964 | return &inst.base; | ||
| 965 | } | ||
| 966 | |||
| 967 | fn analyzeInstCmp( | ||
| 968 | mod: *Module, | ||
| 969 | scope: *Scope, | ||
| 970 | inst: *zir.Inst.BinOp, | ||
| 971 | op: std.math.CompareOperator, | ||
| 972 | ) InnerError!*Inst { | ||
| 973 | const lhs = try resolveInst(mod, scope, inst.positionals.lhs); | ||
| 974 | const rhs = try resolveInst(mod, scope, inst.positionals.rhs); | ||
| 975 | |||
| 976 | const is_equality_cmp = switch (op) { | ||
| 977 | .eq, .neq => true, | ||
| 978 | else => false, | ||
| 979 | }; | ||
| 980 | const lhs_ty_tag = lhs.ty.zigTypeTag(); | ||
| 981 | const rhs_ty_tag = rhs.ty.zigTypeTag(); | ||
| 982 | if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) { | ||
| 983 | // null == null, null != null | ||
| 984 | return mod.constBool(scope, inst.base.src, op == .eq); | ||
| 985 | } else if (is_equality_cmp and | ||
| 986 | ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or | ||
| 987 | rhs_ty_tag == .Null and lhs_ty_tag == .Optional)) | ||
| 988 | { | ||
| 989 | // comparing null with optionals | ||
| 990 | const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs; | ||
| 991 | if (opt_operand.value()) |opt_val| { | ||
| 992 | const is_null = opt_val.isNull(); | ||
| 993 | return mod.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null); | ||
| 994 | } | ||
| 995 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 996 | const inst_tag: Inst.Tag = switch (op) { | ||
| 997 | .eq => .isnull, | ||
| 998 | .neq => .isnonnull, | ||
| 999 | else => unreachable, | ||
| 1000 | }; | ||
| 1001 | return mod.addUnOp(b, inst.base.src, Type.initTag(.bool), inst_tag, opt_operand); | ||
| 1002 | } else if (is_equality_cmp and | ||
| 1003 | ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr()))) | ||
| 1004 | { | ||
| 1005 | return mod.fail(scope, inst.base.src, "TODO implement C pointer cmp", .{}); | ||
| 1006 | } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) { | ||
| 1007 | const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty; | ||
| 1008 | return mod.fail(scope, inst.base.src, "comparison of '{}' with null", .{non_null_type}); | ||
| 1009 | } else if (is_equality_cmp and | ||
| 1010 | ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or | ||
| 1011 | (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union))) | ||
| 1012 | { | ||
| 1013 | return mod.fail(scope, inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); | ||
| 1014 | } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { | ||
| 1015 | if (!is_equality_cmp) { | ||
| 1016 | return mod.fail(scope, inst.base.src, "{} operator not allowed for errors", .{@tagName(op)}); | ||
| 1017 | } | ||
| 1018 | return mod.fail(scope, inst.base.src, "TODO implement equality comparison between errors", .{}); | ||
| 1019 | } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) { | ||
| 1020 | // This operation allows any combination of integer and float types, regardless of the | ||
| 1021 | // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for | ||
| 1022 | // numeric types. | ||
| 1023 | return mod.cmpNumeric(scope, inst.base.src, lhs, rhs, op); | ||
| 1024 | } | ||
| 1025 | return mod.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{}); | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | fn analyzeInstTypeOf(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 1029 | const operand = try resolveInst(mod, scope, inst.positionals.operand); | ||
| 1030 | return mod.constType(scope, inst.base.src, operand.ty); | ||
| 1031 | } | ||
| 1032 | |||
| 1033 | fn analyzeInstBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 1034 | const uncasted_operand = try resolveInst(mod, scope, inst.positionals.operand); | ||
| 1035 | const bool_type = Type.initTag(.bool); | ||
| 1036 | const operand = try mod.coerce(scope, bool_type, uncasted_operand); | ||
| 1037 | if (try mod.resolveDefinedValue(scope, operand)) |val| { | ||
| 1038 | return mod.constBool(scope, inst.base.src, !val.toBool()); | ||
| 1039 | } | ||
| 1040 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 1041 | return mod.addUnOp(b, inst.base.src, bool_type, .not, operand); | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | fn analyzeInstIsNonNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst { | ||
| 1045 | const operand = try resolveInst(mod, scope, inst.positionals.operand); | ||
| 1046 | return mod.analyzeIsNull(scope, inst.base.src, operand, invert_logic); | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst { | ||
| 1050 | const uncasted_cond = try resolveInst(mod, scope, inst.positionals.condition); | ||
| 1051 | const cond = try mod.coerce(scope, Type.initTag(.bool), uncasted_cond); | ||
| 1052 | |||
| 1053 | if (try mod.resolveDefinedValue(scope, cond)) |cond_val| { | ||
| 1054 | const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body; | ||
| 1055 | try analyzeBody(mod, scope, body.*); | ||
| 1056 | return mod.constVoid(scope, inst.base.src); | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 1060 | |||
| 1061 | var true_block: Scope.Block = .{ | ||
| 1062 | .parent = parent_block, | ||
| 1063 | .func = parent_block.func, | ||
| 1064 | .decl = parent_block.decl, | ||
| 1065 | .instructions = .{}, | ||
| 1066 | .arena = parent_block.arena, | ||
| 1067 | }; | ||
| 1068 | defer true_block.instructions.deinit(mod.gpa); | ||
| 1069 | try analyzeBody(mod, &true_block.base, inst.positionals.then_body); | ||
| 1070 | |||
| 1071 | var false_block: Scope.Block = .{ | ||
| 1072 | .parent = parent_block, | ||
| 1073 | .func = parent_block.func, | ||
| 1074 | .decl = parent_block.decl, | ||
| 1075 | .instructions = .{}, | ||
| 1076 | .arena = parent_block.arena, | ||
| 1077 | }; | ||
| 1078 | defer false_block.instructions.deinit(mod.gpa); | ||
| 1079 | try analyzeBody(mod, &false_block.base, inst.positionals.else_body); | ||
| 1080 | |||
| 1081 | const then_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) }; | ||
| 1082 | const else_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) }; | ||
| 1083 | return mod.addCondBr(parent_block, inst.base.src, cond, then_body, else_body); | ||
| 1084 | } | ||
| 1085 | |||
| 1086 | fn analyzeInstUnreachNoChk(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 1087 | return mod.analyzeUnreach(scope, unreach.base.src); | ||
| 1088 | } | ||
| 1089 | |||
| 1090 | fn analyzeInstUnreachable(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 1091 | const b = try mod.requireRuntimeBlock(scope, unreach.base.src); | ||
| 1092 | // TODO Add compile error for @optimizeFor occurring too late in a scope. | ||
| 1093 | if (mod.wantSafety(scope)) { | ||
| 1094 | // TODO Once we have a panic function to call, call it here instead of this. | ||
| 1095 | _ = try mod.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint); | ||
| 1096 | } | ||
| 1097 | return mod.analyzeUnreach(scope, unreach.base.src); | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 1101 | const operand = try resolveInst(mod, scope, inst.positionals.operand); | ||
| 1102 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 1103 | return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand); | ||
| 1104 | } | ||
| 1105 | |||
| 1106 | fn analyzeInstRetVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | ||
| 1107 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | ||
| 1108 | return mod.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid); | ||
| 1109 | } | ||
| 1110 | |||
| 1111 | fn floatOpAllowed(tag: zir.Inst.Tag) bool { | ||
| 1112 | // extend this swich as additional operators are implemented | ||
| 1113 | return switch (tag) { | ||
| 1114 | .add, .sub => true, | ||
| 1115 | else => false, | ||
| 1116 | }; | ||
| 1117 | } | ||
| 1118 | |||
| 1119 | fn analyzeBreak( | ||
| 1120 | mod: *Module, | ||
| 1121 | scope: *Scope, | ||
| 1122 | src: usize, | ||
| 1123 | zir_block: *zir.Inst.Block, | ||
| 1124 | operand: *Inst, | ||
| 1125 | ) InnerError!*Inst { | ||
| 1126 | var opt_block = scope.cast(Scope.Block); | ||
| 1127 | while (opt_block) |block| { | ||
| 1128 | if (block.label) |*label| { | ||
| 1129 | if (label.zir_block == zir_block) { | ||
| 1130 | try label.results.append(mod.gpa, operand); | ||
| 1131 | const b = try mod.requireRuntimeBlock(scope, src); | ||
| 1132 | return mod.addBr(b, src, label.block_inst, operand); | ||
| 1133 | } | ||
| 1134 | } | ||
| 1135 | opt_block = block.parent; | ||
| 1136 | } else unreachable; | ||
| 1137 | } | ||
| 1138 | |||
| 1139 | fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl { | ||
| 1140 | const decl_name = inst.positionals.name; | ||
| 1141 | const zir_module = scope.namespace().cast(Scope.ZIRModule).?; | ||
| 1142 | const src_decl = zir_module.contents.module.findDecl(decl_name) orelse | ||
| 1143 | return mod.fail(scope, inst.base.src, "use of undeclared identifier '{}'", .{decl_name}); | ||
| 1144 | |||
| 1145 | const decl = try resolveCompleteZirDecl(mod, scope, src_decl.decl); | ||
| 1146 | |||
| 1147 | return decl; | ||
| 1148 | } | ||
| 1149 | |||
| 1150 | fn analyzeInstSingleConstPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 1151 | const elem_type = try resolveType(mod, scope, inst.positionals.operand); | ||
| 1152 | const ty = try mod.singleConstPtrType(scope, inst.base.src, elem_type); | ||
| 1153 | return mod.constType(scope, inst.base.src, ty); | ||
| 1154 | } | ||
| 1155 | |||
| 1156 | fn analyzeInstSingleMutPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { | ||
| 1157 | const elem_type = try resolveType(mod, scope, inst.positionals.operand); | ||
| 1158 | const ty = try mod.singleMutPtrType(scope, inst.base.src, elem_type); | ||
| 1159 | return mod.constType(scope, inst.base.src, ty); | ||
| 1160 | } | ||
src/all_types.hpp+1-1| ... | @@ -4110,7 +4110,7 @@ struct IrInstSrcCheckSwitchProngs { | ... | @@ -4110,7 +4110,7 @@ struct IrInstSrcCheckSwitchProngs { |
| 4110 | IrInstSrc *target_value; | 4110 | IrInstSrc *target_value; |
| 4111 | IrInstSrcCheckSwitchProngsRange *ranges; | 4111 | IrInstSrcCheckSwitchProngsRange *ranges; |
| 4112 | size_t range_count; | 4112 | size_t range_count; |
| 4113 | bool have_else_prong; | 4113 | AstNode* else_prong; |
| 4114 | bool have_underscore_prong; | 4114 | bool have_underscore_prong; |
| 4115 | }; | 4115 | }; |
| 4116 | 4116 |
src/analyze.cpp+14| ... | @@ -1490,6 +1490,20 @@ static OnePossibleValue type_val_resolve_has_one_possible_value(CodeGen *g, ZigV | ... | @@ -1490,6 +1490,20 @@ static OnePossibleValue type_val_resolve_has_one_possible_value(CodeGen *g, ZigV |
| 1490 | } | 1490 | } |
| 1491 | 1491 | ||
| 1492 | ZigType *analyze_type_expr(CodeGen *g, Scope *scope, AstNode *node) { | 1492 | ZigType *analyze_type_expr(CodeGen *g, Scope *scope, AstNode *node) { |
| 1493 | Error err; | ||
| 1494 | // Hot path for simple identifiers, to avoid unnecessary memory allocations. | ||
| 1495 | if (node->type == NodeTypeSymbol) { | ||
| 1496 | Buf *variable_name = node->data.symbol_expr.symbol; | ||
| 1497 | if (buf_eql_str(variable_name, "_")) | ||
| 1498 | goto abort_hot_path; | ||
| 1499 | ZigType *primitive_type; | ||
| 1500 | if ((err = get_primitive_type(g, variable_name, &primitive_type))) { | ||
| 1501 | goto abort_hot_path; | ||
| 1502 | } else { | ||
| 1503 | return primitive_type; | ||
| 1504 | } | ||
| 1505 | abort_hot_path:; | ||
| 1506 | } | ||
| 1493 | ZigValue *result = analyze_const_value(g, scope, node, g->builtin_types.entry_type, | 1507 | ZigValue *result = analyze_const_value(g, scope, node, g->builtin_types.entry_type, |
| 1494 | nullptr, UndefBad); | 1508 | nullptr, UndefBad); |
| 1495 | if (type_is_invalid(result->type)) | 1509 | if (type_is_invalid(result->type)) |
src/ir.cpp+58-16| ... | @@ -4300,14 +4300,14 @@ static IrInstGen *ir_build_err_to_int_gen(IrAnalyze *ira, Scope *scope, AstNode | ... | @@ -4300,14 +4300,14 @@ static IrInstGen *ir_build_err_to_int_gen(IrAnalyze *ira, Scope *scope, AstNode |
| 4300 | 4300 | ||
| 4301 | static IrInstSrc *ir_build_check_switch_prongs(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, | 4301 | static IrInstSrc *ir_build_check_switch_prongs(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, |
| 4302 | IrInstSrc *target_value, IrInstSrcCheckSwitchProngsRange *ranges, size_t range_count, | 4302 | IrInstSrc *target_value, IrInstSrcCheckSwitchProngsRange *ranges, size_t range_count, |
| 4303 | bool have_else_prong, bool have_underscore_prong) | 4303 | AstNode* else_prong, bool have_underscore_prong) |
| 4304 | { | 4304 | { |
| 4305 | IrInstSrcCheckSwitchProngs *instruction = ir_build_instruction<IrInstSrcCheckSwitchProngs>( | 4305 | IrInstSrcCheckSwitchProngs *instruction = ir_build_instruction<IrInstSrcCheckSwitchProngs>( |
| 4306 | irb, scope, source_node); | 4306 | irb, scope, source_node); |
| 4307 | instruction->target_value = target_value; | 4307 | instruction->target_value = target_value; |
| 4308 | instruction->ranges = ranges; | 4308 | instruction->ranges = ranges; |
| 4309 | instruction->range_count = range_count; | 4309 | instruction->range_count = range_count; |
| 4310 | instruction->have_else_prong = have_else_prong; | 4310 | instruction->else_prong = else_prong; |
| 4311 | instruction->have_underscore_prong = have_underscore_prong; | 4311 | instruction->have_underscore_prong = have_underscore_prong; |
| 4312 | 4312 | ||
| 4313 | ir_ref_instruction(target_value, irb->current_basic_block); | 4313 | 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 | ... | @@ -9347,7 +9347,7 @@ static IrInstSrc *ir_gen_switch_expr(IrBuilderSrc *irb, Scope *scope, AstNode *n |
| 9347 | } | 9347 | } |
| 9348 | 9348 | ||
| 9349 | IrInstSrc *switch_prongs_void = ir_build_check_switch_prongs(irb, scope, node, target_value, | 9349 | IrInstSrc *switch_prongs_void = ir_build_check_switch_prongs(irb, scope, node, target_value, |
| 9350 | check_ranges.items, check_ranges.length, else_prong != nullptr, underscore_prong != nullptr); | 9350 | check_ranges.items, check_ranges.length, else_prong, underscore_prong != nullptr); |
| 9351 | 9351 | ||
| 9352 | IrInstSrc *br_instruction; | 9352 | IrInstSrc *br_instruction; |
| 9353 | if (cases.length == 0) { | 9353 | if (cases.length == 0) { |
| ... | @@ -20604,17 +20604,25 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, | ... | @@ -20604,17 +20604,25 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, |
| 20604 | return ira->codegen->invalid_inst_gen; | 20604 | return ira->codegen->invalid_inst_gen; |
| 20605 | } | 20605 | } |
| 20606 | 20606 | ||
| 20607 | ZigType *expected_return_type = result_loc->value->type->data.pointer.child_type; | ||
| 20608 | |||
| 20607 | IrInstGen *dummy_value = ir_const(ira, source_instr, return_type); | 20609 | IrInstGen *dummy_value = ir_const(ira, source_instr, return_type); |
| 20608 | dummy_value->value->special = ConstValSpecialRuntime; | 20610 | dummy_value->value->special = ConstValSpecialRuntime; |
| 20609 | IrInstGen *dummy_result = ir_implicit_cast2(ira, source_instr, | 20611 | IrInstGen *dummy_result = ir_implicit_cast2(ira, source_instr, |
| 20610 | dummy_value, result_loc->value->type->data.pointer.child_type); | 20612 | dummy_value, expected_return_type); |
| 20611 | if (type_is_invalid(dummy_result->value->type)) | 20613 | if (type_is_invalid(dummy_result->value->type)) { |
| 20614 | if ((return_type->id == ZigTypeIdErrorUnion || return_type->id == ZigTypeIdErrorSet) && | ||
| 20615 | expected_return_type->id != ZigTypeIdErrorUnion && expected_return_type->id != ZigTypeIdErrorSet) | ||
| 20616 | { | ||
| 20617 | add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg, | ||
| 20618 | ira->explicit_return_type_source_node, buf_create_from_str("function cannot return an error")); | ||
| 20619 | } | ||
| 20612 | return ira->codegen->invalid_inst_gen; | 20620 | return ira->codegen->invalid_inst_gen; |
| 20613 | ZigType *res_child_type = result_loc->value->type->data.pointer.child_type; | ||
| 20614 | if (res_child_type == ira->codegen->builtin_types.entry_anytype) { | ||
| 20615 | res_child_type = return_type; | ||
| 20616 | } | 20621 | } |
| 20617 | if (!handle_is_ptr(ira->codegen, res_child_type)) { | 20622 | if (expected_return_type == ira->codegen->builtin_types.entry_anytype) { |
| 20623 | expected_return_type = return_type; | ||
| 20624 | } | ||
| 20625 | if (!handle_is_ptr(ira->codegen, expected_return_type)) { | ||
| 20618 | ir_reset_result(call_result_loc); | 20626 | ir_reset_result(call_result_loc); |
| 20619 | result_loc = nullptr; | 20627 | result_loc = nullptr; |
| 20620 | } | 20628 | } |
| ... | @@ -28828,7 +28836,7 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, | ... | @@ -28828,7 +28836,7 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, |
| 28828 | buf_ptr(enum_field->name))); | 28836 | buf_ptr(enum_field->name))); |
| 28829 | } | 28837 | } |
| 28830 | } | 28838 | } |
| 28831 | } else if (!instruction->have_else_prong) { | 28839 | } else if (instruction->else_prong == nullptr) { |
| 28832 | if (switch_type->data.enumeration.non_exhaustive) { | 28840 | if (switch_type->data.enumeration.non_exhaustive) { |
| 28833 | ir_add_error(ira, &instruction->base.base, | 28841 | ir_add_error(ira, &instruction->base.base, |
| 28834 | buf_sprintf("switch on non-exhaustive enum must include `else` or `_` prong")); | 28842 | 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, | ... | @@ -28843,6 +28851,10 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, |
| 28843 | buf_ptr(enum_field->name))); | 28851 | buf_ptr(enum_field->name))); |
| 28844 | } | 28852 | } |
| 28845 | } | 28853 | } |
| 28854 | } else if(!switch_type->data.enumeration.non_exhaustive && switch_type->data.enumeration.src_field_count == instruction->range_count) { | ||
| 28855 | ir_add_error_node(ira, instruction->else_prong, | ||
| 28856 | buf_sprintf("unreachable else prong, all cases already handled")); | ||
| 28857 | return ira->codegen->invalid_inst_gen; | ||
| 28846 | } | 28858 | } |
| 28847 | } else if (switch_type->id == ZigTypeIdErrorSet) { | 28859 | } else if (switch_type->id == ZigTypeIdErrorSet) { |
| 28848 | if (!resolve_inferred_error_set(ira->codegen, switch_type, target_value->base.source_node)) { | 28860 | 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, | ... | @@ -28889,7 +28901,7 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, |
| 28889 | } | 28901 | } |
| 28890 | field_prev_uses[start_index] = start_value->base.source_node; | 28902 | field_prev_uses[start_index] = start_value->base.source_node; |
| 28891 | } | 28903 | } |
| 28892 | if (!instruction->have_else_prong) { | 28904 | if (instruction->else_prong == nullptr) { |
| 28893 | if (type_is_global_error_set(switch_type)) { | 28905 | if (type_is_global_error_set(switch_type)) { |
| 28894 | ir_add_error(ira, &instruction->base.base, | 28906 | ir_add_error(ira, &instruction->base.base, |
| 28895 | buf_sprintf("else prong required when switching on type 'anyerror'")); | 28907 | buf_sprintf("else prong required when switching on type 'anyerror'")); |
| ... | @@ -28951,16 +28963,20 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, | ... | @@ -28951,16 +28963,20 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, |
| 28951 | return ira->codegen->invalid_inst_gen; | 28963 | return ira->codegen->invalid_inst_gen; |
| 28952 | } | 28964 | } |
| 28953 | } | 28965 | } |
| 28954 | if (!instruction->have_else_prong) { | 28966 | |
| 28955 | BigInt min_val; | 28967 | BigInt min_val; |
| 28956 | eval_min_max_value_int(ira->codegen, switch_type, &min_val, false); | 28968 | eval_min_max_value_int(ira->codegen, switch_type, &min_val, false); |
| 28957 | BigInt max_val; | 28969 | BigInt max_val; |
| 28958 | eval_min_max_value_int(ira->codegen, switch_type, &max_val, true); | 28970 | eval_min_max_value_int(ira->codegen, switch_type, &max_val, true); |
| 28959 | if (!rangeset_spans(&rs, &min_val, &max_val)) { | 28971 | bool handles_all_cases = rangeset_spans(&rs, &min_val, &max_val); |
| 28972 | if (!handles_all_cases && instruction->else_prong == nullptr) { | ||
| 28960 | ir_add_error(ira, &instruction->base.base, buf_sprintf("switch must handle all possibilities")); | 28973 | ir_add_error(ira, &instruction->base.base, buf_sprintf("switch must handle all possibilities")); |
| 28961 | return ira->codegen->invalid_inst_gen; | 28974 | return ira->codegen->invalid_inst_gen; |
| 28975 | } else if(handles_all_cases && instruction->else_prong != nullptr) { | ||
| 28976 | ir_add_error_node(ira, instruction->else_prong, | ||
| 28977 | buf_sprintf("unreachable else prong, all cases already handled")); | ||
| 28978 | return ira->codegen->invalid_inst_gen; | ||
| 28962 | } | 28979 | } |
| 28963 | } | ||
| 28964 | } else if (switch_type->id == ZigTypeIdBool) { | 28980 | } else if (switch_type->id == ZigTypeIdBool) { |
| 28965 | int seenTrue = 0; | 28981 | int seenTrue = 0; |
| 28966 | int seenFalse = 0; | 28982 | int seenFalse = 0; |
| ... | @@ -28990,11 +29006,17 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, | ... | @@ -28990,11 +29006,17 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, |
| 28990 | return ira->codegen->invalid_inst_gen; | 29006 | return ira->codegen->invalid_inst_gen; |
| 28991 | } | 29007 | } |
| 28992 | } | 29008 | } |
| 28993 | if (((seenTrue < 1) || (seenFalse < 1)) && !instruction->have_else_prong) { | 29009 | if (((seenTrue < 1) || (seenFalse < 1)) && instruction->else_prong == nullptr) { |
| 28994 | ir_add_error(ira, &instruction->base.base, buf_sprintf("switch must handle all possibilities")); | 29010 | ir_add_error(ira, &instruction->base.base, buf_sprintf("switch must handle all possibilities")); |
| 28995 | return ira->codegen->invalid_inst_gen; | 29011 | return ira->codegen->invalid_inst_gen; |
| 28996 | } | 29012 | } |
| 28997 | } else if (!instruction->have_else_prong) { | 29013 | |
| 29014 | if(seenTrue == 1 && seenFalse == 1 && instruction->else_prong != nullptr) { | ||
| 29015 | ir_add_error_node(ira, instruction->else_prong, | ||
| 29016 | buf_sprintf("unreachable else prong, all cases already handled")); | ||
| 29017 | return ira->codegen->invalid_inst_gen; | ||
| 29018 | } | ||
| 29019 | } else if (instruction->else_prong == nullptr) { | ||
| 28998 | ir_add_error(ira, &instruction->base.base, | 29020 | ir_add_error(ira, &instruction->base.base, |
| 28999 | buf_sprintf("else prong required when switching on type '%s'", buf_ptr(&switch_type->name))); | 29021 | buf_sprintf("else prong required when switching on type '%s'", buf_ptr(&switch_type->name))); |
| 29000 | return ira->codegen->invalid_inst_gen; | 29022 | return ira->codegen->invalid_inst_gen; |
| ... | @@ -29077,6 +29099,19 @@ static IrInstGen *ir_align_cast(IrAnalyze *ira, IrInstGen *target, uint32_t alig | ... | @@ -29077,6 +29099,19 @@ static IrInstGen *ir_align_cast(IrAnalyze *ira, IrInstGen *target, uint32_t alig |
| 29077 | ZigType *result_type; | 29099 | ZigType *result_type; |
| 29078 | uint32_t old_align_bytes; | 29100 | uint32_t old_align_bytes; |
| 29079 | 29101 | ||
| 29102 | ZigType *actual_ptr = target_type; | ||
| 29103 | if (actual_ptr->id == ZigTypeIdOptional) { | ||
| 29104 | actual_ptr = actual_ptr->data.maybe.child_type; | ||
| 29105 | } else if (is_slice(actual_ptr)) { | ||
| 29106 | actual_ptr = actual_ptr->data.structure.fields[slice_ptr_index]->type_entry; | ||
| 29107 | } | ||
| 29108 | |||
| 29109 | if (safety_check_on && !type_has_bits(ira->codegen, actual_ptr)) { | ||
| 29110 | ir_add_error(ira, &target->base, | ||
| 29111 | buf_sprintf("cannot adjust alignment of zero sized type '%s'", buf_ptr(&target_type->name))); | ||
| 29112 | return ira->codegen->invalid_inst_gen; | ||
| 29113 | } | ||
| 29114 | |||
| 29080 | if (target_type->id == ZigTypeIdPointer) { | 29115 | if (target_type->id == ZigTypeIdPointer) { |
| 29081 | result_type = adjust_ptr_align(ira->codegen, target_type, align_bytes); | 29116 | result_type = adjust_ptr_align(ira->codegen, target_type, align_bytes); |
| 29082 | if ((err = resolve_ptr_align(ira, target_type, &old_align_bytes))) | 29117 | 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 | ... | @@ -30894,6 +30929,13 @@ static IrInstGen *ir_analyze_instruction_end_expr(IrAnalyze *ira, IrInstSrcEndEx |
| 30894 | IrInstGen *store_ptr = ir_analyze_store_ptr(ira, &instruction->base.base, result_loc, value, | 30929 | IrInstGen *store_ptr = ir_analyze_store_ptr(ira, &instruction->base.base, result_loc, value, |
| 30895 | instruction->result_loc->allow_write_through_const); | 30930 | instruction->result_loc->allow_write_through_const); |
| 30896 | if (type_is_invalid(store_ptr->value->type)) { | 30931 | if (type_is_invalid(store_ptr->value->type)) { |
| 30932 | if (instruction->result_loc->id == ResultLocIdReturn && | ||
| 30933 | (value->value->type->id == ZigTypeIdErrorUnion || value->value->type->id == ZigTypeIdErrorSet) && | ||
| 30934 | ira->explicit_return_type->id != ZigTypeIdErrorUnion && ira->explicit_return_type->id != ZigTypeIdErrorSet) | ||
| 30935 | { | ||
| 30936 | add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg, | ||
| 30937 | ira->explicit_return_type_source_node, buf_create_from_str("function cannot return an error")); | ||
| 30938 | } | ||
| 30897 | return ira->codegen->invalid_inst_gen; | 30939 | return ira->codegen->invalid_inst_gen; |
| 30898 | } | 30940 | } |
| 30899 | } | 30941 | } |
src/ir_print.cpp+1-1| ... | @@ -2175,7 +2175,7 @@ static void ir_print_check_switch_prongs(IrPrintSrc *irp, IrInstSrcCheckSwitchPr | ... | @@ -2175,7 +2175,7 @@ static void ir_print_check_switch_prongs(IrPrintSrc *irp, IrInstSrcCheckSwitchPr |
| 2175 | fprintf(irp->f, "..."); | 2175 | fprintf(irp->f, "..."); |
| 2176 | ir_print_other_inst_src(irp, instruction->ranges[i].end); | 2176 | ir_print_other_inst_src(irp, instruction->ranges[i].end); |
| 2177 | } | 2177 | } |
| 2178 | const char *have_else_str = instruction->have_else_prong ? "yes" : "no"; | 2178 | const char *have_else_str = instruction->else_prong != nullptr ? "yes" : "no"; |
| 2179 | fprintf(irp->f, ")else:%s", have_else_str); | 2179 | fprintf(irp->f, ")else:%s", have_else_str); |
| 2180 | } | 2180 | } |
| 2181 | 2181 |
src/link.cpp+4| ... | @@ -2107,6 +2107,10 @@ static void construct_linker_job_wasm(LinkJob *lj) { | ... | @@ -2107,6 +2107,10 @@ static void construct_linker_job_wasm(LinkJob *lj) { |
| 2107 | lj->args.append("-z"); | 2107 | lj->args.append("-z"); |
| 2108 | lj->args.append(buf_ptr(buf_sprintf("stack-size=%" ZIG_PRI_usize, stack_size))); | 2108 | lj->args.append(buf_ptr(buf_sprintf("stack-size=%" ZIG_PRI_usize, stack_size))); |
| 2109 | 2109 | ||
| 2110 | // put stack before globals so that stack overflow results in segfault immediately before corrupting globals | ||
| 2111 | // see https://github.com/ziglang/zig/issues/4496 | ||
| 2112 | lj->args.append("--stack-first"); | ||
| 2113 | |||
| 2110 | if (g->out_type != OutTypeExe) { | 2114 | if (g->out_type != OutTypeExe) { |
| 2111 | lj->args.append("--no-entry"); // So lld doesn't look for _start. | 2115 | lj->args.append("--no-entry"); // So lld doesn't look for _start. |
| 2112 | 2116 |
src/zig_clang.cpp+8| ... | @@ -2823,6 +2823,14 @@ struct ZigClangSourceLocation ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc( | ... | @@ -2823,6 +2823,14 @@ struct ZigClangSourceLocation ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc( |
| 2823 | return bitcast(casted->getBeginLoc()); | 2823 | return bitcast(casted->getBeginLoc()); |
| 2824 | } | 2824 | } |
| 2825 | 2825 | ||
| 2826 | |||
| 2827 | enum ZigClangUnaryExprOrTypeTrait_Kind ZigClangUnaryExprOrTypeTraitExpr_getKind( | ||
| 2828 | const struct ZigClangUnaryExprOrTypeTraitExpr *self) | ||
| 2829 | { | ||
| 2830 | auto casted = reinterpret_cast<const clang::UnaryExprOrTypeTraitExpr *>(self); | ||
| 2831 | return (ZigClangUnaryExprOrTypeTrait_Kind)casted->getKind(); | ||
| 2832 | } | ||
| 2833 | |||
| 2826 | const struct ZigClangStmt *ZigClangDoStmt_getBody(const struct ZigClangDoStmt *self) { | 2834 | const struct ZigClangStmt *ZigClangDoStmt_getBody(const struct ZigClangDoStmt *self) { |
| 2827 | auto casted = reinterpret_cast<const clang::DoStmt *>(self); | 2835 | auto casted = reinterpret_cast<const clang::DoStmt *>(self); |
| 2828 | return reinterpret_cast<const struct ZigClangStmt *>(casted->getBody()); | 2836 | return reinterpret_cast<const struct ZigClangStmt *>(casted->getBody()); |
src/zig_clang.h+9| ... | @@ -901,6 +901,14 @@ enum ZigClangExpr_ConstExprUsage { | ... | @@ -901,6 +901,14 @@ enum ZigClangExpr_ConstExprUsage { |
| 901 | ZigClangExpr_EvaluateForMangling, | 901 | ZigClangExpr_EvaluateForMangling, |
| 902 | }; | 902 | }; |
| 903 | 903 | ||
| 904 | enum ZigClangUnaryExprOrTypeTrait_Kind { | ||
| 905 | ZigClangUnaryExprOrTypeTrait_KindSizeOf, | ||
| 906 | ZigClangUnaryExprOrTypeTrait_KindAlignOf, | ||
| 907 | ZigClangUnaryExprOrTypeTrait_KindVecStep, | ||
| 908 | ZigClangUnaryExprOrTypeTrait_KindOpenMPRequiredSimdAlign, | ||
| 909 | ZigClangUnaryExprOrTypeTrait_KindPreferredAlignOf, | ||
| 910 | }; | ||
| 911 | |||
| 904 | ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangSourceManager_getSpellingLoc(const struct ZigClangSourceManager *, | 912 | ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangSourceManager_getSpellingLoc(const struct ZigClangSourceManager *, |
| 905 | struct ZigClangSourceLocation Loc); | 913 | struct ZigClangSourceLocation Loc); |
| 906 | ZIG_EXTERN_C const char *ZigClangSourceManager_getFilename(const struct ZigClangSourceManager *, | 914 | ZIG_EXTERN_C const char *ZigClangSourceManager_getFilename(const struct ZigClangSourceManager *, |
| ... | @@ -1190,6 +1198,7 @@ ZIG_EXTERN_C const struct ZigClangExpr *ZigClangArraySubscriptExpr_getIdx(const | ... | @@ -1190,6 +1198,7 @@ ZIG_EXTERN_C const struct ZigClangExpr *ZigClangArraySubscriptExpr_getIdx(const |
| 1190 | 1198 | ||
| 1191 | ZIG_EXTERN_C struct ZigClangQualType ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(const struct ZigClangUnaryExprOrTypeTraitExpr *); | 1199 | ZIG_EXTERN_C struct ZigClangQualType ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(const struct ZigClangUnaryExprOrTypeTraitExpr *); |
| 1192 | ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(const struct ZigClangUnaryExprOrTypeTraitExpr *); | 1200 | ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(const struct ZigClangUnaryExprOrTypeTraitExpr *); |
| 1201 | ZIG_EXTERN_C enum ZigClangUnaryExprOrTypeTrait_Kind ZigClangUnaryExprOrTypeTraitExpr_getKind(const struct ZigClangUnaryExprOrTypeTraitExpr *); | ||
| 1193 | 1202 | ||
| 1194 | ZIG_EXTERN_C const struct ZigClangStmt *ZigClangDoStmt_getBody(const struct ZigClangDoStmt *); | 1203 | ZIG_EXTERN_C const struct ZigClangStmt *ZigClangDoStmt_getBody(const struct ZigClangDoStmt *); |
| 1195 | ZIG_EXTERN_C const struct ZigClangExpr *ZigClangDoStmt_getCond(const struct ZigClangDoStmt *); | 1204 | ZIG_EXTERN_C const struct ZigClangExpr *ZigClangDoStmt_getCond(const struct ZigClangDoStmt *); |
test/compile_errors.zig+144| ... | @@ -2,6 +2,32 @@ const tests = @import("tests.zig"); | ... | @@ -2,6 +2,32 @@ const tests = @import("tests.zig"); |
| 2 | const std = @import("std"); | 2 | const std = @import("std"); |
| 3 | 3 | ||
| 4 | pub fn addCases(cases: *tests.CompileErrorContext) void { | 4 | pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 5 | cases.addTest("@alignCast of zero sized types", | ||
| 6 | \\export fn foo() void { | ||
| 7 | \\ const a: *void = undefined; | ||
| 8 | \\ _ = @alignCast(2, a); | ||
| 9 | \\} | ||
| 10 | \\export fn bar() void { | ||
| 11 | \\ const a: ?*void = undefined; | ||
| 12 | \\ _ = @alignCast(2, a); | ||
| 13 | \\} | ||
| 14 | \\export fn baz() void { | ||
| 15 | \\ const a: []void = undefined; | ||
| 16 | \\ _ = @alignCast(2, a); | ||
| 17 | \\} | ||
| 18 | \\export fn qux() void { | ||
| 19 | \\ const a = struct { | ||
| 20 | \\ fn a(comptime b: u32) void {} | ||
| 21 | \\ }.a; | ||
| 22 | \\ _ = @alignCast(2, a); | ||
| 23 | \\} | ||
| 24 | , &[_][]const u8{ | ||
| 25 | "tmp.zig:3:23: error: cannot adjust alignment of zero sized type '*void'", | ||
| 26 | "tmp.zig:7:23: error: cannot adjust alignment of zero sized type '?*void'", | ||
| 27 | "tmp.zig:11:23: error: cannot adjust alignment of zero sized type '[]void'", | ||
| 28 | "tmp.zig:17:23: error: cannot adjust alignment of zero sized type 'fn(u32) anytype'", | ||
| 29 | }); | ||
| 30 | |||
| 5 | cases.addTest("invalid pointer with @Type", | 31 | cases.addTest("invalid pointer with @Type", |
| 6 | \\export fn entry() void { | 32 | \\export fn entry() void { |
| 7 | \\ _ = @Type(.{ .Pointer = .{ | 33 | \\ _ = @Type(.{ .Pointer = .{ |
| ... | @@ -18,6 +44,28 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { | ... | @@ -18,6 +44,28 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 18 | "tmp.zig:2:16: error: sentinels are only allowed on slices and unknown-length pointers", | 44 | "tmp.zig:2:16: error: sentinels are only allowed on slices and unknown-length pointers", |
| 19 | }); | 45 | }); |
| 20 | 46 | ||
| 47 | cases.addTest("helpful return type error message", | ||
| 48 | \\export fn foo() u32 { | ||
| 49 | \\ return error.Ohno; | ||
| 50 | \\} | ||
| 51 | \\fn bar() !u32 { | ||
| 52 | \\ return error.Ohno; | ||
| 53 | \\} | ||
| 54 | \\export fn baz() void { | ||
| 55 | \\ try bar(); | ||
| 56 | \\} | ||
| 57 | \\export fn quux() u32 { | ||
| 58 | \\ return bar(); | ||
| 59 | \\} | ||
| 60 | , &[_][]const u8{ | ||
| 61 | "tmp.zig:2:17: error: expected type 'u32', found 'error{Ohno}'", | ||
| 62 | "tmp.zig:1:17: note: function cannot return an error", | ||
| 63 | "tmp.zig:8:5: error: expected type 'void', found '@TypeOf(bar).ReturnType.ErrorSet'", | ||
| 64 | "tmp.zig:7:17: note: function cannot return an error", | ||
| 65 | "tmp.zig:11:15: error: expected type 'u32', found '@TypeOf(bar).ReturnType.ErrorSet!u32'", | ||
| 66 | "tmp.zig:10:18: note: function cannot return an error", | ||
| 67 | }); | ||
| 68 | |||
| 21 | cases.addTest("int/float conversion to comptime_int/float", | 69 | cases.addTest("int/float conversion to comptime_int/float", |
| 22 | \\export fn foo() void { | 70 | \\export fn foo() void { |
| 23 | \\ var a: f32 = 2; | 71 | \\ var a: f32 = 2; |
| ... | @@ -509,6 +557,102 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { | ... | @@ -509,6 +557,102 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 509 | "tmp.zig:12:5: error: switch on non-exhaustive enum must include `else` or `_` prong", | 557 | "tmp.zig:12:5: error: switch on non-exhaustive enum must include `else` or `_` prong", |
| 510 | }); | 558 | }); |
| 511 | 559 | ||
| 560 | cases.add("switch expression - unreachable else prong (bool)", | ||
| 561 | \\fn foo(x: bool) void { | ||
| 562 | \\ switch (x) { | ||
| 563 | \\ true => {}, | ||
| 564 | \\ false => {}, | ||
| 565 | \\ else => {}, | ||
| 566 | \\ } | ||
| 567 | \\} | ||
| 568 | \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); } | ||
| 569 | , &[_][]const u8{ | ||
| 570 | "tmp.zig:5:9: error: unreachable else prong, all cases already handled", | ||
| 571 | }); | ||
| 572 | |||
| 573 | cases.add("switch expression - unreachable else prong (u1)", | ||
| 574 | \\fn foo(x: u1) void { | ||
| 575 | \\ switch (x) { | ||
| 576 | \\ 0 => {}, | ||
| 577 | \\ 1 => {}, | ||
| 578 | \\ else => {}, | ||
| 579 | \\ } | ||
| 580 | \\} | ||
| 581 | \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); } | ||
| 582 | , &[_][]const u8{ | ||
| 583 | "tmp.zig:5:9: error: unreachable else prong, all cases already handled", | ||
| 584 | }); | ||
| 585 | |||
| 586 | cases.add("switch expression - unreachable else prong (u2)", | ||
| 587 | \\fn foo(x: u2) void { | ||
| 588 | \\ switch (x) { | ||
| 589 | \\ 0 => {}, | ||
| 590 | \\ 1 => {}, | ||
| 591 | \\ 2 => {}, | ||
| 592 | \\ 3 => {}, | ||
| 593 | \\ else => {}, | ||
| 594 | \\ } | ||
| 595 | \\} | ||
| 596 | \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); } | ||
| 597 | , &[_][]const u8{ | ||
| 598 | "tmp.zig:7:9: error: unreachable else prong, all cases already handled", | ||
| 599 | }); | ||
| 600 | |||
| 601 | cases.add("switch expression - unreachable else prong (range u8)", | ||
| 602 | \\fn foo(x: u8) void { | ||
| 603 | \\ switch (x) { | ||
| 604 | \\ 0 => {}, | ||
| 605 | \\ 1 => {}, | ||
| 606 | \\ 2 => {}, | ||
| 607 | \\ 3 => {}, | ||
| 608 | \\ 4...255 => {}, | ||
| 609 | \\ else => {}, | ||
| 610 | \\ } | ||
| 611 | \\} | ||
| 612 | \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); } | ||
| 613 | , &[_][]const u8{ | ||
| 614 | "tmp.zig:8:9: error: unreachable else prong, all cases already handled", | ||
| 615 | }); | ||
| 616 | |||
| 617 | cases.add("switch expression - unreachable else prong (range i8)", | ||
| 618 | \\fn foo(x: i8) void { | ||
| 619 | \\ switch (x) { | ||
| 620 | \\ -128...0 => {}, | ||
| 621 | \\ 1 => {}, | ||
| 622 | \\ 2 => {}, | ||
| 623 | \\ 3 => {}, | ||
| 624 | \\ 4...127 => {}, | ||
| 625 | \\ else => {}, | ||
| 626 | \\ } | ||
| 627 | \\} | ||
| 628 | \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); } | ||
| 629 | , &[_][]const u8{ | ||
| 630 | "tmp.zig:8:9: error: unreachable else prong, all cases already handled", | ||
| 631 | }); | ||
| 632 | |||
| 633 | cases.add("switch expression - unreachable else prong (enum)", | ||
| 634 | \\const TestEnum = enum{ T1, T2 }; | ||
| 635 | \\ | ||
| 636 | \\fn err(x: u8) TestEnum { | ||
| 637 | \\ switch (x) { | ||
| 638 | \\ 0 => return TestEnum.T1, | ||
| 639 | \\ else => return TestEnum.T2, | ||
| 640 | \\ } | ||
| 641 | \\} | ||
| 642 | \\ | ||
| 643 | \\fn foo(x: u8) void { | ||
| 644 | \\ switch (err(x)) { | ||
| 645 | \\ TestEnum.T1 => {}, | ||
| 646 | \\ TestEnum.T2 => {}, | ||
| 647 | \\ else => {}, | ||
| 648 | \\ } | ||
| 649 | \\} | ||
| 650 | \\ | ||
| 651 | \\export fn entry() usize { return @sizeOf(@TypeOf(foo)); } | ||
| 652 | , &[_][]const u8{ | ||
| 653 | "tmp.zig:14:9: error: unreachable else prong, all cases already handled", | ||
| 654 | }); | ||
| 655 | |||
| 512 | cases.addTest("@export with empty name string", | 656 | cases.addTest("@export with empty name string", |
| 513 | \\pub export fn entry() void { } | 657 | \\pub export fn entry() void { } |
| 514 | \\comptime { | 658 | \\comptime { |
test/stage1/behavior/bugs/1111.zig-1| ... | @@ -7,6 +7,5 @@ test "issue 1111 fixed" { | ... | @@ -7,6 +7,5 @@ test "issue 1111 fixed" { |
| 7 | 7 | ||
| 8 | switch (v) { | 8 | switch (v) { |
| 9 | Foo.Bar => return, | 9 | Foo.Bar => return, |
| 10 | else => return, | ||
| 11 | } | 10 | } |
| 12 | } | 11 | } |
test/stage2/compare_output.zig+105| ... | @@ -7,6 +7,11 @@ const linux_x64 = std.zig.CrossTarget{ | ... | @@ -7,6 +7,11 @@ const linux_x64 = std.zig.CrossTarget{ |
| 7 | .os_tag = .linux, | 7 | .os_tag = .linux, |
| 8 | }; | 8 | }; |
| 9 | 9 | ||
| 10 | const linux_riscv64 = std.zig.CrossTarget{ | ||
| 11 | .cpu_arch = .riscv64, | ||
| 12 | .os_tag = .linux, | ||
| 13 | }; | ||
| 14 | |||
| 10 | pub fn addCases(ctx: *TestContext) !void { | 15 | pub fn addCases(ctx: *TestContext) !void { |
| 11 | if (std.Target.current.os.tag != .linux or | 16 | if (std.Target.current.os.tag != .linux or |
| 12 | std.Target.current.cpu.arch != .x86_64) | 17 | std.Target.current.cpu.arch != .x86_64) |
| ... | @@ -118,6 +123,42 @@ pub fn addCases(ctx: *TestContext) !void { | ... | @@ -118,6 +123,42 @@ pub fn addCases(ctx: *TestContext) !void { |
| 118 | \\ | 123 | \\ |
| 119 | ); | 124 | ); |
| 120 | } | 125 | } |
| 126 | |||
| 127 | { | ||
| 128 | var case = ctx.exe("hello world", linux_riscv64); | ||
| 129 | // Regular old hello world | ||
| 130 | case.addCompareOutput( | ||
| 131 | \\export fn _start() noreturn { | ||
| 132 | \\ print(); | ||
| 133 | \\ | ||
| 134 | \\ exit(); | ||
| 135 | \\} | ||
| 136 | \\ | ||
| 137 | \\fn print() void { | ||
| 138 | \\ asm volatile ("ecall" | ||
| 139 | \\ : | ||
| 140 | \\ : [number] "{a7}" (64), | ||
| 141 | \\ [arg1] "{a0}" (1), | ||
| 142 | \\ [arg2] "{a1}" (@ptrToInt("Hello, World!\n")), | ||
| 143 | \\ [arg3] "{a2}" ("Hello, World!\n".len) | ||
| 144 | \\ : "rcx", "r11", "memory" | ||
| 145 | \\ ); | ||
| 146 | \\ return; | ||
| 147 | \\} | ||
| 148 | \\ | ||
| 149 | \\fn exit() noreturn { | ||
| 150 | \\ asm volatile ("ecall" | ||
| 151 | \\ : | ||
| 152 | \\ : [number] "{a7}" (94), | ||
| 153 | \\ [arg1] "{a0}" (0) | ||
| 154 | \\ : "rcx", "r11", "memory" | ||
| 155 | \\ ); | ||
| 156 | \\ unreachable; | ||
| 157 | \\} | ||
| 158 | , | ||
| 159 | "Hello, World!\n", | ||
| 160 | ); | ||
| 161 | } | ||
| 121 | 162 | ||
| 122 | { | 163 | { |
| 123 | var case = ctx.exe("adding numbers at comptime", linux_x64); | 164 | var case = ctx.exe("adding numbers at comptime", linux_x64); |
| ... | @@ -333,5 +374,69 @@ pub fn addCases(ctx: *TestContext) !void { | ... | @@ -333,5 +374,69 @@ pub fn addCases(ctx: *TestContext) !void { |
| 333 | , | 374 | , |
| 334 | "", | 375 | "", |
| 335 | ); | 376 | ); |
| 377 | |||
| 378 | // Now we test integer return values. | ||
| 379 | case.addCompareOutput( | ||
| 380 | \\export fn _start() noreturn { | ||
| 381 | \\ assert(add(3, 4) == 7); | ||
| 382 | \\ assert(add(20, 10) == 30); | ||
| 383 | \\ | ||
| 384 | \\ exit(); | ||
| 385 | \\} | ||
| 386 | \\ | ||
| 387 | \\fn add(a: u32, b: u32) u32 { | ||
| 388 | \\ return a + b; | ||
| 389 | \\} | ||
| 390 | \\ | ||
| 391 | \\pub fn assert(ok: bool) void { | ||
| 392 | \\ if (!ok) unreachable; // assertion failure | ||
| 393 | \\} | ||
| 394 | \\ | ||
| 395 | \\fn exit() noreturn { | ||
| 396 | \\ asm volatile ("syscall" | ||
| 397 | \\ : | ||
| 398 | \\ : [number] "{rax}" (231), | ||
| 399 | \\ [arg1] "{rdi}" (0) | ||
| 400 | \\ : "rcx", "r11", "memory" | ||
| 401 | \\ ); | ||
| 402 | \\ unreachable; | ||
| 403 | \\} | ||
| 404 | , | ||
| 405 | "", | ||
| 406 | ); | ||
| 407 | |||
| 408 | // Local mutable variables. | ||
| 409 | case.addCompareOutput( | ||
| 410 | \\export fn _start() noreturn { | ||
| 411 | \\ assert(add(3, 4) == 7); | ||
| 412 | \\ assert(add(20, 10) == 30); | ||
| 413 | \\ | ||
| 414 | \\ exit(); | ||
| 415 | \\} | ||
| 416 | \\ | ||
| 417 | \\fn add(a: u32, b: u32) u32 { | ||
| 418 | \\ var x: u32 = undefined; | ||
| 419 | \\ x = 0; | ||
| 420 | \\ x += a; | ||
| 421 | \\ x += b; | ||
| 422 | \\ return x; | ||
| 423 | \\} | ||
| 424 | \\ | ||
| 425 | \\pub fn assert(ok: bool) void { | ||
| 426 | \\ if (!ok) unreachable; // assertion failure | ||
| 427 | \\} | ||
| 428 | \\ | ||
| 429 | \\fn exit() noreturn { | ||
| 430 | \\ asm volatile ("syscall" | ||
| 431 | \\ : | ||
| 432 | \\ : [number] "{rax}" (231), | ||
| 433 | \\ [arg1] "{rdi}" (0) | ||
| 434 | \\ : "rcx", "r11", "memory" | ||
| 435 | \\ ); | ||
| 436 | \\ unreachable; | ||
| 437 | \\} | ||
| 438 | , | ||
| 439 | "", | ||
| 440 | ); | ||
| 336 | } | 441 | } |
| 337 | } | 442 | } |
test/translate_c.zig+20| ... | @@ -3,6 +3,16 @@ const std = @import("std"); | ... | @@ -3,6 +3,16 @@ const std = @import("std"); |
| 3 | const CrossTarget = std.zig.CrossTarget; | 3 | const CrossTarget = std.zig.CrossTarget; |
| 4 | 4 | ||
| 5 | pub fn addCases(cases: *tests.TranslateCContext) void { | 5 | pub fn addCases(cases: *tests.TranslateCContext) void { |
| 6 | cases.add("alignof", | ||
| 7 | \\int main() { | ||
| 8 | \\ int a = _Alignof(int); | ||
| 9 | \\} | ||
| 10 | , &[_][]const u8{ | ||
| 11 | \\pub export fn main() c_int { | ||
| 12 | \\ var a: c_int = @bitCast(c_int, @truncate(c_uint, @alignOf(c_int))); | ||
| 13 | \\} | ||
| 14 | }); | ||
| 15 | |||
| 6 | cases.add("initializer list macro", | 16 | cases.add("initializer list macro", |
| 7 | \\typedef struct Color { | 17 | \\typedef struct Color { |
| 8 | \\ unsigned char r; | 18 | \\ unsigned char r; |
| ... | @@ -2715,6 +2725,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void { | ... | @@ -2715,6 +2725,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 2715 | \\pub const BAR = (@import("std").meta.cast(?*c_void, a)); | 2725 | \\pub const BAR = (@import("std").meta.cast(?*c_void, a)); |
| 2716 | }); | 2726 | }); |
| 2717 | 2727 | ||
| 2728 | cases.add("macro with cast to unsigned short, long, and long long", | ||
| 2729 | \\#define CURLAUTH_BASIC_BUT_USHORT ((unsigned short) 1) | ||
| 2730 | \\#define CURLAUTH_BASIC ((unsigned long) 1) | ||
| 2731 | \\#define CURLAUTH_BASIC_BUT_ULONGLONG ((unsigned long long) 1) | ||
| 2732 | , &[_][]const u8{ | ||
| 2733 | \\pub const CURLAUTH_BASIC_BUT_USHORT = (@import("std").meta.cast(c_ushort, 1)); | ||
| 2734 | \\pub const CURLAUTH_BASIC = (@import("std").meta.cast(c_ulong, 1)); | ||
| 2735 | \\pub const CURLAUTH_BASIC_BUT_ULONGLONG = (@import("std").meta.cast(c_ulonglong, 1)); | ||
| 2736 | }); | ||
| 2737 | |||
| 2718 | cases.add("macro conditional operator", | 2738 | cases.add("macro conditional operator", |
| 2719 | \\#define FOO a ? b : c | 2739 | \\#define FOO a ? b : c |
| 2720 | , &[_][]const u8{ | 2740 | , &[_][]const u8{ |