| author | |
| committer | |
| log | 16f100b82e4075a047f008c0de6c44fc418eb58e |
| tree | 93555fbcba29bcd1120349a03ae6904321cb7718 |
| parent | 9a22c8b6ca98fd01795f8cd4f3e9d92311175f13 |
| parent | b0968abccbfb4072528c3b5e039bc03b27af89a1 |
| signature | Signed by PGP key 4AEE18F83AFDEB23 |
rework self-hosted compiler for incremental builds37 files changed, 6309 insertions(+), 6595 deletions(-)
README.md-37| ... | @@ -71,40 +71,3 @@ can do about it. See that issue for a workaround you can do in the meantime. | ... | @@ -71,40 +71,3 @@ can do about it. See that issue for a workaround you can do in the meantime. |
| 71 | ##### Windows | 71 | ##### Windows |
| 72 | 72 | ||
| 73 | See https://github.com/ziglang/zig/wiki/Building-Zig-on-Windows | 73 | See https://github.com/ziglang/zig/wiki/Building-Zig-on-Windows |
| 74 | |||
| 75 | ### Stage 2: Build Self-Hosted Zig from Zig Source Code | ||
| 76 | |||
| 77 | *Note: Stage 2 compiler is not complete. Beta users of Zig should use the | ||
| 78 | Stage 1 compiler for now.* | ||
| 79 | |||
| 80 | Dependencies are the same as Stage 1, except now you can use stage 1 to compile | ||
| 81 | Zig code. | ||
| 82 | |||
| 83 | ``` | ||
| 84 | bin/zig build --prefix $(pwd)/stage2 | ||
| 85 | ``` | ||
| 86 | |||
| 87 | This produces `./stage2/bin/zig` which can be used for testing and development. | ||
| 88 | Once it is feature complete, it will be used to build stage 3 - the final compiler | ||
| 89 | binary. | ||
| 90 | |||
| 91 | ### Stage 3: Rebuild Self-Hosted Zig Using the Self-Hosted Compiler | ||
| 92 | |||
| 93 | *Note: Stage 2 compiler is not yet able to build Stage 3. Building Stage 3 is | ||
| 94 | not yet supported.* | ||
| 95 | |||
| 96 | Once the self-hosted compiler can build itself, this will be the actual | ||
| 97 | compiler binary that we will install to the system. Until then, users should | ||
| 98 | use stage 1. | ||
| 99 | |||
| 100 | #### Debug / Development Build | ||
| 101 | |||
| 102 | ``` | ||
| 103 | ./stage2/bin/zig build --prefix $(pwd)/stage3 | ||
| 104 | ``` | ||
| 105 | |||
| 106 | #### Release / Install Build | ||
| 107 | |||
| 108 | ``` | ||
| 109 | ./stage2/bin/zig build install -Drelease | ||
| 110 | ``` |
build.zig+8-7| ... | @@ -51,6 +51,8 @@ pub fn build(b: *Builder) !void { | ... | @@ -51,6 +51,8 @@ pub fn build(b: *Builder) !void { |
| 51 | 51 | ||
| 52 | var exe = b.addExecutable("zig", "src-self-hosted/main.zig"); | 52 | var exe = b.addExecutable("zig", "src-self-hosted/main.zig"); |
| 53 | exe.setBuildMode(mode); | 53 | exe.setBuildMode(mode); |
| 54 | test_step.dependOn(&exe.step); | ||
| 55 | b.default_step.dependOn(&exe.step); | ||
| 54 | 56 | ||
| 55 | const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; | 57 | const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; |
| 56 | const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release; | 58 | const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release; |
| ... | @@ -58,21 +60,20 @@ pub fn build(b: *Builder) !void { | ... | @@ -58,21 +60,20 @@ pub fn build(b: *Builder) !void { |
| 58 | const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release; | 60 | const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release; |
| 59 | const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false; | 61 | const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false; |
| 60 | const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false; | 62 | const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false; |
| 61 | const skip_self_hosted = (b.option(bool, "skip-self-hosted", "Main test suite skips building self hosted compiler") orelse false) or true; // TODO evented I/O good enough that this passes everywhere | ||
| 62 | if (!skip_self_hosted) { | ||
| 63 | test_step.dependOn(&exe.step); | ||
| 64 | } | ||
| 65 | 63 | ||
| 66 | const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false; | 64 | const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false; |
| 67 | if (!only_install_lib_files and !skip_self_hosted) { | 65 | const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse false; |
| 66 | if (enable_llvm) { | ||
| 68 | var ctx = parseConfigH(b, config_h_text); | 67 | var ctx = parseConfigH(b, config_h_text); |
| 69 | ctx.llvm = try findLLVM(b, ctx.llvm_config_exe); | 68 | ctx.llvm = try findLLVM(b, ctx.llvm_config_exe); |
| 70 | 69 | ||
| 71 | try configureStage2(b, exe, ctx); | 70 | try configureStage2(b, exe, ctx); |
| 72 | 71 | } | |
| 73 | b.default_step.dependOn(&exe.step); | 72 | if (!only_install_lib_files) { |
| 74 | exe.install(); | 73 | exe.install(); |
| 75 | } | 74 | } |
| 75 | const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false; | ||
| 76 | if (link_libc) exe.linkLibC(); | ||
| 76 | 77 | ||
| 77 | b.installDirectory(InstallDirectoryOptions{ | 78 | b.installDirectory(InstallDirectoryOptions{ |
| 78 | .source_dir = "lib", | 79 | .source_dir = "lib", |
lib/std/array_list.zig+242-9| ... | @@ -8,13 +8,13 @@ const Allocator = mem.Allocator; | ... | @@ -8,13 +8,13 @@ const Allocator = mem.Allocator; |
| 8 | /// A contiguous, growable list of items in memory. | 8 | /// A contiguous, growable list of items in memory. |
| 9 | /// This is a wrapper around an array of T values. Initialize with `init`. | 9 | /// This is a wrapper around an array of T values. Initialize with `init`. |
| 10 | pub fn ArrayList(comptime T: type) type { | 10 | pub fn ArrayList(comptime T: type) type { |
| 11 | return AlignedArrayList(T, null); | 11 | return ArrayListAligned(T, null); |
| 12 | } | 12 | } |
| 13 | 13 | ||
| 14 | pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { | 14 | pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { |
| 15 | if (alignment) |a| { | 15 | if (alignment) |a| { |
| 16 | if (a == @alignOf(T)) { | 16 | if (a == @alignOf(T)) { |
| 17 | return AlignedArrayList(T, null); | 17 | return ArrayListAligned(T, null); |
| 18 | } | 18 | } |
| 19 | } | 19 | } |
| 20 | return struct { | 20 | return struct { |
| ... | @@ -76,6 +76,10 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { | ... | @@ -76,6 +76,10 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { |
| 76 | }; | 76 | }; |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) { | ||
| 80 | return .{ .items = self.items, .capacity = self.capacity }; | ||
| 81 | } | ||
| 82 | |||
| 79 | /// The caller owns the returned memory. ArrayList becomes empty. | 83 | /// The caller owns the returned memory. ArrayList becomes empty. |
| 80 | pub fn toOwnedSlice(self: *Self) Slice { | 84 | pub fn toOwnedSlice(self: *Self) Slice { |
| 81 | const allocator = self.allocator; | 85 | const allocator = self.allocator; |
| ... | @@ -84,8 +88,8 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { | ... | @@ -84,8 +88,8 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { |
| 84 | return result; | 88 | return result; |
| 85 | } | 89 | } |
| 86 | 90 | ||
| 87 | /// Insert `item` at index `n`. Moves `list[n .. list.len]` | 91 | /// Insert `item` at index `n` by moving `list[n .. list.len]` to make room. |
| 88 | /// to make room. | 92 | /// This operation is O(N). |
| 89 | pub fn insert(self: *Self, n: usize, item: T) !void { | 93 | pub fn insert(self: *Self, n: usize, item: T) !void { |
| 90 | try self.ensureCapacity(self.items.len + 1); | 94 | try self.ensureCapacity(self.items.len + 1); |
| 91 | self.items.len += 1; | 95 | self.items.len += 1; |
| ... | @@ -94,8 +98,7 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { | ... | @@ -94,8 +98,7 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { |
| 94 | self.items[n] = item; | 98 | self.items[n] = item; |
| 95 | } | 99 | } |
| 96 | 100 | ||
| 97 | /// Insert slice `items` at index `i`. Moves | 101 | /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room. |
| 98 | /// `list[i .. list.len]` to make room. | ||
| 99 | /// This operation is O(N). | 102 | /// This operation is O(N). |
| 100 | pub fn insertSlice(self: *Self, i: usize, items: SliceConst) !void { | 103 | pub fn insertSlice(self: *Self, i: usize, items: SliceConst) !void { |
| 101 | try self.ensureCapacity(self.items.len + items.len); | 104 | try self.ensureCapacity(self.items.len + items.len); |
| ... | @@ -146,10 +149,15 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { | ... | @@ -146,10 +149,15 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { |
| 146 | /// Append the slice of items to the list. Allocates more | 149 | /// Append the slice of items to the list. Allocates more |
| 147 | /// memory as necessary. | 150 | /// memory as necessary. |
| 148 | pub fn appendSlice(self: *Self, items: SliceConst) !void { | 151 | pub fn appendSlice(self: *Self, items: SliceConst) !void { |
| 152 | try self.ensureCapacity(self.items.len + items.len); | ||
| 153 | self.appendSliceAssumeCapacity(items); | ||
| 154 | } | ||
| 155 | |||
| 156 | /// Append the slice of items to the list, asserting the capacity is already | ||
| 157 | /// enough to store the new items. | ||
| 158 | pub fn appendSliceAssumeCapacity(self: *Self, items: SliceConst) void { | ||
| 149 | const oldlen = self.items.len; | 159 | const oldlen = self.items.len; |
| 150 | const newlen = self.items.len + items.len; | 160 | const newlen = self.items.len + items.len; |
| 151 | |||
| 152 | try self.ensureCapacity(newlen); | ||
| 153 | self.items.len = newlen; | 161 | self.items.len = newlen; |
| 154 | mem.copy(T, self.items[oldlen..], items); | 162 | mem.copy(T, self.items[oldlen..], items); |
| 155 | } | 163 | } |
| ... | @@ -259,6 +267,231 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { | ... | @@ -259,6 +267,231 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { |
| 259 | }; | 267 | }; |
| 260 | } | 268 | } |
| 261 | 269 | ||
| 270 | /// Bring-your-own allocator with every function call. | ||
| 271 | /// Initialize directly and deinitialize with `deinit` or use `toOwnedSlice`. | ||
| 272 | pub fn ArrayListUnmanaged(comptime T: type) type { | ||
| 273 | return ArrayListAlignedUnmanaged(T, null); | ||
| 274 | } | ||
| 275 | |||
| 276 | pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) type { | ||
| 277 | if (alignment) |a| { | ||
| 278 | if (a == @alignOf(T)) { | ||
| 279 | return ArrayListAlignedUnmanaged(T, null); | ||
| 280 | } | ||
| 281 | } | ||
| 282 | return struct { | ||
| 283 | const Self = @This(); | ||
| 284 | |||
| 285 | /// Content of the ArrayList. | ||
| 286 | items: Slice = &[_]T{}, | ||
| 287 | capacity: usize = 0, | ||
| 288 | |||
| 289 | pub const Slice = if (alignment) |a| ([]align(a) T) else []T; | ||
| 290 | pub const SliceConst = if (alignment) |a| ([]align(a) const T) else []const T; | ||
| 291 | |||
| 292 | /// Initialize with capacity to hold at least num elements. | ||
| 293 | /// Deinitialize with `deinit` or use `toOwnedSlice`. | ||
| 294 | pub fn initCapacity(allocator: *Allocator, num: usize) !Self { | ||
| 295 | var self = Self.init(allocator); | ||
| 296 | try self.ensureCapacity(allocator, num); | ||
| 297 | return self; | ||
| 298 | } | ||
| 299 | |||
| 300 | /// Release all allocated memory. | ||
| 301 | pub fn deinit(self: *Self, allocator: *Allocator) void { | ||
| 302 | allocator.free(self.allocatedSlice()); | ||
| 303 | self.* = undefined; | ||
| 304 | } | ||
| 305 | |||
| 306 | pub fn toManaged(self: *Self, allocator: *Allocator) ArrayListAligned(T, alignment) { | ||
| 307 | return .{ .items = self.items, .capacity = self.capacity, .allocator = allocator }; | ||
| 308 | } | ||
| 309 | |||
| 310 | /// The caller owns the returned memory. ArrayList becomes empty. | ||
| 311 | pub fn toOwnedSlice(self: *Self, allocator: *Allocator) Slice { | ||
| 312 | const result = allocator.shrink(self.allocatedSlice(), self.items.len); | ||
| 313 | self.* = Self{}; | ||
| 314 | return result; | ||
| 315 | } | ||
| 316 | |||
| 317 | /// Insert `item` at index `n`. Moves `list[n .. list.len]` | ||
| 318 | /// to make room. | ||
| 319 | pub fn insert(self: *Self, allocator: *Allocator, n: usize, item: T) !void { | ||
| 320 | try self.ensureCapacity(allocator, self.items.len + 1); | ||
| 321 | self.items.len += 1; | ||
| 322 | |||
| 323 | mem.copyBackwards(T, self.items[n + 1 .. self.items.len], self.items[n .. self.items.len - 1]); | ||
| 324 | self.items[n] = item; | ||
| 325 | } | ||
| 326 | |||
| 327 | /// Insert slice `items` at index `i`. Moves | ||
| 328 | /// `list[i .. list.len]` to make room. | ||
| 329 | /// This operation is O(N). | ||
| 330 | pub fn insertSlice(self: *Self, allocator: *Allocator, i: usize, items: SliceConst) !void { | ||
| 331 | try self.ensureCapacity(allocator, self.items.len + items.len); | ||
| 332 | self.items.len += items.len; | ||
| 333 | |||
| 334 | mem.copyBackwards(T, self.items[i + items.len .. self.items.len], self.items[i .. self.items.len - items.len]); | ||
| 335 | mem.copy(T, self.items[i .. i + items.len], items); | ||
| 336 | } | ||
| 337 | |||
| 338 | /// Extend the list by 1 element. Allocates more memory as necessary. | ||
| 339 | pub fn append(self: *Self, allocator: *Allocator, item: T) !void { | ||
| 340 | const new_item_ptr = try self.addOne(allocator); | ||
| 341 | new_item_ptr.* = item; | ||
| 342 | } | ||
| 343 | |||
| 344 | /// Extend the list by 1 element, but asserting `self.capacity` | ||
| 345 | /// is sufficient to hold an additional item. | ||
| 346 | pub fn appendAssumeCapacity(self: *Self, item: T) void { | ||
| 347 | const new_item_ptr = self.addOneAssumeCapacity(); | ||
| 348 | new_item_ptr.* = item; | ||
| 349 | } | ||
| 350 | |||
| 351 | /// Remove the element at index `i` from the list and return its value. | ||
| 352 | /// Asserts the array has at least one item. | ||
| 353 | /// This operation is O(N). | ||
| 354 | pub fn orderedRemove(self: *Self, i: usize) T { | ||
| 355 | const newlen = self.items.len - 1; | ||
| 356 | if (newlen == i) return self.pop(); | ||
| 357 | |||
| 358 | const old_item = self.items[i]; | ||
| 359 | for (self.items[i..newlen]) |*b, j| b.* = self.items[i + 1 + j]; | ||
| 360 | self.items[newlen] = undefined; | ||
| 361 | self.items.len = newlen; | ||
| 362 | return old_item; | ||
| 363 | } | ||
| 364 | |||
| 365 | /// Removes the element at the specified index and returns it. | ||
| 366 | /// The empty slot is filled from the end of the list. | ||
| 367 | /// This operation is O(1). | ||
| 368 | pub fn swapRemove(self: *Self, i: usize) T { | ||
| 369 | if (self.items.len - 1 == i) return self.pop(); | ||
| 370 | |||
| 371 | const old_item = self.items[i]; | ||
| 372 | self.items[i] = self.pop(); | ||
| 373 | return old_item; | ||
| 374 | } | ||
| 375 | |||
| 376 | /// Append the slice of items to the list. Allocates more | ||
| 377 | /// memory as necessary. | ||
| 378 | pub fn appendSlice(self: *Self, allocator: *Allocator, items: SliceConst) !void { | ||
| 379 | try self.ensureCapacity(allocator, self.items.len + items.len); | ||
| 380 | self.appendSliceAssumeCapacity(items); | ||
| 381 | } | ||
| 382 | |||
| 383 | /// Append the slice of items to the list, asserting the capacity is enough | ||
| 384 | /// to store the new items. | ||
| 385 | pub fn appendSliceAssumeCapacity(self: *Self, items: SliceConst) void { | ||
| 386 | const oldlen = self.items.len; | ||
| 387 | const newlen = self.items.len + items.len; | ||
| 388 | |||
| 389 | self.items.len = newlen; | ||
| 390 | mem.copy(T, self.items[oldlen..], items); | ||
| 391 | } | ||
| 392 | |||
| 393 | /// Same as `append` except it returns the number of bytes written, which is always the same | ||
| 394 | /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API. | ||
| 395 | /// This function may be called only when `T` is `u8`. | ||
| 396 | fn appendWrite(self: *Self, allocator: *Allocator, m: []const u8) !usize { | ||
| 397 | try self.appendSlice(allocator, m); | ||
| 398 | return m.len; | ||
| 399 | } | ||
| 400 | |||
| 401 | /// Append a value to the list `n` times. | ||
| 402 | /// Allocates more memory as necessary. | ||
| 403 | pub fn appendNTimes(self: *Self, allocator: *Allocator, value: T, n: usize) !void { | ||
| 404 | const old_len = self.items.len; | ||
| 405 | try self.resize(self.items.len + n); | ||
| 406 | mem.set(T, self.items[old_len..self.items.len], value); | ||
| 407 | } | ||
| 408 | |||
| 409 | /// Adjust the list's length to `new_len`. | ||
| 410 | /// Does not initialize added items if any. | ||
| 411 | pub fn resize(self: *Self, allocator: *Allocator, new_len: usize) !void { | ||
| 412 | try self.ensureCapacity(allocator, new_len); | ||
| 413 | self.items.len = new_len; | ||
| 414 | } | ||
| 415 | |||
| 416 | /// Reduce allocated capacity to `new_len`. | ||
| 417 | /// Invalidates element pointers. | ||
| 418 | pub fn shrink(self: *Self, allocator: *Allocator, new_len: usize) void { | ||
| 419 | assert(new_len <= self.items.len); | ||
| 420 | |||
| 421 | self.items = allocator.realloc(self.allocatedSlice(), new_len) catch |e| switch (e) { | ||
| 422 | error.OutOfMemory => { // no problem, capacity is still correct then. | ||
| 423 | self.items.len = new_len; | ||
| 424 | return; | ||
| 425 | }, | ||
| 426 | }; | ||
| 427 | self.capacity = new_len; | ||
| 428 | } | ||
| 429 | |||
| 430 | pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void { | ||
| 431 | var better_capacity = self.capacity; | ||
| 432 | if (better_capacity >= new_capacity) return; | ||
| 433 | |||
| 434 | while (true) { | ||
| 435 | better_capacity += better_capacity / 2 + 8; | ||
| 436 | if (better_capacity >= new_capacity) break; | ||
| 437 | } | ||
| 438 | |||
| 439 | const new_memory = try allocator.realloc(self.allocatedSlice(), better_capacity); | ||
| 440 | self.items.ptr = new_memory.ptr; | ||
| 441 | self.capacity = new_memory.len; | ||
| 442 | } | ||
| 443 | |||
| 444 | /// Increases the array's length to match the full capacity that is already allocated. | ||
| 445 | /// The new elements have `undefined` values. | ||
| 446 | /// This operation does not invalidate any element pointers. | ||
| 447 | pub fn expandToCapacity(self: *Self) void { | ||
| 448 | self.items.len = self.capacity; | ||
| 449 | } | ||
| 450 | |||
| 451 | /// Increase length by 1, returning pointer to the new item. | ||
| 452 | /// The returned pointer becomes invalid when the list is resized. | ||
| 453 | pub fn addOne(self: *Self, allocator: *Allocator) !*T { | ||
| 454 | const newlen = self.items.len + 1; | ||
| 455 | try self.ensureCapacity(allocator, newlen); | ||
| 456 | return self.addOneAssumeCapacity(); | ||
| 457 | } | ||
| 458 | |||
| 459 | /// Increase length by 1, returning pointer to the new item. | ||
| 460 | /// Asserts that there is already space for the new item without allocating more. | ||
| 461 | /// The returned pointer becomes invalid when the list is resized. | ||
| 462 | /// This operation does not invalidate any element pointers. | ||
| 463 | pub fn addOneAssumeCapacity(self: *Self) *T { | ||
| 464 | assert(self.items.len < self.capacity); | ||
| 465 | |||
| 466 | self.items.len += 1; | ||
| 467 | return &self.items[self.items.len - 1]; | ||
| 468 | } | ||
| 469 | |||
| 470 | /// Remove and return the last element from the list. | ||
| 471 | /// Asserts the list has at least one item. | ||
| 472 | /// This operation does not invalidate any element pointers. | ||
| 473 | pub fn pop(self: *Self) T { | ||
| 474 | const val = self.items[self.items.len - 1]; | ||
| 475 | self.items.len -= 1; | ||
| 476 | return val; | ||
| 477 | } | ||
| 478 | |||
| 479 | /// Remove and return the last element from the list. | ||
| 480 | /// If the list is empty, returns `null`. | ||
| 481 | /// This operation does not invalidate any element pointers. | ||
| 482 | pub fn popOrNull(self: *Self) ?T { | ||
| 483 | if (self.items.len == 0) return null; | ||
| 484 | return self.pop(); | ||
| 485 | } | ||
| 486 | |||
| 487 | /// For a nicer API, `items.len` is the length, not the capacity. | ||
| 488 | /// This requires "unsafe" slicing. | ||
| 489 | fn allocatedSlice(self: Self) Slice { | ||
| 490 | return self.items.ptr[0..self.capacity]; | ||
| 491 | } | ||
| 492 | }; | ||
| 493 | } | ||
| 494 | |||
| 262 | test "std.ArrayList.init" { | 495 | test "std.ArrayList.init" { |
| 263 | var list = ArrayList(i32).init(testing.allocator); | 496 | var list = ArrayList(i32).init(testing.allocator); |
| 264 | defer list.deinit(); | 497 | defer list.deinit(); |
lib/std/fifo.zig+20-17| ... | @@ -191,8 +191,8 @@ pub fn LinearFifo( | ... | @@ -191,8 +191,8 @@ pub fn LinearFifo( |
| 191 | } | 191 | } |
| 192 | 192 | ||
| 193 | /// Read the next item from the fifo | 193 | /// Read the next item from the fifo |
| 194 | pub fn readItem(self: *Self) !T { | 194 | pub fn readItem(self: *Self) ?T { |
| 195 | if (self.count == 0) return error.EndOfStream; | 195 | if (self.count == 0) return null; |
| 196 | 196 | ||
| 197 | const c = self.buf[self.head]; | 197 | const c = self.buf[self.head]; |
| 198 | self.discard(1); | 198 | self.discard(1); |
| ... | @@ -282,7 +282,10 @@ pub fn LinearFifo( | ... | @@ -282,7 +282,10 @@ pub fn LinearFifo( |
| 282 | /// Write a single item to the fifo | 282 | /// Write a single item to the fifo |
| 283 | pub fn writeItem(self: *Self, item: T) !void { | 283 | pub fn writeItem(self: *Self, item: T) !void { |
| 284 | try self.ensureUnusedCapacity(1); | 284 | try self.ensureUnusedCapacity(1); |
| 285 | return self.writeItemAssumeCapacity(item); | ||
| 286 | } | ||
| 285 | 287 | ||
| 288 | pub fn writeItemAssumeCapacity(self: *Self, item: T) void { | ||
| 286 | var tail = self.head + self.count; | 289 | var tail = self.head + self.count; |
| 287 | if (powers_of_two) { | 290 | if (powers_of_two) { |
| 288 | tail &= self.buf.len - 1; | 291 | tail &= self.buf.len - 1; |
| ... | @@ -342,10 +345,10 @@ pub fn LinearFifo( | ... | @@ -342,10 +345,10 @@ pub fn LinearFifo( |
| 342 | } | 345 | } |
| 343 | } | 346 | } |
| 344 | 347 | ||
| 345 | /// Peek at the item at `offset` | 348 | /// Returns the item at `offset`. |
| 346 | pub fn peekItem(self: Self, offset: usize) error{EndOfStream}!T { | 349 | /// Asserts offset is within bounds. |
| 347 | if (offset >= self.count) | 350 | pub fn peekItem(self: Self, offset: usize) T { |
| 348 | return error.EndOfStream; | 351 | assert(offset < self.count); |
| 349 | 352 | ||
| 350 | var index = self.head + offset; | 353 | var index = self.head + offset; |
| 351 | if (powers_of_two) { | 354 | if (powers_of_two) { |
| ... | @@ -369,18 +372,18 @@ test "LinearFifo(u8, .Dynamic)" { | ... | @@ -369,18 +372,18 @@ test "LinearFifo(u8, .Dynamic)" { |
| 369 | { | 372 | { |
| 370 | var i: usize = 0; | 373 | var i: usize = 0; |
| 371 | while (i < 5) : (i += 1) { | 374 | while (i < 5) : (i += 1) { |
| 372 | try fifo.write(&[_]u8{try fifo.peekItem(i)}); | 375 | try fifo.write(&[_]u8{fifo.peekItem(i)}); |
| 373 | } | 376 | } |
| 374 | testing.expectEqual(@as(usize, 10), fifo.readableLength()); | 377 | testing.expectEqual(@as(usize, 10), fifo.readableLength()); |
| 375 | testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0)); | 378 | testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0)); |
| 376 | } | 379 | } |
| 377 | 380 | ||
| 378 | { | 381 | { |
| 379 | testing.expectEqual(@as(u8, 'H'), try fifo.readItem()); | 382 | testing.expectEqual(@as(u8, 'H'), fifo.readItem().?); |
| 380 | testing.expectEqual(@as(u8, 'E'), try fifo.readItem()); | 383 | testing.expectEqual(@as(u8, 'E'), fifo.readItem().?); |
| 381 | testing.expectEqual(@as(u8, 'L'), try fifo.readItem()); | 384 | testing.expectEqual(@as(u8, 'L'), fifo.readItem().?); |
| 382 | testing.expectEqual(@as(u8, 'L'), try fifo.readItem()); | 385 | testing.expectEqual(@as(u8, 'L'), fifo.readItem().?); |
| 383 | testing.expectEqual(@as(u8, 'O'), try fifo.readItem()); | 386 | testing.expectEqual(@as(u8, 'O'), fifo.readItem().?); |
| 384 | } | 387 | } |
| 385 | testing.expectEqual(@as(usize, 5), fifo.readableLength()); | 388 | testing.expectEqual(@as(usize, 5), fifo.readableLength()); |
| 386 | 389 | ||
| ... | @@ -451,11 +454,11 @@ test "LinearFifo" { | ... | @@ -451,11 +454,11 @@ test "LinearFifo" { |
| 451 | testing.expectEqual(@as(usize, 5), fifo.readableLength()); | 454 | testing.expectEqual(@as(usize, 5), fifo.readableLength()); |
| 452 | 455 | ||
| 453 | { | 456 | { |
| 454 | testing.expectEqual(@as(T, 0), try fifo.readItem()); | 457 | testing.expectEqual(@as(T, 0), fifo.readItem().?); |
| 455 | testing.expectEqual(@as(T, 1), try fifo.readItem()); | 458 | testing.expectEqual(@as(T, 1), fifo.readItem().?); |
| 456 | testing.expectEqual(@as(T, 1), try fifo.readItem()); | 459 | testing.expectEqual(@as(T, 1), fifo.readItem().?); |
| 457 | testing.expectEqual(@as(T, 0), try fifo.readItem()); | 460 | testing.expectEqual(@as(T, 0), fifo.readItem().?); |
| 458 | testing.expectEqual(@as(T, 1), try fifo.readItem()); | 461 | testing.expectEqual(@as(T, 1), fifo.readItem().?); |
| 459 | testing.expectEqual(@as(usize, 0), fifo.readableLength()); | 462 | testing.expectEqual(@as(usize, 0), fifo.readableLength()); |
| 460 | } | 463 | } |
| 461 | 464 |
lib/std/fs/file.zig+27| ... | @@ -527,6 +527,33 @@ pub const File = struct { | ... | @@ -527,6 +527,33 @@ pub const File = struct { |
| 527 | } | 527 | } |
| 528 | } | 528 | } |
| 529 | 529 | ||
| 530 | pub const CopyRangeError = PWriteError || PReadError; | ||
| 531 | |||
| 532 | pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: usize) CopyRangeError!usize { | ||
| 533 | // TODO take advantage of copy_file_range OS APIs | ||
| 534 | var buf: [8 * 4096]u8 = undefined; | ||
| 535 | const adjusted_count = math.min(buf.len, len); | ||
| 536 | const amt_read = try in.pread(buf[0..adjusted_count], in_offset); | ||
| 537 | if (amt_read == 0) return @as(usize, 0); | ||
| 538 | return out.pwrite(buf[0..amt_read], out_offset); | ||
| 539 | } | ||
| 540 | |||
| 541 | /// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it | ||
| 542 | /// means the in file reached the end. Reaching the end of a file is not an error condition. | ||
| 543 | pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: usize) CopyRangeError!usize { | ||
| 544 | var total_bytes_copied: usize = 0; | ||
| 545 | var in_off = in_offset; | ||
| 546 | var out_off = out_offset; | ||
| 547 | while (total_bytes_copied < len) { | ||
| 548 | const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied); | ||
| 549 | if (amt_copied == 0) return total_bytes_copied; | ||
| 550 | total_bytes_copied += amt_copied; | ||
| 551 | in_off += amt_copied; | ||
| 552 | out_off += amt_copied; | ||
| 553 | } | ||
| 554 | return total_bytes_copied; | ||
| 555 | } | ||
| 556 | |||
| 530 | pub const WriteFileOptions = struct { | 557 | pub const WriteFileOptions = struct { |
| 531 | in_offset: u64 = 0, | 558 | in_offset: u64 = 0, |
| 532 | 559 |
lib/std/hash_map.zig+5-1| ... | @@ -10,7 +10,7 @@ const Wyhash = std.hash.Wyhash; | ... | @@ -10,7 +10,7 @@ const Wyhash = std.hash.Wyhash; |
| 10 | const Allocator = mem.Allocator; | 10 | const Allocator = mem.Allocator; |
| 11 | const builtin = @import("builtin"); | 11 | const builtin = @import("builtin"); |
| 12 | 12 | ||
| 13 | const want_modification_safety = builtin.mode != .ReleaseFast; | 13 | const want_modification_safety = std.debug.runtime_safety; |
| 14 | const debug_u32 = if (want_modification_safety) u32 else void; | 14 | const debug_u32 = if (want_modification_safety) u32 else void; |
| 15 | 15 | ||
| 16 | pub fn AutoHashMap(comptime K: type, comptime V: type) type { | 16 | pub fn AutoHashMap(comptime K: type, comptime V: type) type { |
| ... | @@ -219,6 +219,10 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 | ... | @@ -219,6 +219,10 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 |
| 219 | return put_result.old_kv; | 219 | return put_result.old_kv; |
| 220 | } | 220 | } |
| 221 | 221 | ||
| 222 | pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { | ||
| 223 | assert(self.putAssumeCapacity(key, value) == null); | ||
| 224 | } | ||
| 225 | |||
| 222 | pub fn get(hm: *const Self, key: K) ?*KV { | 226 | pub fn get(hm: *const Self, key: K) ?*KV { |
| 223 | if (hm.entries.len == 0) { | 227 | if (hm.entries.len == 0) { |
| 224 | return null; | 228 | return null; |
lib/std/heap.zig+1-89| ... | @@ -11,6 +11,7 @@ const maxInt = std.math.maxInt; | ... | @@ -11,6 +11,7 @@ const maxInt = std.math.maxInt; |
| 11 | 11 | ||
| 12 | pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator; | 12 | pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator; |
| 13 | pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator; | 13 | pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator; |
| 14 | pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator; | ||
| 14 | 15 | ||
| 15 | const Allocator = mem.Allocator; | 16 | const Allocator = mem.Allocator; |
| 16 | 17 | ||
| ... | @@ -510,95 +511,6 @@ pub const HeapAllocator = switch (builtin.os.tag) { | ... | @@ -510,95 +511,6 @@ pub const HeapAllocator = switch (builtin.os.tag) { |
| 510 | else => @compileError("Unsupported OS"), | 511 | else => @compileError("Unsupported OS"), |
| 511 | }; | 512 | }; |
| 512 | 513 | ||
| 513 | /// This allocator takes an existing allocator, wraps it, and provides an interface | ||
| 514 | /// where you can allocate without freeing, and then free it all together. | ||
| 515 | pub const ArenaAllocator = struct { | ||
| 516 | allocator: Allocator, | ||
| 517 | |||
| 518 | child_allocator: *Allocator, | ||
| 519 | buffer_list: std.SinglyLinkedList([]u8), | ||
| 520 | end_index: usize, | ||
| 521 | |||
| 522 | const BufNode = std.SinglyLinkedList([]u8).Node; | ||
| 523 | |||
| 524 | pub fn init(child_allocator: *Allocator) ArenaAllocator { | ||
| 525 | return ArenaAllocator{ | ||
| 526 | .allocator = Allocator{ | ||
| 527 | .reallocFn = realloc, | ||
| 528 | .shrinkFn = shrink, | ||
| 529 | }, | ||
| 530 | .child_allocator = child_allocator, | ||
| 531 | .buffer_list = std.SinglyLinkedList([]u8).init(), | ||
| 532 | .end_index = 0, | ||
| 533 | }; | ||
| 534 | } | ||
| 535 | |||
| 536 | pub fn deinit(self: ArenaAllocator) void { | ||
| 537 | var it = self.buffer_list.first; | ||
| 538 | while (it) |node| { | ||
| 539 | // this has to occur before the free because the free frees node | ||
| 540 | const next_it = node.next; | ||
| 541 | self.child_allocator.free(node.data); | ||
| 542 | it = next_it; | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | fn createNode(self: *ArenaAllocator, prev_len: usize, minimum_size: usize) !*BufNode { | ||
| 547 | const actual_min_size = minimum_size + @sizeOf(BufNode); | ||
| 548 | var len = prev_len; | ||
| 549 | while (true) { | ||
| 550 | len += len / 2; | ||
| 551 | len += mem.page_size - @rem(len, mem.page_size); | ||
| 552 | if (len >= actual_min_size) break; | ||
| 553 | } | ||
| 554 | const buf = try self.child_allocator.alignedAlloc(u8, @alignOf(BufNode), len); | ||
| 555 | const buf_node_slice = mem.bytesAsSlice(BufNode, buf[0..@sizeOf(BufNode)]); | ||
| 556 | const buf_node = &buf_node_slice[0]; | ||
| 557 | buf_node.* = BufNode{ | ||
| 558 | .data = buf, | ||
| 559 | .next = null, | ||
| 560 | }; | ||
| 561 | self.buffer_list.prepend(buf_node); | ||
| 562 | self.end_index = 0; | ||
| 563 | return buf_node; | ||
| 564 | } | ||
| 565 | |||
| 566 | fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 { | ||
| 567 | const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator); | ||
| 568 | |||
| 569 | var cur_node = if (self.buffer_list.first) |first_node| first_node else try self.createNode(0, n + alignment); | ||
| 570 | while (true) { | ||
| 571 | const cur_buf = cur_node.data[@sizeOf(BufNode)..]; | ||
| 572 | const addr = @ptrToInt(cur_buf.ptr) + self.end_index; | ||
| 573 | const adjusted_addr = mem.alignForward(addr, alignment); | ||
| 574 | const adjusted_index = self.end_index + (adjusted_addr - addr); | ||
| 575 | const new_end_index = adjusted_index + n; | ||
| 576 | if (new_end_index > cur_buf.len) { | ||
| 577 | cur_node = try self.createNode(cur_buf.len, n + alignment); | ||
| 578 | continue; | ||
| 579 | } | ||
| 580 | const result = cur_buf[adjusted_index..new_end_index]; | ||
| 581 | self.end_index = new_end_index; | ||
| 582 | return result; | ||
| 583 | } | ||
| 584 | } | ||
| 585 | |||
| 586 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | ||
| 587 | if (new_size <= old_mem.len and new_align <= new_size) { | ||
| 588 | // We can't do anything with the memory, so tell the client to keep it. | ||
| 589 | return error.OutOfMemory; | ||
| 590 | } else { | ||
| 591 | const result = try alloc(allocator, new_size, new_align); | ||
| 592 | @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); | ||
| 593 | return result; | ||
| 594 | } | ||
| 595 | } | ||
| 596 | |||
| 597 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | ||
| 598 | return old_mem[0..new_size]; | ||
| 599 | } | ||
| 600 | }; | ||
| 601 | |||
| 602 | pub const FixedBufferAllocator = struct { | 514 | pub const FixedBufferAllocator = struct { |
| 603 | allocator: Allocator, | 515 | allocator: Allocator, |
| 604 | end_index: usize, | 516 | end_index: usize, |
lib/std/heap/arena_allocator.zig created+102| ... | @@ -0,0 +1,102 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const mem = std.mem; | ||
| 4 | const Allocator = std.mem.Allocator; | ||
| 5 | |||
| 6 | /// This allocator takes an existing allocator, wraps it, and provides an interface | ||
| 7 | /// where you can allocate without freeing, and then free it all together. | ||
| 8 | pub const ArenaAllocator = struct { | ||
| 9 | allocator: Allocator, | ||
| 10 | |||
| 11 | child_allocator: *Allocator, | ||
| 12 | state: State, | ||
| 13 | |||
| 14 | /// Inner state of ArenaAllocator. Can be stored rather than the entire ArenaAllocator | ||
| 15 | /// as a memory-saving optimization. | ||
| 16 | pub const State = struct { | ||
| 17 | buffer_list: std.SinglyLinkedList([]u8) = @as(std.SinglyLinkedList([]u8), .{}), | ||
| 18 | end_index: usize = 0, | ||
| 19 | |||
| 20 | pub fn promote(self: State, child_allocator: *Allocator) ArenaAllocator { | ||
| 21 | return .{ | ||
| 22 | .allocator = Allocator{ | ||
| 23 | .reallocFn = realloc, | ||
| 24 | .shrinkFn = shrink, | ||
| 25 | }, | ||
| 26 | .child_allocator = child_allocator, | ||
| 27 | .state = self, | ||
| 28 | }; | ||
| 29 | } | ||
| 30 | }; | ||
| 31 | |||
| 32 | const BufNode = std.SinglyLinkedList([]u8).Node; | ||
| 33 | |||
| 34 | pub fn init(child_allocator: *Allocator) ArenaAllocator { | ||
| 35 | return (State{}).promote(child_allocator); | ||
| 36 | } | ||
| 37 | |||
| 38 | pub fn deinit(self: ArenaAllocator) void { | ||
| 39 | var it = self.state.buffer_list.first; | ||
| 40 | while (it) |node| { | ||
| 41 | // this has to occur before the free because the free frees node | ||
| 42 | const next_it = node.next; | ||
| 43 | self.child_allocator.free(node.data); | ||
| 44 | it = next_it; | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | fn createNode(self: *ArenaAllocator, prev_len: usize, minimum_size: usize) !*BufNode { | ||
| 49 | const actual_min_size = minimum_size + @sizeOf(BufNode); | ||
| 50 | var len = prev_len; | ||
| 51 | while (true) { | ||
| 52 | len += len / 2; | ||
| 53 | len += mem.page_size - @rem(len, mem.page_size); | ||
| 54 | if (len >= actual_min_size) break; | ||
| 55 | } | ||
| 56 | const buf = try self.child_allocator.alignedAlloc(u8, @alignOf(BufNode), len); | ||
| 57 | const buf_node_slice = mem.bytesAsSlice(BufNode, buf[0..@sizeOf(BufNode)]); | ||
| 58 | const buf_node = &buf_node_slice[0]; | ||
| 59 | buf_node.* = BufNode{ | ||
| 60 | .data = buf, | ||
| 61 | .next = null, | ||
| 62 | }; | ||
| 63 | self.state.buffer_list.prepend(buf_node); | ||
| 64 | self.state.end_index = 0; | ||
| 65 | return buf_node; | ||
| 66 | } | ||
| 67 | |||
| 68 | fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 { | ||
| 69 | const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator); | ||
| 70 | |||
| 71 | var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + alignment); | ||
| 72 | while (true) { | ||
| 73 | const cur_buf = cur_node.data[@sizeOf(BufNode)..]; | ||
| 74 | const addr = @ptrToInt(cur_buf.ptr) + self.state.end_index; | ||
| 75 | const adjusted_addr = mem.alignForward(addr, alignment); | ||
| 76 | const adjusted_index = self.state.end_index + (adjusted_addr - addr); | ||
| 77 | const new_end_index = adjusted_index + n; | ||
| 78 | if (new_end_index > cur_buf.len) { | ||
| 79 | cur_node = try self.createNode(cur_buf.len, n + alignment); | ||
| 80 | continue; | ||
| 81 | } | ||
| 82 | const result = cur_buf[adjusted_index..new_end_index]; | ||
| 83 | self.state.end_index = new_end_index; | ||
| 84 | return result; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { | ||
| 89 | if (new_size <= old_mem.len and new_align <= new_size) { | ||
| 90 | // We can't do anything with the memory, so tell the client to keep it. | ||
| 91 | return error.OutOfMemory; | ||
| 92 | } else { | ||
| 93 | const result = try alloc(allocator, new_size, new_align); | ||
| 94 | @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); | ||
| 95 | return result; | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { | ||
| 100 | return old_mem[0..new_size]; | ||
| 101 | } | ||
| 102 | }; | ||
lib/std/linked_list.zig+1-1| ... | @@ -49,7 +49,7 @@ pub fn SinglyLinkedList(comptime T: type) type { | ... | @@ -49,7 +49,7 @@ pub fn SinglyLinkedList(comptime T: type) type { |
| 49 | } | 49 | } |
| 50 | }; | 50 | }; |
| 51 | 51 | ||
| 52 | first: ?*Node, | 52 | first: ?*Node = null, |
| 53 | 53 | ||
| 54 | /// Initialize a linked list. | 54 | /// Initialize a linked list. |
| 55 | /// | 55 | /// |
lib/std/mem.zig+24-10| ... | @@ -279,6 +279,21 @@ pub const Allocator = struct { | ... | @@ -279,6 +279,21 @@ pub const Allocator = struct { |
| 279 | const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1); | 279 | const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1); |
| 280 | assert(shrink_result.len == 0); | 280 | assert(shrink_result.len == 0); |
| 281 | } | 281 | } |
| 282 | |||
| 283 | /// Copies `m` to newly allocated memory. Caller owns the memory. | ||
| 284 | pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T { | ||
| 285 | const new_buf = try allocator.alloc(T, m.len); | ||
| 286 | copy(T, new_buf, m); | ||
| 287 | return new_buf; | ||
| 288 | } | ||
| 289 | |||
| 290 | /// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. | ||
| 291 | pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T { | ||
| 292 | const new_buf = try allocator.alloc(T, m.len + 1); | ||
| 293 | copy(T, new_buf, m); | ||
| 294 | new_buf[m.len] = 0; | ||
| 295 | return new_buf[0..m.len :0]; | ||
| 296 | } | ||
| 282 | }; | 297 | }; |
| 283 | 298 | ||
| 284 | var failAllocator = Allocator { | 299 | var failAllocator = Allocator { |
| ... | @@ -785,19 +800,14 @@ pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool { | ... | @@ -785,19 +800,14 @@ pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool { |
| 785 | return true; | 800 | return true; |
| 786 | } | 801 | } |
| 787 | 802 | ||
| 788 | /// Copies `m` to newly allocated memory. Caller owns the memory. | 803 | /// Deprecated, use `Allocator.dupe`. |
| 789 | pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T { | 804 | pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T { |
| 790 | const new_buf = try allocator.alloc(T, m.len); | 805 | return allocator.dupe(T, m); |
| 791 | copy(T, new_buf, m); | ||
| 792 | return new_buf; | ||
| 793 | } | 806 | } |
| 794 | 807 | ||
| 795 | /// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. | 808 | /// Deprecated, use `Allocator.dupeZ`. |
| 796 | pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T { | 809 | pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T { |
| 797 | const new_buf = try allocator.alloc(T, m.len + 1); | 810 | return allocator.dupeZ(T, m); |
| 798 | copy(T, new_buf, m); | ||
| 799 | new_buf[m.len] = 0; | ||
| 800 | return new_buf[0..m.len :0]; | ||
| 801 | } | 811 | } |
| 802 | 812 | ||
| 803 | /// Remove values from the beginning of a slice. | 813 | /// Remove values from the beginning of a slice. |
| ... | @@ -2112,7 +2122,11 @@ pub fn alignBackwardGeneric(comptime T: type, addr: T, alignment: T) T { | ... | @@ -2112,7 +2122,11 @@ pub fn alignBackwardGeneric(comptime T: type, addr: T, alignment: T) T { |
| 2112 | /// Given an address and an alignment, return true if the address is a multiple of the alignment | 2122 | /// Given an address and an alignment, return true if the address is a multiple of the alignment |
| 2113 | /// The alignment must be a power of 2 and greater than 0. | 2123 | /// The alignment must be a power of 2 and greater than 0. |
| 2114 | pub fn isAligned(addr: usize, alignment: usize) bool { | 2124 | pub fn isAligned(addr: usize, alignment: usize) bool { |
| 2115 | return alignBackward(addr, alignment) == addr; | 2125 | return isAlignedGeneric(u64, addr, alignment); |
| 2126 | } | ||
| 2127 | |||
| 2128 | pub fn isAlignedGeneric(comptime T: type, addr: T, alignment: T) bool { | ||
| 2129 | return alignBackwardGeneric(T, addr, alignment) == addr; | ||
| 2116 | } | 2130 | } |
| 2117 | 2131 | ||
| 2118 | test "isAligned" { | 2132 | test "isAligned" { |
lib/std/std.zig+3-1| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | pub const AlignedArrayList = @import("array_list.zig").AlignedArrayList; | ||
| 2 | pub const ArrayList = @import("array_list.zig").ArrayList; | 1 | pub const ArrayList = @import("array_list.zig").ArrayList; |
| 2 | pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned; | ||
| 3 | pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged; | ||
| 3 | pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled; | 4 | pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled; |
| 5 | pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged; | ||
| 4 | pub const AutoHashMap = @import("hash_map.zig").AutoHashMap; | 6 | pub const AutoHashMap = @import("hash_map.zig").AutoHashMap; |
| 5 | pub const BloomFilter = @import("bloom_filter.zig").BloomFilter; | 7 | pub const BloomFilter = @import("bloom_filter.zig").BloomFilter; |
| 6 | pub const BufMap = @import("buf_map.zig").BufMap; | 8 | pub const BufMap = @import("buf_map.zig").BufMap; |
src-self-hosted/Module.zig created+2091| ... | @@ -0,0 +1,2091 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const mem = std.mem; | ||
| 3 | const Allocator = std.mem.Allocator; | ||
| 4 | const ArrayListUnmanaged = std.ArrayListUnmanaged; | ||
| 5 | const Value = @import("value.zig").Value; | ||
| 6 | const Type = @import("type.zig").Type; | ||
| 7 | const TypedValue = @import("TypedValue.zig"); | ||
| 8 | const assert = std.debug.assert; | ||
| 9 | const BigIntConst = std.math.big.int.Const; | ||
| 10 | const BigIntMutable = std.math.big.int.Mutable; | ||
| 11 | const Target = std.Target; | ||
| 12 | const Package = @import("Package.zig"); | ||
| 13 | const link = @import("link.zig"); | ||
| 14 | const ir = @import("ir.zig"); | ||
| 15 | const zir = @import("zir.zig"); | ||
| 16 | const Module = @This(); | ||
| 17 | const Inst = ir.Inst; | ||
| 18 | |||
| 19 | /// General-purpose allocator. | ||
| 20 | allocator: *Allocator, | ||
| 21 | /// Pointer to externally managed resource. | ||
| 22 | root_pkg: *Package, | ||
| 23 | /// Module owns this resource. | ||
| 24 | root_scope: *Scope.ZIRModule, | ||
| 25 | bin_file: link.ElfFile, | ||
| 26 | bin_file_dir: std.fs.Dir, | ||
| 27 | bin_file_path: []const u8, | ||
| 28 | /// It's rare for a decl to be exported, so we save memory by having a sparse map of | ||
| 29 | /// Decl pointers to details about them being exported. | ||
| 30 | /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. | ||
| 31 | decl_exports: std.AutoHashMap(*Decl, []*Export), | ||
| 32 | /// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl | ||
| 33 | /// is modified. Note that the key of this table is not the Decl being exported, but the Decl that | ||
| 34 | /// is performing the export of another Decl. | ||
| 35 | /// This table owns the Export memory. | ||
| 36 | export_owners: std.AutoHashMap(*Decl, []*Export), | ||
| 37 | /// Maps fully qualified namespaced names to the Decl struct for them. | ||
| 38 | decl_table: std.AutoHashMap(Decl.Hash, *Decl), | ||
| 39 | |||
| 40 | optimize_mode: std.builtin.Mode, | ||
| 41 | link_error_flags: link.ElfFile.ErrorFlags = link.ElfFile.ErrorFlags{}, | ||
| 42 | |||
| 43 | work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), | ||
| 44 | |||
| 45 | /// We optimize memory usage for a compilation with no compile errors by storing the | ||
| 46 | /// error messages and mapping outside of `Decl`. | ||
| 47 | /// The ErrorMsg memory is owned by the decl, using Module's allocator. | ||
| 48 | /// Note that a Decl can succeed but the Fn it represents can fail. In this case, | ||
| 49 | /// a Decl can have a failed_decls entry but have analysis status of success. | ||
| 50 | failed_decls: std.AutoHashMap(*Decl, *ErrorMsg), | ||
| 51 | /// Using a map here for consistency with the other fields here. | ||
| 52 | /// The ErrorMsg memory is owned by the `Scope.ZIRModule`, using Module's allocator. | ||
| 53 | failed_files: std.AutoHashMap(*Scope.ZIRModule, *ErrorMsg), | ||
| 54 | /// Using a map here for consistency with the other fields here. | ||
| 55 | /// The ErrorMsg memory is owned by the `Export`, using Module's allocator. | ||
| 56 | failed_exports: std.AutoHashMap(*Export, *ErrorMsg), | ||
| 57 | |||
| 58 | pub const WorkItem = union(enum) { | ||
| 59 | /// Write the machine code for a Decl to the output file. | ||
| 60 | codegen_decl: *Decl, | ||
| 61 | }; | ||
| 62 | |||
| 63 | pub const Export = struct { | ||
| 64 | options: std.builtin.ExportOptions, | ||
| 65 | /// Byte offset into the file that contains the export directive. | ||
| 66 | src: usize, | ||
| 67 | /// Represents the position of the export, if any, in the output file. | ||
| 68 | link: link.ElfFile.Export, | ||
| 69 | /// The Decl that performs the export. Note that this is *not* the Decl being exported. | ||
| 70 | owner_decl: *Decl, | ||
| 71 | status: enum { | ||
| 72 | in_progress, | ||
| 73 | failed, | ||
| 74 | /// Indicates that the failure was due to a temporary issue, such as an I/O error | ||
| 75 | /// when writing to the output file. Retrying the export may succeed. | ||
| 76 | failed_retryable, | ||
| 77 | complete, | ||
| 78 | }, | ||
| 79 | }; | ||
| 80 | |||
| 81 | pub const Decl = struct { | ||
| 82 | /// This name is relative to the containing namespace of the decl. It uses a null-termination | ||
| 83 | /// to save bytes, since there can be a lot of decls in a compilation. The null byte is not allowed | ||
| 84 | /// in symbol names, because executable file formats use null-terminated strings for symbol names. | ||
| 85 | /// All Decls have names, even values that are not bound to a zig namespace. This is necessary for | ||
| 86 | /// mapping them to an address in the output file. | ||
| 87 | /// Memory owned by this decl, using Module's allocator. | ||
| 88 | name: [*:0]const u8, | ||
| 89 | /// The direct parent container of the Decl. This field will need to get more fleshed out when | ||
| 90 | /// self-hosted supports proper struct types and Zig AST => ZIR. | ||
| 91 | /// Reference to externally owned memory. | ||
| 92 | scope: *Scope.ZIRModule, | ||
| 93 | /// Byte offset into the source file that contains this declaration. | ||
| 94 | /// This is the base offset that src offsets within this Decl are relative to. | ||
| 95 | src: usize, | ||
| 96 | /// The most recent value of the Decl after a successful semantic analysis. | ||
| 97 | /// The tag for this union is determined by the tag value of the analysis field. | ||
| 98 | typed_value: union { | ||
| 99 | never_succeeded: void, | ||
| 100 | most_recent: TypedValue.Managed, | ||
| 101 | }, | ||
| 102 | /// Represents the "shallow" analysis status. For example, for decls that are functions, | ||
| 103 | /// the function type is analyzed with this set to `in_progress`, however, the semantic | ||
| 104 | /// analysis of the function body is performed with this value set to `success`. Functions | ||
| 105 | /// have their own analysis status field. | ||
| 106 | analysis: enum { | ||
| 107 | initial_in_progress, | ||
| 108 | /// This Decl might be OK but it depends on another one which did not successfully complete | ||
| 109 | /// semantic analysis. This Decl never had a value computed. | ||
| 110 | initial_dependency_failure, | ||
| 111 | /// Semantic analysis failure. This Decl never had a value computed. | ||
| 112 | /// There will be a corresponding ErrorMsg in Module.failed_decls. | ||
| 113 | initial_sema_failure, | ||
| 114 | /// In this case the `typed_value.most_recent` can still be accessed. | ||
| 115 | /// There will be a corresponding ErrorMsg in Module.failed_decls. | ||
| 116 | codegen_failure, | ||
| 117 | /// In this case the `typed_value.most_recent` can still be accessed. | ||
| 118 | /// There will be a corresponding ErrorMsg in Module.failed_decls. | ||
| 119 | /// This indicates the failure was something like running out of disk space, | ||
| 120 | /// and attempting codegen again may succeed. | ||
| 121 | codegen_failure_retryable, | ||
| 122 | /// This Decl might be OK but it depends on another one which did not successfully complete | ||
| 123 | /// semantic analysis. There is a most recent value available. | ||
| 124 | repeat_dependency_failure, | ||
| 125 | /// Semantic anlaysis failure, but the `typed_value.most_recent` can be accessed. | ||
| 126 | /// There will be a corresponding ErrorMsg in Module.failed_decls. | ||
| 127 | repeat_sema_failure, | ||
| 128 | /// Completed successfully before; the `typed_value.most_recent` can be accessed, and | ||
| 129 | /// new semantic analysis is in progress. | ||
| 130 | repeat_in_progress, | ||
| 131 | /// Everything is done and updated. | ||
| 132 | complete, | ||
| 133 | }, | ||
| 134 | |||
| 135 | /// Represents the position of the code in the output file. | ||
| 136 | /// This is populated regardless of semantic analysis and code generation. | ||
| 137 | link: link.ElfFile.Decl = link.ElfFile.Decl.empty, | ||
| 138 | |||
| 139 | /// The shallow set of other decls whose typed_value could possibly change if this Decl's | ||
| 140 | /// typed_value is modified. | ||
| 141 | /// TODO look into using a lightweight map/set data structure rather than a linear array. | ||
| 142 | dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, | ||
| 143 | |||
| 144 | contents_hash: Hash, | ||
| 145 | |||
| 146 | pub fn destroy(self: *Decl, allocator: *Allocator) void { | ||
| 147 | allocator.free(mem.spanZ(self.name)); | ||
| 148 | if (self.typedValueManaged()) |tvm| { | ||
| 149 | tvm.deinit(allocator); | ||
| 150 | } | ||
| 151 | allocator.destroy(self); | ||
| 152 | } | ||
| 153 | |||
| 154 | pub const Hash = [16]u8; | ||
| 155 | |||
| 156 | /// If the name is small enough, it is used directly as the hash. | ||
| 157 | /// If it is long, blake3 hash is computed. | ||
| 158 | pub fn hashSimpleName(name: []const u8) Hash { | ||
| 159 | var out: Hash = undefined; | ||
| 160 | if (name.len <= Hash.len) { | ||
| 161 | mem.copy(u8, &out, name); | ||
| 162 | mem.set(u8, out[name.len..], 0); | ||
| 163 | } else { | ||
| 164 | std.crypto.Blake3.hash(name, &out); | ||
| 165 | } | ||
| 166 | return out; | ||
| 167 | } | ||
| 168 | |||
| 169 | /// Must generate unique bytes with no collisions with other decls. | ||
| 170 | /// The point of hashing here is only to limit the number of bytes of | ||
| 171 | /// the unique identifier to a fixed size (16 bytes). | ||
| 172 | pub fn fullyQualifiedNameHash(self: Decl) Hash { | ||
| 173 | // Right now we only have ZIRModule as the source. So this is simply the | ||
| 174 | // relative name of the decl. | ||
| 175 | return hashSimpleName(mem.spanZ(u8, self.name)); | ||
| 176 | } | ||
| 177 | |||
| 178 | pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue { | ||
| 179 | const tvm = self.typedValueManaged() orelse return error.AnalysisFail; | ||
| 180 | return tvm.typed_value; | ||
| 181 | } | ||
| 182 | |||
| 183 | pub fn value(self: *Decl) error{AnalysisFail}!Value { | ||
| 184 | return (try self.typedValue()).val; | ||
| 185 | } | ||
| 186 | |||
| 187 | pub fn dump(self: *Decl) void { | ||
| 188 | const loc = std.zig.findLineColumn(self.scope.source.bytes, self.src); | ||
| 189 | std.debug.warn("{}:{}:{} name={} status={}", .{ | ||
| 190 | self.scope.sub_file_path, | ||
| 191 | loc.line + 1, | ||
| 192 | loc.column + 1, | ||
| 193 | mem.spanZ(self.name), | ||
| 194 | @tagName(self.analysis), | ||
| 195 | }); | ||
| 196 | if (self.typedValueManaged()) |tvm| { | ||
| 197 | std.debug.warn(" ty={} val={}", .{ tvm.typed_value.ty, tvm.typed_value.val }); | ||
| 198 | } | ||
| 199 | std.debug.warn("\n", .{}); | ||
| 200 | } | ||
| 201 | |||
| 202 | fn typedValueManaged(self: *Decl) ?*TypedValue.Managed { | ||
| 203 | switch (self.analysis) { | ||
| 204 | .initial_in_progress, | ||
| 205 | .initial_dependency_failure, | ||
| 206 | .initial_sema_failure, | ||
| 207 | => return null, | ||
| 208 | .codegen_failure, | ||
| 209 | .codegen_failure_retryable, | ||
| 210 | .repeat_dependency_failure, | ||
| 211 | .repeat_sema_failure, | ||
| 212 | .repeat_in_progress, | ||
| 213 | .complete, | ||
| 214 | => return &self.typed_value.most_recent, | ||
| 215 | } | ||
| 216 | } | ||
| 217 | }; | ||
| 218 | |||
| 219 | /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. | ||
| 220 | pub const Fn = struct { | ||
| 221 | /// This memory owned by the Decl's TypedValue.Managed arena allocator. | ||
| 222 | fn_type: Type, | ||
| 223 | analysis: union(enum) { | ||
| 224 | /// The value is the source instruction. | ||
| 225 | queued: *zir.Inst.Fn, | ||
| 226 | in_progress: *Analysis, | ||
| 227 | /// There will be a corresponding ErrorMsg in Module.failed_decls | ||
| 228 | sema_failure, | ||
| 229 | /// This Fn might be OK but it depends on another Decl which did not successfully complete | ||
| 230 | /// semantic analysis. | ||
| 231 | dependency_failure, | ||
| 232 | success: Body, | ||
| 233 | }, | ||
| 234 | |||
| 235 | /// This memory is temporary and points to stack memory for the duration | ||
| 236 | /// of Fn analysis. | ||
| 237 | pub const Analysis = struct { | ||
| 238 | inner_block: Scope.Block, | ||
| 239 | /// TODO Performance optimization idea: instead of this inst_table, | ||
| 240 | /// use a field in the zir.Inst instead to track corresponding instructions | ||
| 241 | inst_table: std.AutoHashMap(*zir.Inst, *Inst), | ||
| 242 | needed_inst_capacity: usize, | ||
| 243 | }; | ||
| 244 | }; | ||
| 245 | |||
| 246 | pub const Scope = struct { | ||
| 247 | tag: Tag, | ||
| 248 | |||
| 249 | pub fn cast(base: *Scope, comptime T: type) ?*T { | ||
| 250 | if (base.tag != T.base_tag) | ||
| 251 | return null; | ||
| 252 | |||
| 253 | return @fieldParentPtr(T, "base", base); | ||
| 254 | } | ||
| 255 | |||
| 256 | /// Asserts the scope has a parent which is a DeclAnalysis and | ||
| 257 | /// returns the arena Allocator. | ||
| 258 | pub fn arena(self: *Scope) *Allocator { | ||
| 259 | switch (self.tag) { | ||
| 260 | .block => return self.cast(Block).?.arena, | ||
| 261 | .decl => return &self.cast(DeclAnalysis).?.arena.allocator, | ||
| 262 | .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | /// Asserts the scope has a parent which is a DeclAnalysis and | ||
| 267 | /// returns the Decl. | ||
| 268 | pub fn decl(self: *Scope) *Decl { | ||
| 269 | switch (self.tag) { | ||
| 270 | .block => return self.cast(Block).?.decl, | ||
| 271 | .decl => return self.cast(DeclAnalysis).?.decl, | ||
| 272 | .zir_module => unreachable, | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | /// Asserts the scope has a parent which is a ZIRModule and | ||
| 277 | /// returns it. | ||
| 278 | pub fn namespace(self: *Scope) *ZIRModule { | ||
| 279 | switch (self.tag) { | ||
| 280 | .block => return self.cast(Block).?.decl.scope, | ||
| 281 | .decl => return self.cast(DeclAnalysis).?.decl.scope, | ||
| 282 | .zir_module => return self.cast(ZIRModule).?, | ||
| 283 | } | ||
| 284 | } | ||
| 285 | |||
| 286 | pub fn dumpInst(self: *Scope, inst: *Inst) void { | ||
| 287 | const zir_module = self.namespace(); | ||
| 288 | const loc = std.zig.findLineColumn(zir_module.source.bytes, inst.src); | ||
| 289 | std.debug.warn("{}:{}:{}: {}: ty={}\n", .{ | ||
| 290 | zir_module.sub_file_path, | ||
| 291 | loc.line + 1, | ||
| 292 | loc.column + 1, | ||
| 293 | @tagName(inst.tag), | ||
| 294 | inst.ty, | ||
| 295 | }); | ||
| 296 | } | ||
| 297 | |||
| 298 | pub const Tag = enum { | ||
| 299 | zir_module, | ||
| 300 | block, | ||
| 301 | decl, | ||
| 302 | }; | ||
| 303 | |||
| 304 | pub const ZIRModule = struct { | ||
| 305 | pub const base_tag: Tag = .zir_module; | ||
| 306 | base: Scope = Scope{ .tag = base_tag }, | ||
| 307 | /// Relative to the owning package's root_src_dir. | ||
| 308 | /// Reference to external memory, not owned by ZIRModule. | ||
| 309 | sub_file_path: []const u8, | ||
| 310 | source: union(enum) { | ||
| 311 | unloaded: void, | ||
| 312 | bytes: [:0]const u8, | ||
| 313 | }, | ||
| 314 | contents: union { | ||
| 315 | not_available: void, | ||
| 316 | module: *zir.Module, | ||
| 317 | }, | ||
| 318 | status: enum { | ||
| 319 | never_loaded, | ||
| 320 | unloaded_success, | ||
| 321 | unloaded_parse_failure, | ||
| 322 | unloaded_sema_failure, | ||
| 323 | |||
| 324 | loaded_sema_failure, | ||
| 325 | loaded_success, | ||
| 326 | }, | ||
| 327 | |||
| 328 | pub fn unload(self: *ZIRModule, allocator: *Allocator) void { | ||
| 329 | switch (self.status) { | ||
| 330 | .never_loaded, | ||
| 331 | .unloaded_parse_failure, | ||
| 332 | .unloaded_sema_failure, | ||
| 333 | .unloaded_success, | ||
| 334 | => {}, | ||
| 335 | |||
| 336 | .loaded_success => { | ||
| 337 | self.contents.module.deinit(allocator); | ||
| 338 | allocator.destroy(self.contents.module); | ||
| 339 | self.status = .unloaded_success; | ||
| 340 | }, | ||
| 341 | .loaded_sema_failure => { | ||
| 342 | self.contents.module.deinit(allocator); | ||
| 343 | allocator.destroy(self.contents.module); | ||
| 344 | self.status = .unloaded_sema_failure; | ||
| 345 | }, | ||
| 346 | } | ||
| 347 | switch (self.source) { | ||
| 348 | .bytes => |bytes| { | ||
| 349 | allocator.free(bytes); | ||
| 350 | self.source = .{ .unloaded = {} }; | ||
| 351 | }, | ||
| 352 | .unloaded => {}, | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | pub fn deinit(self: *ZIRModule, allocator: *Allocator) void { | ||
| 357 | self.unload(allocator); | ||
| 358 | self.* = undefined; | ||
| 359 | } | ||
| 360 | |||
| 361 | pub fn dumpSrc(self: *ZIRModule, src: usize) void { | ||
| 362 | const loc = std.zig.findLineColumn(self.source.bytes, src); | ||
| 363 | std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); | ||
| 364 | } | ||
| 365 | }; | ||
| 366 | |||
| 367 | /// This is a temporary structure, references to it are valid only | ||
| 368 | /// during semantic analysis of the block. | ||
| 369 | pub const Block = struct { | ||
| 370 | pub const base_tag: Tag = .block; | ||
| 371 | base: Scope = Scope{ .tag = base_tag }, | ||
| 372 | func: *Fn, | ||
| 373 | decl: *Decl, | ||
| 374 | instructions: ArrayListUnmanaged(*Inst), | ||
| 375 | /// Points to the arena allocator of DeclAnalysis | ||
| 376 | arena: *Allocator, | ||
| 377 | }; | ||
| 378 | |||
| 379 | /// This is a temporary structure, references to it are valid only | ||
| 380 | /// during semantic analysis of the decl. | ||
| 381 | pub const DeclAnalysis = struct { | ||
| 382 | pub const base_tag: Tag = .decl; | ||
| 383 | base: Scope = Scope{ .tag = base_tag }, | ||
| 384 | decl: *Decl, | ||
| 385 | arena: std.heap.ArenaAllocator, | ||
| 386 | }; | ||
| 387 | }; | ||
| 388 | |||
| 389 | pub const Body = struct { | ||
| 390 | instructions: []*Inst, | ||
| 391 | }; | ||
| 392 | |||
| 393 | pub const AllErrors = struct { | ||
| 394 | arena: std.heap.ArenaAllocator.State, | ||
| 395 | list: []const Message, | ||
| 396 | |||
| 397 | pub const Message = struct { | ||
| 398 | src_path: []const u8, | ||
| 399 | line: usize, | ||
| 400 | column: usize, | ||
| 401 | byte_offset: usize, | ||
| 402 | msg: []const u8, | ||
| 403 | }; | ||
| 404 | |||
| 405 | pub fn deinit(self: *AllErrors, allocator: *Allocator) void { | ||
| 406 | self.arena.promote(allocator).deinit(); | ||
| 407 | } | ||
| 408 | |||
| 409 | fn add( | ||
| 410 | arena: *std.heap.ArenaAllocator, | ||
| 411 | errors: *std.ArrayList(Message), | ||
| 412 | sub_file_path: []const u8, | ||
| 413 | source: []const u8, | ||
| 414 | simple_err_msg: ErrorMsg, | ||
| 415 | ) !void { | ||
| 416 | const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset); | ||
| 417 | try errors.append(.{ | ||
| 418 | .src_path = try arena.allocator.dupe(u8, sub_file_path), | ||
| 419 | .msg = try arena.allocator.dupe(u8, simple_err_msg.msg), | ||
| 420 | .byte_offset = simple_err_msg.byte_offset, | ||
| 421 | .line = loc.line, | ||
| 422 | .column = loc.column, | ||
| 423 | }); | ||
| 424 | } | ||
| 425 | }; | ||
| 426 | |||
| 427 | pub const InitOptions = struct { | ||
| 428 | target: std.Target, | ||
| 429 | root_pkg: *Package, | ||
| 430 | output_mode: std.builtin.OutputMode, | ||
| 431 | bin_file_dir: ?std.fs.Dir = null, | ||
| 432 | bin_file_path: []const u8, | ||
| 433 | link_mode: ?std.builtin.LinkMode = null, | ||
| 434 | object_format: ?std.builtin.ObjectFormat = null, | ||
| 435 | optimize_mode: std.builtin.Mode = .Debug, | ||
| 436 | }; | ||
| 437 | |||
| 438 | pub fn init(gpa: *Allocator, options: InitOptions) !Module { | ||
| 439 | const root_scope = try gpa.create(Scope.ZIRModule); | ||
| 440 | errdefer gpa.destroy(root_scope); | ||
| 441 | |||
| 442 | root_scope.* = .{ | ||
| 443 | .sub_file_path = options.root_pkg.root_src_path, | ||
| 444 | .source = .{ .unloaded = {} }, | ||
| 445 | .contents = .{ .not_available = {} }, | ||
| 446 | .status = .never_loaded, | ||
| 447 | }; | ||
| 448 | |||
| 449 | const bin_file_dir = options.bin_file_dir orelse std.fs.cwd(); | ||
| 450 | var bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{ | ||
| 451 | .target = options.target, | ||
| 452 | .output_mode = options.output_mode, | ||
| 453 | .link_mode = options.link_mode orelse .Static, | ||
| 454 | .object_format = options.object_format orelse options.target.getObjectFormat(), | ||
| 455 | }); | ||
| 456 | errdefer bin_file.deinit(); | ||
| 457 | |||
| 458 | return Module{ | ||
| 459 | .allocator = gpa, | ||
| 460 | .root_pkg = options.root_pkg, | ||
| 461 | .root_scope = root_scope, | ||
| 462 | .bin_file_dir = bin_file_dir, | ||
| 463 | .bin_file_path = options.bin_file_path, | ||
| 464 | .bin_file = bin_file, | ||
| 465 | .optimize_mode = options.optimize_mode, | ||
| 466 | .decl_table = std.AutoHashMap(Decl.Hash, *Decl).init(gpa), | ||
| 467 | .decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa), | ||
| 468 | .export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa), | ||
| 469 | .failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa), | ||
| 470 | .failed_files = std.AutoHashMap(*Scope.ZIRModule, *ErrorMsg).init(gpa), | ||
| 471 | .failed_exports = std.AutoHashMap(*Export, *ErrorMsg).init(gpa), | ||
| 472 | .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa), | ||
| 473 | }; | ||
| 474 | } | ||
| 475 | |||
| 476 | pub fn deinit(self: *Module) void { | ||
| 477 | self.bin_file.deinit(); | ||
| 478 | const allocator = self.allocator; | ||
| 479 | self.work_queue.deinit(); | ||
| 480 | { | ||
| 481 | var it = self.decl_table.iterator(); | ||
| 482 | while (it.next()) |kv| { | ||
| 483 | kv.value.destroy(allocator); | ||
| 484 | } | ||
| 485 | self.decl_table.deinit(); | ||
| 486 | } | ||
| 487 | { | ||
| 488 | var it = self.failed_decls.iterator(); | ||
| 489 | while (it.next()) |kv| { | ||
| 490 | kv.value.destroy(allocator); | ||
| 491 | } | ||
| 492 | self.failed_decls.deinit(); | ||
| 493 | } | ||
| 494 | { | ||
| 495 | var it = self.failed_files.iterator(); | ||
| 496 | while (it.next()) |kv| { | ||
| 497 | kv.value.destroy(allocator); | ||
| 498 | } | ||
| 499 | self.failed_files.deinit(); | ||
| 500 | } | ||
| 501 | { | ||
| 502 | var it = self.failed_exports.iterator(); | ||
| 503 | while (it.next()) |kv| { | ||
| 504 | kv.value.destroy(allocator); | ||
| 505 | } | ||
| 506 | self.failed_exports.deinit(); | ||
| 507 | } | ||
| 508 | { | ||
| 509 | var it = self.decl_exports.iterator(); | ||
| 510 | while (it.next()) |kv| { | ||
| 511 | const export_list = kv.value; | ||
| 512 | allocator.free(export_list); | ||
| 513 | } | ||
| 514 | self.decl_exports.deinit(); | ||
| 515 | } | ||
| 516 | { | ||
| 517 | var it = self.export_owners.iterator(); | ||
| 518 | while (it.next()) |kv| { | ||
| 519 | const export_list = kv.value; | ||
| 520 | for (export_list) |exp| { | ||
| 521 | allocator.destroy(exp); | ||
| 522 | } | ||
| 523 | allocator.free(export_list); | ||
| 524 | } | ||
| 525 | self.export_owners.deinit(); | ||
| 526 | } | ||
| 527 | { | ||
| 528 | self.root_scope.deinit(allocator); | ||
| 529 | allocator.destroy(self.root_scope); | ||
| 530 | } | ||
| 531 | self.* = undefined; | ||
| 532 | } | ||
| 533 | |||
| 534 | pub fn target(self: Module) std.Target { | ||
| 535 | return self.bin_file.options.target; | ||
| 536 | } | ||
| 537 | |||
| 538 | /// Detect changes to source files, perform semantic analysis, and update the output files. | ||
| 539 | pub fn update(self: *Module) !void { | ||
| 540 | // TODO Use the cache hash file system to detect which source files changed. | ||
| 541 | // Here we simulate a full cache miss. | ||
| 542 | // Analyze the root source file now. | ||
| 543 | self.analyzeRoot(self.root_scope) catch |err| switch (err) { | ||
| 544 | error.AnalysisFail => { | ||
| 545 | assert(self.totalErrorCount() != 0); | ||
| 546 | }, | ||
| 547 | else => |e| return e, | ||
| 548 | }; | ||
| 549 | |||
| 550 | try self.performAllTheWork(); | ||
| 551 | |||
| 552 | // Unload all the source files from memory. | ||
| 553 | self.root_scope.unload(self.allocator); | ||
| 554 | |||
| 555 | try self.bin_file.flush(); | ||
| 556 | self.link_error_flags = self.bin_file.error_flags; | ||
| 557 | } | ||
| 558 | |||
| 559 | /// Having the file open for writing is problematic as far as executing the | ||
| 560 | /// binary is concerned. This will remove the write flag, or close the file, | ||
| 561 | /// or whatever is needed so that it can be executed. | ||
| 562 | /// After this, one must call` makeFileWritable` before calling `update`. | ||
| 563 | pub fn makeBinFileExecutable(self: *Module) !void { | ||
| 564 | return self.bin_file.makeExecutable(); | ||
| 565 | } | ||
| 566 | |||
| 567 | pub fn makeBinFileWritable(self: *Module) !void { | ||
| 568 | return self.bin_file.makeWritable(self.bin_file_dir, self.bin_file_path); | ||
| 569 | } | ||
| 570 | |||
| 571 | pub fn totalErrorCount(self: *Module) usize { | ||
| 572 | return self.failed_decls.size + | ||
| 573 | self.failed_files.size + | ||
| 574 | self.failed_exports.size + | ||
| 575 | @boolToInt(self.link_error_flags.no_entry_point_found); | ||
| 576 | } | ||
| 577 | |||
| 578 | pub fn getAllErrorsAlloc(self: *Module) !AllErrors { | ||
| 579 | var arena = std.heap.ArenaAllocator.init(self.allocator); | ||
| 580 | errdefer arena.deinit(); | ||
| 581 | |||
| 582 | var errors = std.ArrayList(AllErrors.Message).init(self.allocator); | ||
| 583 | defer errors.deinit(); | ||
| 584 | |||
| 585 | { | ||
| 586 | var it = self.failed_files.iterator(); | ||
| 587 | while (it.next()) |kv| { | ||
| 588 | const scope = kv.key; | ||
| 589 | const err_msg = kv.value; | ||
| 590 | const source = try self.getSource(scope); | ||
| 591 | try AllErrors.add(&arena, &errors, scope.sub_file_path, source, err_msg.*); | ||
| 592 | } | ||
| 593 | } | ||
| 594 | { | ||
| 595 | var it = self.failed_decls.iterator(); | ||
| 596 | while (it.next()) |kv| { | ||
| 597 | const decl = kv.key; | ||
| 598 | const err_msg = kv.value; | ||
| 599 | const source = try self.getSource(decl.scope); | ||
| 600 | try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg.*); | ||
| 601 | } | ||
| 602 | } | ||
| 603 | { | ||
| 604 | var it = self.failed_exports.iterator(); | ||
| 605 | while (it.next()) |kv| { | ||
| 606 | const decl = kv.key.owner_decl; | ||
| 607 | const err_msg = kv.value; | ||
| 608 | const source = try self.getSource(decl.scope); | ||
| 609 | try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg.*); | ||
| 610 | } | ||
| 611 | } | ||
| 612 | |||
| 613 | if (self.link_error_flags.no_entry_point_found) { | ||
| 614 | try errors.append(.{ | ||
| 615 | .src_path = self.root_pkg.root_src_path, | ||
| 616 | .line = 0, | ||
| 617 | .column = 0, | ||
| 618 | .byte_offset = 0, | ||
| 619 | .msg = try std.fmt.allocPrint(&arena.allocator, "no entry point found", .{}), | ||
| 620 | }); | ||
| 621 | } | ||
| 622 | |||
| 623 | assert(errors.items.len == self.totalErrorCount()); | ||
| 624 | |||
| 625 | return AllErrors{ | ||
| 626 | .arena = arena.state, | ||
| 627 | .list = try arena.allocator.dupe(AllErrors.Message, errors.items), | ||
| 628 | }; | ||
| 629 | } | ||
| 630 | |||
| 631 | const InnerError = error{ OutOfMemory, AnalysisFail }; | ||
| 632 | |||
| 633 | pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { | ||
| 634 | while (self.work_queue.readItem()) |work_item| switch (work_item) { | ||
| 635 | .codegen_decl => |decl| switch (decl.analysis) { | ||
| 636 | .initial_in_progress, | ||
| 637 | .repeat_in_progress, | ||
| 638 | => unreachable, | ||
| 639 | |||
| 640 | .initial_sema_failure, | ||
| 641 | .repeat_sema_failure, | ||
| 642 | .codegen_failure, | ||
| 643 | .initial_dependency_failure, | ||
| 644 | .repeat_dependency_failure, | ||
| 645 | => continue, | ||
| 646 | |||
| 647 | .complete, .codegen_failure_retryable => { | ||
| 648 | if (decl.typed_value.most_recent.typed_value.val.cast(Value.Payload.Function)) |payload| { | ||
| 649 | switch (payload.func.analysis) { | ||
| 650 | .queued => self.analyzeFnBody(decl, payload.func) catch |err| switch (err) { | ||
| 651 | error.AnalysisFail => { | ||
| 652 | if (payload.func.analysis == .queued) { | ||
| 653 | payload.func.analysis = .dependency_failure; | ||
| 654 | } | ||
| 655 | continue; | ||
| 656 | }, | ||
| 657 | else => |e| return e, | ||
| 658 | }, | ||
| 659 | .in_progress => unreachable, | ||
| 660 | .sema_failure, .dependency_failure => continue, | ||
| 661 | .success => {}, | ||
| 662 | } | ||
| 663 | } | ||
| 664 | |||
| 665 | assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); | ||
| 666 | |||
| 667 | self.bin_file.updateDecl(self, decl) catch |err| switch (err) { | ||
| 668 | error.OutOfMemory => return error.OutOfMemory, | ||
| 669 | error.AnalysisFail => { | ||
| 670 | decl.analysis = .repeat_dependency_failure; | ||
| 671 | }, | ||
| 672 | else => { | ||
| 673 | try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); | ||
| 674 | self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( | ||
| 675 | self.allocator, | ||
| 676 | decl.src, | ||
| 677 | "unable to codegen: {}", | ||
| 678 | .{@errorName(err)}, | ||
| 679 | )); | ||
| 680 | decl.analysis = .codegen_failure_retryable; | ||
| 681 | }, | ||
| 682 | }; | ||
| 683 | }, | ||
| 684 | }, | ||
| 685 | }; | ||
| 686 | } | ||
| 687 | |||
| 688 | fn getSource(self: *Module, root_scope: *Scope.ZIRModule) ![:0]const u8 { | ||
| 689 | switch (root_scope.source) { | ||
| 690 | .unloaded => { | ||
| 691 | const source = try self.root_pkg.root_src_dir.readFileAllocOptions( | ||
| 692 | self.allocator, | ||
| 693 | root_scope.sub_file_path, | ||
| 694 | std.math.maxInt(u32), | ||
| 695 | 1, | ||
| 696 | 0, | ||
| 697 | ); | ||
| 698 | root_scope.source = .{ .bytes = source }; | ||
| 699 | return source; | ||
| 700 | }, | ||
| 701 | .bytes => |bytes| return bytes, | ||
| 702 | } | ||
| 703 | } | ||
| 704 | |||
| 705 | fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { | ||
| 706 | switch (root_scope.status) { | ||
| 707 | .never_loaded, .unloaded_success => { | ||
| 708 | try self.failed_files.ensureCapacity(self.failed_files.size + 1); | ||
| 709 | |||
| 710 | const source = try self.getSource(root_scope); | ||
| 711 | |||
| 712 | var keep_zir_module = false; | ||
| 713 | const zir_module = try self.allocator.create(zir.Module); | ||
| 714 | defer if (!keep_zir_module) self.allocator.destroy(zir_module); | ||
| 715 | |||
| 716 | zir_module.* = try zir.parse(self.allocator, source); | ||
| 717 | defer if (!keep_zir_module) zir_module.deinit(self.allocator); | ||
| 718 | |||
| 719 | if (zir_module.error_msg) |src_err_msg| { | ||
| 720 | self.failed_files.putAssumeCapacityNoClobber( | ||
| 721 | root_scope, | ||
| 722 | try ErrorMsg.create(self.allocator, src_err_msg.byte_offset, "{}", .{src_err_msg.msg}), | ||
| 723 | ); | ||
| 724 | root_scope.status = .unloaded_parse_failure; | ||
| 725 | return error.AnalysisFail; | ||
| 726 | } | ||
| 727 | |||
| 728 | root_scope.status = .loaded_success; | ||
| 729 | root_scope.contents = .{ .module = zir_module }; | ||
| 730 | keep_zir_module = true; | ||
| 731 | |||
| 732 | return zir_module; | ||
| 733 | }, | ||
| 734 | |||
| 735 | .unloaded_parse_failure, | ||
| 736 | .unloaded_sema_failure, | ||
| 737 | => return error.AnalysisFail, | ||
| 738 | |||
| 739 | .loaded_success, .loaded_sema_failure => return root_scope.contents.module, | ||
| 740 | } | ||
| 741 | } | ||
| 742 | |||
| 743 | fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { | ||
| 744 | // TODO use the cache to identify, from the modified source files, the decls which have | ||
| 745 | // changed based on the span of memory that represents the decl in the re-parsed source file. | ||
| 746 | // Use the cached dependency graph to recursively determine the set of decls which need | ||
| 747 | // regeneration. | ||
| 748 | // Here we simulate adding a source file which was previously not part of the compilation, | ||
| 749 | // which means scanning the decls looking for exports. | ||
| 750 | // TODO also identify decls that need to be deleted. | ||
| 751 | switch (root_scope.status) { | ||
| 752 | .never_loaded => { | ||
| 753 | const src_module = try self.getSrcModule(root_scope); | ||
| 754 | |||
| 755 | // Here we ensure enough queue capacity to store all the decls, so that later we can use | ||
| 756 | // appendAssumeCapacity. | ||
| 757 | try self.work_queue.ensureUnusedCapacity(src_module.decls.len); | ||
| 758 | |||
| 759 | for (src_module.decls) |decl| { | ||
| 760 | if (decl.cast(zir.Inst.Export)) |export_inst| { | ||
| 761 | _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.Decl.empty); | ||
| 762 | } | ||
| 763 | } | ||
| 764 | }, | ||
| 765 | |||
| 766 | .unloaded_parse_failure, | ||
| 767 | .unloaded_sema_failure, | ||
| 768 | .unloaded_success, | ||
| 769 | .loaded_sema_failure, | ||
| 770 | .loaded_success, | ||
| 771 | => { | ||
| 772 | const src_module = try self.getSrcModule(root_scope); | ||
| 773 | |||
| 774 | // Look for changed decls. | ||
| 775 | for (src_module.decls) |src_decl| { | ||
| 776 | const name_hash = Decl.hashSimpleName(src_decl.name); | ||
| 777 | if (self.decl_table.get(name_hash)) |kv| { | ||
| 778 | const decl = kv.value; | ||
| 779 | const new_contents_hash = Decl.hashSimpleName(src_decl.contents); | ||
| 780 | if (!mem.eql(u8, &new_contents_hash, &decl.contents_hash)) { | ||
| 781 | // TODO recursive dependency management | ||
| 782 | //std.debug.warn("noticed that '{}' changed\n", .{src_decl.name}); | ||
| 783 | self.decl_table.removeAssertDiscard(name_hash); | ||
| 784 | const saved_link = decl.link; | ||
| 785 | decl.destroy(self.allocator); | ||
| 786 | if (self.export_owners.getValue(decl)) |exports| { | ||
| 787 | @panic("TODO handle updating a decl that does an export"); | ||
| 788 | } | ||
| 789 | const new_decl = self.resolveDecl( | ||
| 790 | &root_scope.base, | ||
| 791 | src_decl, | ||
| 792 | saved_link, | ||
| 793 | ) catch |err| switch (err) { | ||
| 794 | error.OutOfMemory => return error.OutOfMemory, | ||
| 795 | error.AnalysisFail => continue, | ||
| 796 | }; | ||
| 797 | if (self.decl_exports.remove(decl)) |entry| { | ||
| 798 | self.decl_exports.putAssumeCapacityNoClobber(new_decl, entry.value); | ||
| 799 | } | ||
| 800 | } | ||
| 801 | } else if (src_decl.cast(zir.Inst.Export)) |export_inst| { | ||
| 802 | _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.Decl.empty); | ||
| 803 | } | ||
| 804 | } | ||
| 805 | }, | ||
| 806 | } | ||
| 807 | } | ||
| 808 | |||
| 809 | fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { | ||
| 810 | // Use the Decl's arena for function memory. | ||
| 811 | var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); | ||
| 812 | defer decl.typed_value.most_recent.arena.?.* = arena.state; | ||
| 813 | var analysis: Fn.Analysis = .{ | ||
| 814 | .inner_block = .{ | ||
| 815 | .func = func, | ||
| 816 | .decl = decl, | ||
| 817 | .instructions = .{}, | ||
| 818 | .arena = &arena.allocator, | ||
| 819 | }, | ||
| 820 | .needed_inst_capacity = 0, | ||
| 821 | .inst_table = std.AutoHashMap(*zir.Inst, *Inst).init(self.allocator), | ||
| 822 | }; | ||
| 823 | defer analysis.inner_block.instructions.deinit(self.allocator); | ||
| 824 | defer analysis.inst_table.deinit(); | ||
| 825 | |||
| 826 | const fn_inst = func.analysis.queued; | ||
| 827 | func.analysis = .{ .in_progress = &analysis }; | ||
| 828 | |||
| 829 | try self.analyzeBody(&analysis.inner_block.base, fn_inst.positionals.body); | ||
| 830 | |||
| 831 | func.analysis = .{ | ||
| 832 | .success = .{ | ||
| 833 | .instructions = try arena.allocator.dupe(*Inst, analysis.inner_block.instructions.items), | ||
| 834 | }, | ||
| 835 | }; | ||
| 836 | } | ||
| 837 | |||
| 838 | fn resolveDecl( | ||
| 839 | self: *Module, | ||
| 840 | scope: *Scope, | ||
| 841 | old_inst: *zir.Inst, | ||
| 842 | bin_file_link: link.ElfFile.Decl, | ||
| 843 | ) InnerError!*Decl { | ||
| 844 | const hash = Decl.hashSimpleName(old_inst.name); | ||
| 845 | if (self.decl_table.get(hash)) |kv| { | ||
| 846 | return kv.value; | ||
| 847 | } else { | ||
| 848 | const new_decl = blk: { | ||
| 849 | try self.decl_table.ensureCapacity(self.decl_table.size + 1); | ||
| 850 | const new_decl = try self.allocator.create(Decl); | ||
| 851 | errdefer self.allocator.destroy(new_decl); | ||
| 852 | const name = try mem.dupeZ(self.allocator, u8, old_inst.name); | ||
| 853 | errdefer self.allocator.free(name); | ||
| 854 | new_decl.* = .{ | ||
| 855 | .name = name, | ||
| 856 | .scope = scope.namespace(), | ||
| 857 | .src = old_inst.src, | ||
| 858 | .typed_value = .{ .never_succeeded = {} }, | ||
| 859 | .analysis = .initial_in_progress, | ||
| 860 | .contents_hash = Decl.hashSimpleName(old_inst.contents), | ||
| 861 | .link = bin_file_link, | ||
| 862 | }; | ||
| 863 | self.decl_table.putAssumeCapacityNoClobber(hash, new_decl); | ||
| 864 | break :blk new_decl; | ||
| 865 | }; | ||
| 866 | |||
| 867 | var decl_scope: Scope.DeclAnalysis = .{ | ||
| 868 | .decl = new_decl, | ||
| 869 | .arena = std.heap.ArenaAllocator.init(self.allocator), | ||
| 870 | }; | ||
| 871 | errdefer decl_scope.arena.deinit(); | ||
| 872 | |||
| 873 | const typed_value = self.analyzeInstConst(&decl_scope.base, old_inst) catch |err| switch (err) { | ||
| 874 | error.OutOfMemory => return error.OutOfMemory, | ||
| 875 | error.AnalysisFail => { | ||
| 876 | switch (new_decl.analysis) { | ||
| 877 | .initial_in_progress => new_decl.analysis = .initial_dependency_failure, | ||
| 878 | .repeat_in_progress => new_decl.analysis = .repeat_dependency_failure, | ||
| 879 | else => {}, | ||
| 880 | } | ||
| 881 | return error.AnalysisFail; | ||
| 882 | }, | ||
| 883 | }; | ||
| 884 | const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); | ||
| 885 | |||
| 886 | const has_codegen_bits = typed_value.ty.hasCodeGenBits(); | ||
| 887 | if (has_codegen_bits) { | ||
| 888 | // We don't fully codegen the decl until later, but we do need to reserve a global | ||
| 889 | // offset table index for it. This allows us to codegen decls out of dependency order, | ||
| 890 | // increasing how many computations can be done in parallel. | ||
| 891 | try self.bin_file.allocateDeclIndexes(new_decl); | ||
| 892 | } | ||
| 893 | |||
| 894 | arena_state.* = decl_scope.arena.state; | ||
| 895 | |||
| 896 | new_decl.typed_value = .{ | ||
| 897 | .most_recent = .{ | ||
| 898 | .typed_value = typed_value, | ||
| 899 | .arena = arena_state, | ||
| 900 | }, | ||
| 901 | }; | ||
| 902 | new_decl.analysis = .complete; | ||
| 903 | if (has_codegen_bits) { | ||
| 904 | // We ensureCapacity when scanning for decls. | ||
| 905 | self.work_queue.writeItemAssumeCapacity(.{ .codegen_decl = new_decl }); | ||
| 906 | } | ||
| 907 | return new_decl; | ||
| 908 | } | ||
| 909 | } | ||
| 910 | |||
| 911 | fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { | ||
| 912 | const decl = try self.resolveDecl(scope, old_inst, link.ElfFile.Decl.empty); | ||
| 913 | switch (decl.analysis) { | ||
| 914 | .initial_in_progress => unreachable, | ||
| 915 | .repeat_in_progress => unreachable, | ||
| 916 | .initial_dependency_failure, | ||
| 917 | .repeat_dependency_failure, | ||
| 918 | .initial_sema_failure, | ||
| 919 | .repeat_sema_failure, | ||
| 920 | .codegen_failure, | ||
| 921 | .codegen_failure_retryable, | ||
| 922 | => return error.AnalysisFail, | ||
| 923 | |||
| 924 | .complete => return decl, | ||
| 925 | } | ||
| 926 | } | ||
| 927 | |||
| 928 | fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { | ||
| 929 | if (scope.cast(Scope.Block)) |block| { | ||
| 930 | if (block.func.analysis.in_progress.inst_table.get(old_inst)) |kv| { | ||
| 931 | return kv.value; | ||
| 932 | } | ||
| 933 | } | ||
| 934 | |||
| 935 | const decl = try self.resolveCompleteDecl(scope, old_inst); | ||
| 936 | const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl); | ||
| 937 | return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); | ||
| 938 | } | ||
| 939 | |||
| 940 | fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { | ||
| 941 | return scope.cast(Scope.Block) orelse | ||
| 942 | return self.fail(scope, src, "instruction illegal outside function body", .{}); | ||
| 943 | } | ||
| 944 | |||
| 945 | fn resolveInstConst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { | ||
| 946 | const new_inst = try self.resolveInst(scope, old_inst); | ||
| 947 | const val = try self.resolveConstValue(scope, new_inst); | ||
| 948 | return TypedValue{ | ||
| 949 | .ty = new_inst.ty, | ||
| 950 | .val = val, | ||
| 951 | }; | ||
| 952 | } | ||
| 953 | |||
| 954 | fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value { | ||
| 955 | return (try self.resolveDefinedValue(scope, base)) orelse | ||
| 956 | return self.fail(scope, base.src, "unable to resolve comptime value", .{}); | ||
| 957 | } | ||
| 958 | |||
| 959 | fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value { | ||
| 960 | if (base.value()) |val| { | ||
| 961 | if (val.isUndef()) { | ||
| 962 | return self.fail(scope, base.src, "use of undefined value here causes undefined behavior", .{}); | ||
| 963 | } | ||
| 964 | return val; | ||
| 965 | } | ||
| 966 | return null; | ||
| 967 | } | ||
| 968 | |||
| 969 | fn resolveConstString(self: *Module, scope: *Scope, old_inst: *zir.Inst) ![]u8 { | ||
| 970 | const new_inst = try self.resolveInst(scope, old_inst); | ||
| 971 | const wanted_type = Type.initTag(.const_slice_u8); | ||
| 972 | const coerced_inst = try self.coerce(scope, wanted_type, new_inst); | ||
| 973 | const val = try self.resolveConstValue(scope, coerced_inst); | ||
| 974 | return val.toAllocatedBytes(scope.arena()); | ||
| 975 | } | ||
| 976 | |||
| 977 | fn resolveType(self: *Module, scope: *Scope, old_inst: *zir.Inst) !Type { | ||
| 978 | const new_inst = try self.resolveInst(scope, old_inst); | ||
| 979 | const wanted_type = Type.initTag(.@"type"); | ||
| 980 | const coerced_inst = try self.coerce(scope, wanted_type, new_inst); | ||
| 981 | const val = try self.resolveConstValue(scope, coerced_inst); | ||
| 982 | return val.toType(); | ||
| 983 | } | ||
| 984 | |||
| 985 | fn analyzeExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!void { | ||
| 986 | try self.decl_exports.ensureCapacity(self.decl_exports.size + 1); | ||
| 987 | try self.export_owners.ensureCapacity(self.export_owners.size + 1); | ||
| 988 | const symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name); | ||
| 989 | const exported_decl = try self.resolveCompleteDecl(scope, export_inst.positionals.value); | ||
| 990 | const typed_value = exported_decl.typed_value.most_recent.typed_value; | ||
| 991 | switch (typed_value.ty.zigTypeTag()) { | ||
| 992 | .Fn => {}, | ||
| 993 | else => return self.fail( | ||
| 994 | scope, | ||
| 995 | export_inst.positionals.value.src, | ||
| 996 | "unable to export type '{}'", | ||
| 997 | .{typed_value.ty}, | ||
| 998 | ), | ||
| 999 | } | ||
| 1000 | const new_export = try self.allocator.create(Export); | ||
| 1001 | errdefer self.allocator.destroy(new_export); | ||
| 1002 | |||
| 1003 | const owner_decl = scope.decl(); | ||
| 1004 | |||
| 1005 | new_export.* = .{ | ||
| 1006 | .options = .{ .name = symbol_name }, | ||
| 1007 | .src = export_inst.base.src, | ||
| 1008 | .link = .{}, | ||
| 1009 | .owner_decl = owner_decl, | ||
| 1010 | .status = .in_progress, | ||
| 1011 | }; | ||
| 1012 | |||
| 1013 | // Add to export_owners table. | ||
| 1014 | const eo_gop = self.export_owners.getOrPut(owner_decl) catch unreachable; | ||
| 1015 | if (!eo_gop.found_existing) { | ||
| 1016 | eo_gop.kv.value = &[0]*Export{}; | ||
| 1017 | } | ||
| 1018 | eo_gop.kv.value = try self.allocator.realloc(eo_gop.kv.value, eo_gop.kv.value.len + 1); | ||
| 1019 | eo_gop.kv.value[eo_gop.kv.value.len - 1] = new_export; | ||
| 1020 | errdefer eo_gop.kv.value = self.allocator.shrink(eo_gop.kv.value, eo_gop.kv.value.len - 1); | ||
| 1021 | |||
| 1022 | // Add to exported_decl table. | ||
| 1023 | const de_gop = self.decl_exports.getOrPut(exported_decl) catch unreachable; | ||
| 1024 | if (!de_gop.found_existing) { | ||
| 1025 | de_gop.kv.value = &[0]*Export{}; | ||
| 1026 | } | ||
| 1027 | de_gop.kv.value = try self.allocator.realloc(de_gop.kv.value, de_gop.kv.value.len + 1); | ||
| 1028 | de_gop.kv.value[de_gop.kv.value.len - 1] = new_export; | ||
| 1029 | errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1); | ||
| 1030 | |||
| 1031 | self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) { | ||
| 1032 | error.OutOfMemory => return error.OutOfMemory, | ||
| 1033 | else => { | ||
| 1034 | try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); | ||
| 1035 | self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( | ||
| 1036 | self.allocator, | ||
| 1037 | export_inst.base.src, | ||
| 1038 | "unable to export: {}", | ||
| 1039 | .{@errorName(err)}, | ||
| 1040 | )); | ||
| 1041 | new_export.status = .failed_retryable; | ||
| 1042 | }, | ||
| 1043 | }; | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | /// TODO should not need the cast on the last parameter at the callsites | ||
| 1047 | fn addNewInstArgs( | ||
| 1048 | self: *Module, | ||
| 1049 | block: *Scope.Block, | ||
| 1050 | src: usize, | ||
| 1051 | ty: Type, | ||
| 1052 | comptime T: type, | ||
| 1053 | args: Inst.Args(T), | ||
| 1054 | ) !*Inst { | ||
| 1055 | const inst = try self.addNewInst(block, src, ty, T); | ||
| 1056 | inst.args = args; | ||
| 1057 | return &inst.base; | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime T: type) !*T { | ||
| 1061 | const inst = try block.arena.create(T); | ||
| 1062 | inst.* = .{ | ||
| 1063 | .base = .{ | ||
| 1064 | .tag = T.base_tag, | ||
| 1065 | .ty = ty, | ||
| 1066 | .src = src, | ||
| 1067 | }, | ||
| 1068 | .args = undefined, | ||
| 1069 | }; | ||
| 1070 | try block.instructions.append(self.allocator, &inst.base); | ||
| 1071 | return inst; | ||
| 1072 | } | ||
| 1073 | |||
| 1074 | fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { | ||
| 1075 | const const_inst = try scope.arena().create(Inst.Constant); | ||
| 1076 | const_inst.* = .{ | ||
| 1077 | .base = .{ | ||
| 1078 | .tag = Inst.Constant.base_tag, | ||
| 1079 | .ty = typed_value.ty, | ||
| 1080 | .src = src, | ||
| 1081 | }, | ||
| 1082 | .val = typed_value.val, | ||
| 1083 | }; | ||
| 1084 | return &const_inst.base; | ||
| 1085 | } | ||
| 1086 | |||
| 1087 | fn constStr(self: *Module, scope: *Scope, src: usize, str: []const u8) !*Inst { | ||
| 1088 | const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); | ||
| 1089 | ty_payload.* = .{ .len = str.len }; | ||
| 1090 | |||
| 1091 | const bytes_payload = try scope.arena().create(Value.Payload.Bytes); | ||
| 1092 | bytes_payload.* = .{ .data = str }; | ||
| 1093 | |||
| 1094 | return self.constInst(scope, src, .{ | ||
| 1095 | .ty = Type.initPayload(&ty_payload.base), | ||
| 1096 | .val = Value.initPayload(&bytes_payload.base), | ||
| 1097 | }); | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { | ||
| 1101 | return self.constInst(scope, src, .{ | ||
| 1102 | .ty = Type.initTag(.type), | ||
| 1103 | .val = try ty.toValue(scope.arena()), | ||
| 1104 | }); | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst { | ||
| 1108 | return self.constInst(scope, src, .{ | ||
| 1109 | .ty = Type.initTag(.void), | ||
| 1110 | .val = Value.initTag(.the_one_possible_value), | ||
| 1111 | }); | ||
| 1112 | } | ||
| 1113 | |||
| 1114 | fn constUndef(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { | ||
| 1115 | return self.constInst(scope, src, .{ | ||
| 1116 | .ty = ty, | ||
| 1117 | .val = Value.initTag(.undef), | ||
| 1118 | }); | ||
| 1119 | } | ||
| 1120 | |||
| 1121 | fn constBool(self: *Module, scope: *Scope, src: usize, v: bool) !*Inst { | ||
| 1122 | return self.constInst(scope, src, .{ | ||
| 1123 | .ty = Type.initTag(.bool), | ||
| 1124 | .val = ([2]Value{ Value.initTag(.bool_false), Value.initTag(.bool_true) })[@boolToInt(v)], | ||
| 1125 | }); | ||
| 1126 | } | ||
| 1127 | |||
| 1128 | fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64) !*Inst { | ||
| 1129 | const int_payload = try scope.arena().create(Value.Payload.Int_u64); | ||
| 1130 | int_payload.* = .{ .int = int }; | ||
| 1131 | |||
| 1132 | return self.constInst(scope, src, .{ | ||
| 1133 | .ty = ty, | ||
| 1134 | .val = Value.initPayload(&int_payload.base), | ||
| 1135 | }); | ||
| 1136 | } | ||
| 1137 | |||
| 1138 | fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) !*Inst { | ||
| 1139 | const int_payload = try scope.arena().create(Value.Payload.Int_i64); | ||
| 1140 | int_payload.* = .{ .int = int }; | ||
| 1141 | |||
| 1142 | return self.constInst(scope, src, .{ | ||
| 1143 | .ty = ty, | ||
| 1144 | .val = Value.initPayload(&int_payload.base), | ||
| 1145 | }); | ||
| 1146 | } | ||
| 1147 | |||
| 1148 | fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst { | ||
| 1149 | const val_payload = if (big_int.positive) blk: { | ||
| 1150 | if (big_int.to(u64)) |x| { | ||
| 1151 | return self.constIntUnsigned(scope, src, ty, x); | ||
| 1152 | } else |err| switch (err) { | ||
| 1153 | error.NegativeIntoUnsigned => unreachable, | ||
| 1154 | error.TargetTooSmall => {}, // handled below | ||
| 1155 | } | ||
| 1156 | const big_int_payload = try scope.arena().create(Value.Payload.IntBigPositive); | ||
| 1157 | big_int_payload.* = .{ .limbs = big_int.limbs }; | ||
| 1158 | break :blk &big_int_payload.base; | ||
| 1159 | } else blk: { | ||
| 1160 | if (big_int.to(i64)) |x| { | ||
| 1161 | return self.constIntSigned(scope, src, ty, x); | ||
| 1162 | } else |err| switch (err) { | ||
| 1163 | error.NegativeIntoUnsigned => unreachable, | ||
| 1164 | error.TargetTooSmall => {}, // handled below | ||
| 1165 | } | ||
| 1166 | const big_int_payload = try scope.arena().create(Value.Payload.IntBigNegative); | ||
| 1167 | big_int_payload.* = .{ .limbs = big_int.limbs }; | ||
| 1168 | break :blk &big_int_payload.base; | ||
| 1169 | }; | ||
| 1170 | |||
| 1171 | return self.constInst(scope, src, .{ | ||
| 1172 | .ty = ty, | ||
| 1173 | .val = Value.initPayload(val_payload), | ||
| 1174 | }); | ||
| 1175 | } | ||
| 1176 | |||
| 1177 | fn analyzeInstConst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { | ||
| 1178 | const new_inst = try self.analyzeInst(scope, old_inst); | ||
| 1179 | return TypedValue{ | ||
| 1180 | .ty = new_inst.ty, | ||
| 1181 | .val = try self.resolveConstValue(scope, new_inst), | ||
| 1182 | }; | ||
| 1183 | } | ||
| 1184 | |||
| 1185 | fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { | ||
| 1186 | switch (old_inst.tag) { | ||
| 1187 | .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.cast(zir.Inst.Breakpoint).?), | ||
| 1188 | .call => return self.analyzeInstCall(scope, old_inst.cast(zir.Inst.Call).?), | ||
| 1189 | .declref => return self.analyzeInstDeclRef(scope, old_inst.cast(zir.Inst.DeclRef).?), | ||
| 1190 | .str => { | ||
| 1191 | const bytes = old_inst.cast(zir.Inst.Str).?.positionals.bytes; | ||
| 1192 | // The bytes references memory inside the ZIR module, which can get deallocated | ||
| 1193 | // after semantic analysis is complete. We need the memory to be in the Decl's arena. | ||
| 1194 | const arena_bytes = try scope.arena().dupe(u8, bytes); | ||
| 1195 | return self.constStr(scope, old_inst.src, arena_bytes); | ||
| 1196 | }, | ||
| 1197 | .int => { | ||
| 1198 | const big_int = old_inst.cast(zir.Inst.Int).?.positionals.int; | ||
| 1199 | return self.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int); | ||
| 1200 | }, | ||
| 1201 | .ptrtoint => return self.analyzeInstPtrToInt(scope, old_inst.cast(zir.Inst.PtrToInt).?), | ||
| 1202 | .fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.cast(zir.Inst.FieldPtr).?), | ||
| 1203 | .deref => return self.analyzeInstDeref(scope, old_inst.cast(zir.Inst.Deref).?), | ||
| 1204 | .as => return self.analyzeInstAs(scope, old_inst.cast(zir.Inst.As).?), | ||
| 1205 | .@"asm" => return self.analyzeInstAsm(scope, old_inst.cast(zir.Inst.Asm).?), | ||
| 1206 | .@"unreachable" => return self.analyzeInstUnreachable(scope, old_inst.cast(zir.Inst.Unreachable).?), | ||
| 1207 | .@"return" => return self.analyzeInstRet(scope, old_inst.cast(zir.Inst.Return).?), | ||
| 1208 | .@"fn" => return self.analyzeInstFn(scope, old_inst.cast(zir.Inst.Fn).?), | ||
| 1209 | .@"export" => { | ||
| 1210 | try self.analyzeExport(scope, old_inst.cast(zir.Inst.Export).?); | ||
| 1211 | return self.constVoid(scope, old_inst.src); | ||
| 1212 | }, | ||
| 1213 | .primitive => return self.analyzeInstPrimitive(scope, old_inst.cast(zir.Inst.Primitive).?), | ||
| 1214 | .ref => return self.analyzeInstRef(scope, old_inst.cast(zir.Inst.Ref).?), | ||
| 1215 | .fntype => return self.analyzeInstFnType(scope, old_inst.cast(zir.Inst.FnType).?), | ||
| 1216 | .intcast => return self.analyzeInstIntCast(scope, old_inst.cast(zir.Inst.IntCast).?), | ||
| 1217 | .bitcast => return self.analyzeInstBitCast(scope, old_inst.cast(zir.Inst.BitCast).?), | ||
| 1218 | .elemptr => return self.analyzeInstElemPtr(scope, old_inst.cast(zir.Inst.ElemPtr).?), | ||
| 1219 | .add => return self.analyzeInstAdd(scope, old_inst.cast(zir.Inst.Add).?), | ||
| 1220 | .cmp => return self.analyzeInstCmp(scope, old_inst.cast(zir.Inst.Cmp).?), | ||
| 1221 | .condbr => return self.analyzeInstCondBr(scope, old_inst.cast(zir.Inst.CondBr).?), | ||
| 1222 | .isnull => return self.analyzeInstIsNull(scope, old_inst.cast(zir.Inst.IsNull).?), | ||
| 1223 | .isnonnull => return self.analyzeInstIsNonNull(scope, old_inst.cast(zir.Inst.IsNonNull).?), | ||
| 1224 | } | ||
| 1225 | } | ||
| 1226 | |||
| 1227 | fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.Breakpoint) InnerError!*Inst { | ||
| 1228 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 1229 | return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, Inst.Args(Inst.Breakpoint){}); | ||
| 1230 | } | ||
| 1231 | |||
| 1232 | fn analyzeInstRef(self: *Module, scope: *Scope, inst: *zir.Inst.Ref) InnerError!*Inst { | ||
| 1233 | const decl = try self.resolveCompleteDecl(scope, inst.positionals.operand); | ||
| 1234 | return self.analyzeDeclRef(scope, inst.base.src, decl); | ||
| 1235 | } | ||
| 1236 | |||
| 1237 | fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst { | ||
| 1238 | const decl_name = try self.resolveConstString(scope, inst.positionals.name); | ||
| 1239 | // This will need to get more fleshed out when there are proper structs & namespaces. | ||
| 1240 | const zir_module = scope.namespace(); | ||
| 1241 | for (zir_module.contents.module.decls) |src_decl| { | ||
| 1242 | if (mem.eql(u8, src_decl.name, decl_name)) { | ||
| 1243 | const decl = try self.resolveCompleteDecl(scope, src_decl); | ||
| 1244 | return self.analyzeDeclRef(scope, inst.base.src, decl); | ||
| 1245 | } | ||
| 1246 | } | ||
| 1247 | return self.fail(scope, inst.positionals.name.src, "use of undeclared identifier '{}'", .{decl_name}); | ||
| 1248 | } | ||
| 1249 | |||
| 1250 | fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst { | ||
| 1251 | const decl_tv = try decl.typedValue(); | ||
| 1252 | const ty_payload = try scope.arena().create(Type.Payload.SingleConstPointer); | ||
| 1253 | ty_payload.* = .{ .pointee_type = decl_tv.ty }; | ||
| 1254 | const val_payload = try scope.arena().create(Value.Payload.DeclRef); | ||
| 1255 | val_payload.* = .{ .decl = decl }; | ||
| 1256 | return self.constInst(scope, src, .{ | ||
| 1257 | .ty = Type.initPayload(&ty_payload.base), | ||
| 1258 | .val = Value.initPayload(&val_payload.base), | ||
| 1259 | }); | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { | ||
| 1263 | const func = try self.resolveInst(scope, inst.positionals.func); | ||
| 1264 | if (func.ty.zigTypeTag() != .Fn) | ||
| 1265 | return self.fail(scope, inst.positionals.func.src, "type '{}' not a function", .{func.ty}); | ||
| 1266 | |||
| 1267 | const cc = func.ty.fnCallingConvention(); | ||
| 1268 | if (cc == .Naked) { | ||
| 1269 | // TODO add error note: declared here | ||
| 1270 | return self.fail( | ||
| 1271 | scope, | ||
| 1272 | inst.positionals.func.src, | ||
| 1273 | "unable to call function with naked calling convention", | ||
| 1274 | .{}, | ||
| 1275 | ); | ||
| 1276 | } | ||
| 1277 | const call_params_len = inst.positionals.args.len; | ||
| 1278 | const fn_params_len = func.ty.fnParamLen(); | ||
| 1279 | if (func.ty.fnIsVarArgs()) { | ||
| 1280 | if (call_params_len < fn_params_len) { | ||
| 1281 | // TODO add error note: declared here | ||
| 1282 | return self.fail( | ||
| 1283 | scope, | ||
| 1284 | inst.positionals.func.src, | ||
| 1285 | "expected at least {} arguments, found {}", | ||
| 1286 | .{ fn_params_len, call_params_len }, | ||
| 1287 | ); | ||
| 1288 | } | ||
| 1289 | return self.fail(scope, inst.base.src, "TODO implement support for calling var args functions", .{}); | ||
| 1290 | } else if (fn_params_len != call_params_len) { | ||
| 1291 | // TODO add error note: declared here | ||
| 1292 | return self.fail( | ||
| 1293 | scope, | ||
| 1294 | inst.positionals.func.src, | ||
| 1295 | "expected {} arguments, found {}", | ||
| 1296 | .{ fn_params_len, call_params_len }, | ||
| 1297 | ); | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | if (inst.kw_args.modifier == .compile_time) { | ||
| 1301 | return self.fail(scope, inst.base.src, "TODO implement comptime function calls", .{}); | ||
| 1302 | } | ||
| 1303 | if (inst.kw_args.modifier != .auto) { | ||
| 1304 | return self.fail(scope, inst.base.src, "TODO implement call with modifier {}", .{inst.kw_args.modifier}); | ||
| 1305 | } | ||
| 1306 | |||
| 1307 | // TODO handle function calls of generic functions | ||
| 1308 | |||
| 1309 | const fn_param_types = try self.allocator.alloc(Type, fn_params_len); | ||
| 1310 | defer self.allocator.free(fn_param_types); | ||
| 1311 | func.ty.fnParamTypes(fn_param_types); | ||
| 1312 | |||
| 1313 | const casted_args = try scope.arena().alloc(*Inst, fn_params_len); | ||
| 1314 | for (inst.positionals.args) |src_arg, i| { | ||
| 1315 | const uncasted_arg = try self.resolveInst(scope, src_arg); | ||
| 1316 | casted_args[i] = try self.coerce(scope, fn_param_types[i], uncasted_arg); | ||
| 1317 | } | ||
| 1318 | |||
| 1319 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 1320 | return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Call, Inst.Args(Inst.Call){ | ||
| 1321 | .func = func, | ||
| 1322 | .args = casted_args, | ||
| 1323 | }); | ||
| 1324 | } | ||
| 1325 | |||
| 1326 | fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst { | ||
| 1327 | const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type); | ||
| 1328 | const new_func = try scope.arena().create(Fn); | ||
| 1329 | new_func.* = .{ | ||
| 1330 | .fn_type = fn_type, | ||
| 1331 | .analysis = .{ .queued = fn_inst }, | ||
| 1332 | }; | ||
| 1333 | const fn_payload = try scope.arena().create(Value.Payload.Function); | ||
| 1334 | fn_payload.* = .{ .func = new_func }; | ||
| 1335 | return self.constInst(scope, fn_inst.base.src, .{ | ||
| 1336 | .ty = fn_type, | ||
| 1337 | .val = Value.initPayload(&fn_payload.base), | ||
| 1338 | }); | ||
| 1339 | } | ||
| 1340 | |||
| 1341 | fn analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { | ||
| 1342 | const return_type = try self.resolveType(scope, fntype.positionals.return_type); | ||
| 1343 | |||
| 1344 | if (return_type.zigTypeTag() == .NoReturn and | ||
| 1345 | fntype.positionals.param_types.len == 0 and | ||
| 1346 | fntype.kw_args.cc == .Unspecified) | ||
| 1347 | { | ||
| 1348 | return self.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args)); | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | if (return_type.zigTypeTag() == .NoReturn and | ||
| 1352 | fntype.positionals.param_types.len == 0 and | ||
| 1353 | fntype.kw_args.cc == .Naked) | ||
| 1354 | { | ||
| 1355 | return self.constType(scope, fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args)); | ||
| 1356 | } | ||
| 1357 | |||
| 1358 | if (return_type.zigTypeTag() == .Void and | ||
| 1359 | fntype.positionals.param_types.len == 0 and | ||
| 1360 | fntype.kw_args.cc == .C) | ||
| 1361 | { | ||
| 1362 | return self.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args)); | ||
| 1363 | } | ||
| 1364 | |||
| 1365 | return self.fail(scope, fntype.base.src, "TODO implement fntype instruction more", .{}); | ||
| 1366 | } | ||
| 1367 | |||
| 1368 | fn analyzeInstPrimitive(self: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst { | ||
| 1369 | return self.constType(scope, primitive.base.src, primitive.positionals.tag.toType()); | ||
| 1370 | } | ||
| 1371 | |||
| 1372 | fn analyzeInstAs(self: *Module, scope: *Scope, as: *zir.Inst.As) InnerError!*Inst { | ||
| 1373 | const dest_type = try self.resolveType(scope, as.positionals.dest_type); | ||
| 1374 | const new_inst = try self.resolveInst(scope, as.positionals.value); | ||
| 1375 | return self.coerce(scope, dest_type, new_inst); | ||
| 1376 | } | ||
| 1377 | |||
| 1378 | fn analyzeInstPtrToInt(self: *Module, scope: *Scope, ptrtoint: *zir.Inst.PtrToInt) InnerError!*Inst { | ||
| 1379 | const ptr = try self.resolveInst(scope, ptrtoint.positionals.ptr); | ||
| 1380 | if (ptr.ty.zigTypeTag() != .Pointer) { | ||
| 1381 | return self.fail(scope, ptrtoint.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty}); | ||
| 1382 | } | ||
| 1383 | // TODO handle known-pointer-address | ||
| 1384 | const b = try self.requireRuntimeBlock(scope, ptrtoint.base.src); | ||
| 1385 | const ty = Type.initTag(.usize); | ||
| 1386 | return self.addNewInstArgs(b, ptrtoint.base.src, ty, Inst.PtrToInt, Inst.Args(Inst.PtrToInt){ .ptr = ptr }); | ||
| 1387 | } | ||
| 1388 | |||
| 1389 | fn analyzeInstFieldPtr(self: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr) InnerError!*Inst { | ||
| 1390 | const object_ptr = try self.resolveInst(scope, fieldptr.positionals.object_ptr); | ||
| 1391 | const field_name = try self.resolveConstString(scope, fieldptr.positionals.field_name); | ||
| 1392 | |||
| 1393 | const elem_ty = switch (object_ptr.ty.zigTypeTag()) { | ||
| 1394 | .Pointer => object_ptr.ty.elemType(), | ||
| 1395 | else => return self.fail(scope, fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}), | ||
| 1396 | }; | ||
| 1397 | switch (elem_ty.zigTypeTag()) { | ||
| 1398 | .Array => { | ||
| 1399 | if (mem.eql(u8, field_name, "len")) { | ||
| 1400 | const len_payload = try scope.arena().create(Value.Payload.Int_u64); | ||
| 1401 | len_payload.* = .{ .int = elem_ty.arrayLen() }; | ||
| 1402 | |||
| 1403 | const ref_payload = try scope.arena().create(Value.Payload.RefVal); | ||
| 1404 | ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) }; | ||
| 1405 | |||
| 1406 | return self.constInst(scope, fieldptr.base.src, .{ | ||
| 1407 | .ty = Type.initTag(.single_const_pointer_to_comptime_int), | ||
| 1408 | .val = Value.initPayload(&ref_payload.base), | ||
| 1409 | }); | ||
| 1410 | } else { | ||
| 1411 | return self.fail( | ||
| 1412 | scope, | ||
| 1413 | fieldptr.positionals.field_name.src, | ||
| 1414 | "no member named '{}' in '{}'", | ||
| 1415 | .{ field_name, elem_ty }, | ||
| 1416 | ); | ||
| 1417 | } | ||
| 1418 | }, | ||
| 1419 | else => return self.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}), | ||
| 1420 | } | ||
| 1421 | } | ||
| 1422 | |||
| 1423 | fn analyzeInstIntCast(self: *Module, scope: *Scope, intcast: *zir.Inst.IntCast) InnerError!*Inst { | ||
| 1424 | const dest_type = try self.resolveType(scope, intcast.positionals.dest_type); | ||
| 1425 | const new_inst = try self.resolveInst(scope, intcast.positionals.value); | ||
| 1426 | |||
| 1427 | const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { | ||
| 1428 | .ComptimeInt => true, | ||
| 1429 | .Int => false, | ||
| 1430 | else => return self.fail( | ||
| 1431 | scope, | ||
| 1432 | intcast.positionals.dest_type.src, | ||
| 1433 | "expected integer type, found '{}'", | ||
| 1434 | .{ | ||
| 1435 | dest_type, | ||
| 1436 | }, | ||
| 1437 | ), | ||
| 1438 | }; | ||
| 1439 | |||
| 1440 | switch (new_inst.ty.zigTypeTag()) { | ||
| 1441 | .ComptimeInt, .Int => {}, | ||
| 1442 | else => return self.fail( | ||
| 1443 | scope, | ||
| 1444 | intcast.positionals.value.src, | ||
| 1445 | "expected integer type, found '{}'", | ||
| 1446 | .{new_inst.ty}, | ||
| 1447 | ), | ||
| 1448 | } | ||
| 1449 | |||
| 1450 | if (dest_is_comptime_int or new_inst.value() != null) { | ||
| 1451 | return self.coerce(scope, dest_type, new_inst); | ||
| 1452 | } | ||
| 1453 | |||
| 1454 | return self.fail(scope, intcast.base.src, "TODO implement analyze widen or shorten int", .{}); | ||
| 1455 | } | ||
| 1456 | |||
| 1457 | fn analyzeInstBitCast(self: *Module, scope: *Scope, inst: *zir.Inst.BitCast) InnerError!*Inst { | ||
| 1458 | const dest_type = try self.resolveType(scope, inst.positionals.dest_type); | ||
| 1459 | const operand = try self.resolveInst(scope, inst.positionals.operand); | ||
| 1460 | return self.bitcast(scope, dest_type, operand); | ||
| 1461 | } | ||
| 1462 | |||
| 1463 | fn analyzeInstElemPtr(self: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) InnerError!*Inst { | ||
| 1464 | const array_ptr = try self.resolveInst(scope, inst.positionals.array_ptr); | ||
| 1465 | const uncasted_index = try self.resolveInst(scope, inst.positionals.index); | ||
| 1466 | const elem_index = try self.coerce(scope, Type.initTag(.usize), uncasted_index); | ||
| 1467 | |||
| 1468 | if (array_ptr.ty.isSinglePointer() and array_ptr.ty.elemType().zigTypeTag() == .Array) { | ||
| 1469 | if (array_ptr.value()) |array_ptr_val| { | ||
| 1470 | if (elem_index.value()) |index_val| { | ||
| 1471 | // Both array pointer and index are compile-time known. | ||
| 1472 | const index_u64 = index_val.toUnsignedInt(); | ||
| 1473 | // @intCast here because it would have been impossible to construct a value that | ||
| 1474 | // required a larger index. | ||
| 1475 | const elem_ptr = try array_ptr_val.elemPtr(scope.arena(), @intCast(usize, index_u64)); | ||
| 1476 | |||
| 1477 | const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer); | ||
| 1478 | type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() }; | ||
| 1479 | |||
| 1480 | return self.constInst(scope, inst.base.src, .{ | ||
| 1481 | .ty = Type.initPayload(&type_payload.base), | ||
| 1482 | .val = elem_ptr, | ||
| 1483 | }); | ||
| 1484 | } | ||
| 1485 | } | ||
| 1486 | } | ||
| 1487 | |||
| 1488 | return self.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{}); | ||
| 1489 | } | ||
| 1490 | |||
| 1491 | fn analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError!*Inst { | ||
| 1492 | const lhs = try self.resolveInst(scope, inst.positionals.lhs); | ||
| 1493 | const rhs = try self.resolveInst(scope, inst.positionals.rhs); | ||
| 1494 | |||
| 1495 | if (lhs.ty.zigTypeTag() == .Int and rhs.ty.zigTypeTag() == .Int) { | ||
| 1496 | if (lhs.value()) |lhs_val| { | ||
| 1497 | if (rhs.value()) |rhs_val| { | ||
| 1498 | // TODO is this a performance issue? maybe we should try the operation without | ||
| 1499 | // resorting to BigInt first. | ||
| 1500 | var lhs_space: Value.BigIntSpace = undefined; | ||
| 1501 | var rhs_space: Value.BigIntSpace = undefined; | ||
| 1502 | const lhs_bigint = lhs_val.toBigInt(&lhs_space); | ||
| 1503 | const rhs_bigint = rhs_val.toBigInt(&rhs_space); | ||
| 1504 | const limbs = try scope.arena().alloc( | ||
| 1505 | std.math.big.Limb, | ||
| 1506 | std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, | ||
| 1507 | ); | ||
| 1508 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | ||
| 1509 | result_bigint.add(lhs_bigint, rhs_bigint); | ||
| 1510 | const result_limbs = result_bigint.limbs[0..result_bigint.len]; | ||
| 1511 | |||
| 1512 | if (!lhs.ty.eql(rhs.ty)) { | ||
| 1513 | return self.fail(scope, inst.base.src, "TODO implement peer type resolution", .{}); | ||
| 1514 | } | ||
| 1515 | |||
| 1516 | const val_payload = if (result_bigint.positive) blk: { | ||
| 1517 | const val_payload = try scope.arena().create(Value.Payload.IntBigPositive); | ||
| 1518 | val_payload.* = .{ .limbs = result_limbs }; | ||
| 1519 | break :blk &val_payload.base; | ||
| 1520 | } else blk: { | ||
| 1521 | const val_payload = try scope.arena().create(Value.Payload.IntBigNegative); | ||
| 1522 | val_payload.* = .{ .limbs = result_limbs }; | ||
| 1523 | break :blk &val_payload.base; | ||
| 1524 | }; | ||
| 1525 | |||
| 1526 | return self.constInst(scope, inst.base.src, .{ | ||
| 1527 | .ty = lhs.ty, | ||
| 1528 | .val = Value.initPayload(val_payload), | ||
| 1529 | }); | ||
| 1530 | } | ||
| 1531 | } | ||
| 1532 | } | ||
| 1533 | |||
| 1534 | return self.fail(scope, inst.base.src, "TODO implement more analyze add", .{}); | ||
| 1535 | } | ||
| 1536 | |||
| 1537 | fn analyzeInstDeref(self: *Module, scope: *Scope, deref: *zir.Inst.Deref) InnerError!*Inst { | ||
| 1538 | const ptr = try self.resolveInst(scope, deref.positionals.ptr); | ||
| 1539 | return self.analyzeDeref(scope, deref.base.src, ptr, deref.positionals.ptr.src); | ||
| 1540 | } | ||
| 1541 | |||
| 1542 | fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: usize) InnerError!*Inst { | ||
| 1543 | const elem_ty = switch (ptr.ty.zigTypeTag()) { | ||
| 1544 | .Pointer => ptr.ty.elemType(), | ||
| 1545 | else => return self.fail(scope, ptr_src, "expected pointer, found '{}'", .{ptr.ty}), | ||
| 1546 | }; | ||
| 1547 | if (ptr.value()) |val| { | ||
| 1548 | return self.constInst(scope, src, .{ | ||
| 1549 | .ty = elem_ty, | ||
| 1550 | .val = try val.pointerDeref(scope.arena()), | ||
| 1551 | }); | ||
| 1552 | } | ||
| 1553 | |||
| 1554 | return self.fail(scope, src, "TODO implement runtime deref", .{}); | ||
| 1555 | } | ||
| 1556 | |||
| 1557 | fn analyzeInstAsm(self: *Module, scope: *Scope, assembly: *zir.Inst.Asm) InnerError!*Inst { | ||
| 1558 | const return_type = try self.resolveType(scope, assembly.positionals.return_type); | ||
| 1559 | const asm_source = try self.resolveConstString(scope, assembly.positionals.asm_source); | ||
| 1560 | const output = if (assembly.kw_args.output) |o| try self.resolveConstString(scope, o) else null; | ||
| 1561 | |||
| 1562 | const inputs = try scope.arena().alloc([]const u8, assembly.kw_args.inputs.len); | ||
| 1563 | const clobbers = try scope.arena().alloc([]const u8, assembly.kw_args.clobbers.len); | ||
| 1564 | const args = try scope.arena().alloc(*Inst, assembly.kw_args.args.len); | ||
| 1565 | |||
| 1566 | for (inputs) |*elem, i| { | ||
| 1567 | elem.* = try self.resolveConstString(scope, assembly.kw_args.inputs[i]); | ||
| 1568 | } | ||
| 1569 | for (clobbers) |*elem, i| { | ||
| 1570 | elem.* = try self.resolveConstString(scope, assembly.kw_args.clobbers[i]); | ||
| 1571 | } | ||
| 1572 | for (args) |*elem, i| { | ||
| 1573 | const arg = try self.resolveInst(scope, assembly.kw_args.args[i]); | ||
| 1574 | elem.* = try self.coerce(scope, Type.initTag(.usize), arg); | ||
| 1575 | } | ||
| 1576 | |||
| 1577 | const b = try self.requireRuntimeBlock(scope, assembly.base.src); | ||
| 1578 | return self.addNewInstArgs(b, assembly.base.src, return_type, Inst.Assembly, Inst.Args(Inst.Assembly){ | ||
| 1579 | .asm_source = asm_source, | ||
| 1580 | .is_volatile = assembly.kw_args.@"volatile", | ||
| 1581 | .output = output, | ||
| 1582 | .inputs = inputs, | ||
| 1583 | .clobbers = clobbers, | ||
| 1584 | .args = args, | ||
| 1585 | }); | ||
| 1586 | } | ||
| 1587 | |||
| 1588 | fn analyzeInstCmp(self: *Module, scope: *Scope, inst: *zir.Inst.Cmp) InnerError!*Inst { | ||
| 1589 | const lhs = try self.resolveInst(scope, inst.positionals.lhs); | ||
| 1590 | const rhs = try self.resolveInst(scope, inst.positionals.rhs); | ||
| 1591 | const op = inst.positionals.op; | ||
| 1592 | |||
| 1593 | const is_equality_cmp = switch (op) { | ||
| 1594 | .eq, .neq => true, | ||
| 1595 | else => false, | ||
| 1596 | }; | ||
| 1597 | const lhs_ty_tag = lhs.ty.zigTypeTag(); | ||
| 1598 | const rhs_ty_tag = rhs.ty.zigTypeTag(); | ||
| 1599 | if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) { | ||
| 1600 | // null == null, null != null | ||
| 1601 | return self.constBool(scope, inst.base.src, op == .eq); | ||
| 1602 | } else if (is_equality_cmp and | ||
| 1603 | ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or | ||
| 1604 | rhs_ty_tag == .Null and lhs_ty_tag == .Optional)) | ||
| 1605 | { | ||
| 1606 | // comparing null with optionals | ||
| 1607 | const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs; | ||
| 1608 | if (opt_operand.value()) |opt_val| { | ||
| 1609 | const is_null = opt_val.isNull(); | ||
| 1610 | return self.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null); | ||
| 1611 | } | ||
| 1612 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 1613 | switch (op) { | ||
| 1614 | .eq => return self.addNewInstArgs( | ||
| 1615 | b, | ||
| 1616 | inst.base.src, | ||
| 1617 | Type.initTag(.bool), | ||
| 1618 | Inst.IsNull, | ||
| 1619 | Inst.Args(Inst.IsNull){ .operand = opt_operand }, | ||
| 1620 | ), | ||
| 1621 | .neq => return self.addNewInstArgs( | ||
| 1622 | b, | ||
| 1623 | inst.base.src, | ||
| 1624 | Type.initTag(.bool), | ||
| 1625 | Inst.IsNonNull, | ||
| 1626 | Inst.Args(Inst.IsNonNull){ .operand = opt_operand }, | ||
| 1627 | ), | ||
| 1628 | else => unreachable, | ||
| 1629 | } | ||
| 1630 | } else if (is_equality_cmp and | ||
| 1631 | ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr()))) | ||
| 1632 | { | ||
| 1633 | return self.fail(scope, inst.base.src, "TODO implement C pointer cmp", .{}); | ||
| 1634 | } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) { | ||
| 1635 | const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty; | ||
| 1636 | return self.fail(scope, inst.base.src, "comparison of '{}' with null", .{non_null_type}); | ||
| 1637 | } else if (is_equality_cmp and | ||
| 1638 | ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or | ||
| 1639 | (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union))) | ||
| 1640 | { | ||
| 1641 | return self.fail(scope, inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); | ||
| 1642 | } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { | ||
| 1643 | if (!is_equality_cmp) { | ||
| 1644 | return self.fail(scope, inst.base.src, "{} operator not allowed for errors", .{@tagName(op)}); | ||
| 1645 | } | ||
| 1646 | return self.fail(scope, inst.base.src, "TODO implement equality comparison between errors", .{}); | ||
| 1647 | } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) { | ||
| 1648 | // This operation allows any combination of integer and float types, regardless of the | ||
| 1649 | // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for | ||
| 1650 | // numeric types. | ||
| 1651 | return self.cmpNumeric(scope, inst.base.src, lhs, rhs, op); | ||
| 1652 | } | ||
| 1653 | return self.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{}); | ||
| 1654 | } | ||
| 1655 | |||
| 1656 | fn analyzeInstIsNull(self: *Module, scope: *Scope, inst: *zir.Inst.IsNull) InnerError!*Inst { | ||
| 1657 | const operand = try self.resolveInst(scope, inst.positionals.operand); | ||
| 1658 | return self.analyzeIsNull(scope, inst.base.src, operand, true); | ||
| 1659 | } | ||
| 1660 | |||
| 1661 | fn analyzeInstIsNonNull(self: *Module, scope: *Scope, inst: *zir.Inst.IsNonNull) InnerError!*Inst { | ||
| 1662 | const operand = try self.resolveInst(scope, inst.positionals.operand); | ||
| 1663 | return self.analyzeIsNull(scope, inst.base.src, operand, false); | ||
| 1664 | } | ||
| 1665 | |||
| 1666 | fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst { | ||
| 1667 | const uncasted_cond = try self.resolveInst(scope, inst.positionals.condition); | ||
| 1668 | const cond = try self.coerce(scope, Type.initTag(.bool), uncasted_cond); | ||
| 1669 | |||
| 1670 | if (try self.resolveDefinedValue(scope, cond)) |cond_val| { | ||
| 1671 | const body = if (cond_val.toBool()) &inst.positionals.true_body else &inst.positionals.false_body; | ||
| 1672 | try self.analyzeBody(scope, body.*); | ||
| 1673 | return self.constVoid(scope, inst.base.src); | ||
| 1674 | } | ||
| 1675 | |||
| 1676 | const parent_block = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 1677 | |||
| 1678 | var true_block: Scope.Block = .{ | ||
| 1679 | .func = parent_block.func, | ||
| 1680 | .decl = parent_block.decl, | ||
| 1681 | .instructions = .{}, | ||
| 1682 | .arena = parent_block.arena, | ||
| 1683 | }; | ||
| 1684 | defer true_block.instructions.deinit(self.allocator); | ||
| 1685 | try self.analyzeBody(&true_block.base, inst.positionals.true_body); | ||
| 1686 | |||
| 1687 | var false_block: Scope.Block = .{ | ||
| 1688 | .func = parent_block.func, | ||
| 1689 | .decl = parent_block.decl, | ||
| 1690 | .instructions = .{}, | ||
| 1691 | .arena = parent_block.arena, | ||
| 1692 | }; | ||
| 1693 | defer false_block.instructions.deinit(self.allocator); | ||
| 1694 | try self.analyzeBody(&false_block.base, inst.positionals.false_body); | ||
| 1695 | |||
| 1696 | return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){ | ||
| 1697 | .condition = cond, | ||
| 1698 | .true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) }, | ||
| 1699 | .false_body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) }, | ||
| 1700 | }); | ||
| 1701 | } | ||
| 1702 | |||
| 1703 | fn wantSafety(self: *Module, scope: *Scope) bool { | ||
| 1704 | return switch (self.optimize_mode) { | ||
| 1705 | .Debug => true, | ||
| 1706 | .ReleaseSafe => true, | ||
| 1707 | .ReleaseFast => false, | ||
| 1708 | .ReleaseSmall => false, | ||
| 1709 | }; | ||
| 1710 | } | ||
| 1711 | |||
| 1712 | fn analyzeInstUnreachable(self: *Module, scope: *Scope, unreach: *zir.Inst.Unreachable) InnerError!*Inst { | ||
| 1713 | const b = try self.requireRuntimeBlock(scope, unreach.base.src); | ||
| 1714 | if (self.wantSafety(scope)) { | ||
| 1715 | // TODO Once we have a panic function to call, call it here instead of this. | ||
| 1716 | _ = try self.addNewInstArgs(b, unreach.base.src, Type.initTag(.void), Inst.Breakpoint, {}); | ||
| 1717 | } | ||
| 1718 | return self.addNewInstArgs(b, unreach.base.src, Type.initTag(.noreturn), Inst.Unreach, {}); | ||
| 1719 | } | ||
| 1720 | |||
| 1721 | fn analyzeInstRet(self: *Module, scope: *Scope, inst: *zir.Inst.Return) InnerError!*Inst { | ||
| 1722 | const b = try self.requireRuntimeBlock(scope, inst.base.src); | ||
| 1723 | return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.Ret, {}); | ||
| 1724 | } | ||
| 1725 | |||
| 1726 | fn analyzeBody(self: *Module, scope: *Scope, body: zir.Module.Body) !void { | ||
| 1727 | if (scope.cast(Scope.Block)) |b| { | ||
| 1728 | const analysis = b.func.analysis.in_progress; | ||
| 1729 | analysis.needed_inst_capacity += body.instructions.len; | ||
| 1730 | try analysis.inst_table.ensureCapacity(analysis.needed_inst_capacity); | ||
| 1731 | for (body.instructions) |src_inst| { | ||
| 1732 | const new_inst = try self.analyzeInst(scope, src_inst); | ||
| 1733 | analysis.inst_table.putAssumeCapacityNoClobber(src_inst, new_inst); | ||
| 1734 | } | ||
| 1735 | } else { | ||
| 1736 | for (body.instructions) |src_inst| { | ||
| 1737 | _ = try self.analyzeInst(scope, src_inst); | ||
| 1738 | } | ||
| 1739 | } | ||
| 1740 | } | ||
| 1741 | |||
| 1742 | fn analyzeIsNull( | ||
| 1743 | self: *Module, | ||
| 1744 | scope: *Scope, | ||
| 1745 | src: usize, | ||
| 1746 | operand: *Inst, | ||
| 1747 | invert_logic: bool, | ||
| 1748 | ) InnerError!*Inst { | ||
| 1749 | return self.fail(scope, src, "TODO implement analysis of isnull and isnotnull", .{}); | ||
| 1750 | } | ||
| 1751 | |||
| 1752 | /// Asserts that lhs and rhs types are both numeric. | ||
| 1753 | fn cmpNumeric( | ||
| 1754 | self: *Module, | ||
| 1755 | scope: *Scope, | ||
| 1756 | src: usize, | ||
| 1757 | lhs: *Inst, | ||
| 1758 | rhs: *Inst, | ||
| 1759 | op: std.math.CompareOperator, | ||
| 1760 | ) !*Inst { | ||
| 1761 | assert(lhs.ty.isNumeric()); | ||
| 1762 | assert(rhs.ty.isNumeric()); | ||
| 1763 | |||
| 1764 | const lhs_ty_tag = lhs.ty.zigTypeTag(); | ||
| 1765 | const rhs_ty_tag = rhs.ty.zigTypeTag(); | ||
| 1766 | |||
| 1767 | if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) { | ||
| 1768 | if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) { | ||
| 1769 | return self.fail(scope, src, "vector length mismatch: {} and {}", .{ | ||
| 1770 | lhs.ty.arrayLen(), | ||
| 1771 | rhs.ty.arrayLen(), | ||
| 1772 | }); | ||
| 1773 | } | ||
| 1774 | return self.fail(scope, src, "TODO implement support for vectors in cmpNumeric", .{}); | ||
| 1775 | } else if (lhs_ty_tag == .Vector or rhs_ty_tag == .Vector) { | ||
| 1776 | return self.fail(scope, src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{ | ||
| 1777 | lhs.ty, | ||
| 1778 | rhs.ty, | ||
| 1779 | }); | ||
| 1780 | } | ||
| 1781 | |||
| 1782 | if (lhs.value()) |lhs_val| { | ||
| 1783 | if (rhs.value()) |rhs_val| { | ||
| 1784 | return self.constBool(scope, src, Value.compare(lhs_val, op, rhs_val)); | ||
| 1785 | } | ||
| 1786 | } | ||
| 1787 | |||
| 1788 | // TODO handle comparisons against lazy zero values | ||
| 1789 | // Some values can be compared against zero without being runtime known or without forcing | ||
| 1790 | // a full resolution of their value, for example `@sizeOf(@Frame(function))` is known to | ||
| 1791 | // always be nonzero, and we benefit from not forcing the full evaluation and stack frame layout | ||
| 1792 | // of this function if we don't need to. | ||
| 1793 | |||
| 1794 | // It must be a runtime comparison. | ||
| 1795 | const b = try self.requireRuntimeBlock(scope, src); | ||
| 1796 | // For floats, emit a float comparison instruction. | ||
| 1797 | const lhs_is_float = switch (lhs_ty_tag) { | ||
| 1798 | .Float, .ComptimeFloat => true, | ||
| 1799 | else => false, | ||
| 1800 | }; | ||
| 1801 | const rhs_is_float = switch (rhs_ty_tag) { | ||
| 1802 | .Float, .ComptimeFloat => true, | ||
| 1803 | else => false, | ||
| 1804 | }; | ||
| 1805 | if (lhs_is_float and rhs_is_float) { | ||
| 1806 | // Implicit cast the smaller one to the larger one. | ||
| 1807 | const dest_type = x: { | ||
| 1808 | if (lhs_ty_tag == .ComptimeFloat) { | ||
| 1809 | break :x rhs.ty; | ||
| 1810 | } else if (rhs_ty_tag == .ComptimeFloat) { | ||
| 1811 | break :x lhs.ty; | ||
| 1812 | } | ||
| 1813 | if (lhs.ty.floatBits(self.target()) >= rhs.ty.floatBits(self.target())) { | ||
| 1814 | break :x lhs.ty; | ||
| 1815 | } else { | ||
| 1816 | break :x rhs.ty; | ||
| 1817 | } | ||
| 1818 | }; | ||
| 1819 | const casted_lhs = try self.coerce(scope, dest_type, lhs); | ||
| 1820 | const casted_rhs = try self.coerce(scope, dest_type, rhs); | ||
| 1821 | return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){ | ||
| 1822 | .lhs = casted_lhs, | ||
| 1823 | .rhs = casted_rhs, | ||
| 1824 | .op = op, | ||
| 1825 | }); | ||
| 1826 | } | ||
| 1827 | // For mixed unsigned integer sizes, implicit cast both operands to the larger integer. | ||
| 1828 | // For mixed signed and unsigned integers, implicit cast both operands to a signed | ||
| 1829 | // integer with + 1 bit. | ||
| 1830 | // For mixed floats and integers, extract the integer part from the float, cast that to | ||
| 1831 | // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float, | ||
| 1832 | // add/subtract 1. | ||
| 1833 | const lhs_is_signed = if (lhs.value()) |lhs_val| | ||
| 1834 | lhs_val.compareWithZero(.lt) | ||
| 1835 | else | ||
| 1836 | (lhs.ty.isFloat() or lhs.ty.isSignedInt()); | ||
| 1837 | const rhs_is_signed = if (rhs.value()) |rhs_val| | ||
| 1838 | rhs_val.compareWithZero(.lt) | ||
| 1839 | else | ||
| 1840 | (rhs.ty.isFloat() or rhs.ty.isSignedInt()); | ||
| 1841 | const dest_int_is_signed = lhs_is_signed or rhs_is_signed; | ||
| 1842 | |||
| 1843 | var dest_float_type: ?Type = null; | ||
| 1844 | |||
| 1845 | var lhs_bits: usize = undefined; | ||
| 1846 | if (lhs.value()) |lhs_val| { | ||
| 1847 | if (lhs_val.isUndef()) | ||
| 1848 | return self.constUndef(scope, src, Type.initTag(.bool)); | ||
| 1849 | const is_unsigned = if (lhs_is_float) x: { | ||
| 1850 | var bigint_space: Value.BigIntSpace = undefined; | ||
| 1851 | var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(self.allocator); | ||
| 1852 | defer bigint.deinit(); | ||
| 1853 | const zcmp = lhs_val.orderAgainstZero(); | ||
| 1854 | if (lhs_val.floatHasFraction()) { | ||
| 1855 | switch (op) { | ||
| 1856 | .eq => return self.constBool(scope, src, false), | ||
| 1857 | .neq => return self.constBool(scope, src, true), | ||
| 1858 | else => {}, | ||
| 1859 | } | ||
| 1860 | if (zcmp == .lt) { | ||
| 1861 | try bigint.addScalar(bigint.toConst(), -1); | ||
| 1862 | } else { | ||
| 1863 | try bigint.addScalar(bigint.toConst(), 1); | ||
| 1864 | } | ||
| 1865 | } | ||
| 1866 | lhs_bits = bigint.toConst().bitCountTwosComp(); | ||
| 1867 | break :x (zcmp != .lt); | ||
| 1868 | } else x: { | ||
| 1869 | lhs_bits = lhs_val.intBitCountTwosComp(); | ||
| 1870 | break :x (lhs_val.orderAgainstZero() != .lt); | ||
| 1871 | }; | ||
| 1872 | lhs_bits += @boolToInt(is_unsigned and dest_int_is_signed); | ||
| 1873 | } else if (lhs_is_float) { | ||
| 1874 | dest_float_type = lhs.ty; | ||
| 1875 | } else { | ||
| 1876 | const int_info = lhs.ty.intInfo(self.target()); | ||
| 1877 | lhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed); | ||
| 1878 | } | ||
| 1879 | |||
| 1880 | var rhs_bits: usize = undefined; | ||
| 1881 | if (rhs.value()) |rhs_val| { | ||
| 1882 | if (rhs_val.isUndef()) | ||
| 1883 | return self.constUndef(scope, src, Type.initTag(.bool)); | ||
| 1884 | const is_unsigned = if (rhs_is_float) x: { | ||
| 1885 | var bigint_space: Value.BigIntSpace = undefined; | ||
| 1886 | var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(self.allocator); | ||
| 1887 | defer bigint.deinit(); | ||
| 1888 | const zcmp = rhs_val.orderAgainstZero(); | ||
| 1889 | if (rhs_val.floatHasFraction()) { | ||
| 1890 | switch (op) { | ||
| 1891 | .eq => return self.constBool(scope, src, false), | ||
| 1892 | .neq => return self.constBool(scope, src, true), | ||
| 1893 | else => {}, | ||
| 1894 | } | ||
| 1895 | if (zcmp == .lt) { | ||
| 1896 | try bigint.addScalar(bigint.toConst(), -1); | ||
| 1897 | } else { | ||
| 1898 | try bigint.addScalar(bigint.toConst(), 1); | ||
| 1899 | } | ||
| 1900 | } | ||
| 1901 | rhs_bits = bigint.toConst().bitCountTwosComp(); | ||
| 1902 | break :x (zcmp != .lt); | ||
| 1903 | } else x: { | ||
| 1904 | rhs_bits = rhs_val.intBitCountTwosComp(); | ||
| 1905 | break :x (rhs_val.orderAgainstZero() != .lt); | ||
| 1906 | }; | ||
| 1907 | rhs_bits += @boolToInt(is_unsigned and dest_int_is_signed); | ||
| 1908 | } else if (rhs_is_float) { | ||
| 1909 | dest_float_type = rhs.ty; | ||
| 1910 | } else { | ||
| 1911 | const int_info = rhs.ty.intInfo(self.target()); | ||
| 1912 | rhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed); | ||
| 1913 | } | ||
| 1914 | |||
| 1915 | const dest_type = if (dest_float_type) |ft| ft else blk: { | ||
| 1916 | const max_bits = std.math.max(lhs_bits, rhs_bits); | ||
| 1917 | const casted_bits = std.math.cast(u16, max_bits) catch |err| switch (err) { | ||
| 1918 | error.Overflow => return self.fail(scope, src, "{} exceeds maximum integer bit count", .{max_bits}), | ||
| 1919 | }; | ||
| 1920 | break :blk try self.makeIntType(scope, dest_int_is_signed, casted_bits); | ||
| 1921 | }; | ||
| 1922 | const casted_lhs = try self.coerce(scope, dest_type, lhs); | ||
| 1923 | const casted_rhs = try self.coerce(scope, dest_type, lhs); | ||
| 1924 | |||
| 1925 | return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){ | ||
| 1926 | .lhs = casted_lhs, | ||
| 1927 | .rhs = casted_rhs, | ||
| 1928 | .op = op, | ||
| 1929 | }); | ||
| 1930 | } | ||
| 1931 | |||
| 1932 | fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { | ||
| 1933 | if (signed) { | ||
| 1934 | const int_payload = try scope.arena().create(Type.Payload.IntSigned); | ||
| 1935 | int_payload.* = .{ .bits = bits }; | ||
| 1936 | return Type.initPayload(&int_payload.base); | ||
| 1937 | } else { | ||
| 1938 | const int_payload = try scope.arena().create(Type.Payload.IntUnsigned); | ||
| 1939 | int_payload.* = .{ .bits = bits }; | ||
| 1940 | return Type.initPayload(&int_payload.base); | ||
| 1941 | } | ||
| 1942 | } | ||
| 1943 | |||
| 1944 | fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { | ||
| 1945 | // If the types are the same, we can return the operand. | ||
| 1946 | if (dest_type.eql(inst.ty)) | ||
| 1947 | return inst; | ||
| 1948 | |||
| 1949 | const in_memory_result = coerceInMemoryAllowed(dest_type, inst.ty); | ||
| 1950 | if (in_memory_result == .ok) { | ||
| 1951 | return self.bitcast(scope, dest_type, inst); | ||
| 1952 | } | ||
| 1953 | |||
| 1954 | // *[N]T to []T | ||
| 1955 | if (inst.ty.isSinglePointer() and dest_type.isSlice() and | ||
| 1956 | (!inst.ty.pointerIsConst() or dest_type.pointerIsConst())) | ||
| 1957 | { | ||
| 1958 | const array_type = inst.ty.elemType(); | ||
| 1959 | const dst_elem_type = dest_type.elemType(); | ||
| 1960 | if (array_type.zigTypeTag() == .Array and | ||
| 1961 | coerceInMemoryAllowed(dst_elem_type, array_type.elemType()) == .ok) | ||
| 1962 | { | ||
| 1963 | return self.coerceArrayPtrToSlice(scope, dest_type, inst); | ||
| 1964 | } | ||
| 1965 | } | ||
| 1966 | |||
| 1967 | // comptime_int to fixed-width integer | ||
| 1968 | if (inst.ty.zigTypeTag() == .ComptimeInt and dest_type.zigTypeTag() == .Int) { | ||
| 1969 | // The representation is already correct; we only need to make sure it fits in the destination type. | ||
| 1970 | const val = inst.value().?; // comptime_int always has comptime known value | ||
| 1971 | if (!val.intFitsInType(dest_type, self.target())) { | ||
| 1972 | return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val }); | ||
| 1973 | } | ||
| 1974 | return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); | ||
| 1975 | } | ||
| 1976 | |||
| 1977 | // integer widening | ||
| 1978 | if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) { | ||
| 1979 | const src_info = inst.ty.intInfo(self.target()); | ||
| 1980 | const dst_info = dest_type.intInfo(self.target()); | ||
| 1981 | if (src_info.signed == dst_info.signed and dst_info.bits >= src_info.bits) { | ||
| 1982 | if (inst.value()) |val| { | ||
| 1983 | return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); | ||
| 1984 | } else { | ||
| 1985 | return self.fail(scope, inst.src, "TODO implement runtime integer widening", .{}); | ||
| 1986 | } | ||
| 1987 | } else { | ||
| 1988 | return self.fail(scope, inst.src, "TODO implement more int widening {} to {}", .{ inst.ty, dest_type }); | ||
| 1989 | } | ||
| 1990 | } | ||
| 1991 | |||
| 1992 | return self.fail(scope, inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type }); | ||
| 1993 | } | ||
| 1994 | |||
| 1995 | fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { | ||
| 1996 | if (inst.value()) |val| { | ||
| 1997 | // Keep the comptime Value representation; take the new type. | ||
| 1998 | return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); | ||
| 1999 | } | ||
| 2000 | // TODO validate the type size and other compile errors | ||
| 2001 | const b = try self.requireRuntimeBlock(scope, inst.src); | ||
| 2002 | return self.addNewInstArgs(b, inst.src, dest_type, Inst.BitCast, Inst.Args(Inst.BitCast){ .operand = inst }); | ||
| 2003 | } | ||
| 2004 | |||
| 2005 | fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { | ||
| 2006 | if (inst.value()) |val| { | ||
| 2007 | // The comptime Value representation is compatible with both types. | ||
| 2008 | return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); | ||
| 2009 | } | ||
| 2010 | return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{}); | ||
| 2011 | } | ||
| 2012 | |||
| 2013 | fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: var) InnerError { | ||
| 2014 | @setCold(true); | ||
| 2015 | const err_msg = try ErrorMsg.create(self.allocator, src, format, args); | ||
| 2016 | return self.failWithOwnedErrorMsg(scope, src, err_msg); | ||
| 2017 | } | ||
| 2018 | |||
| 2019 | fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *ErrorMsg) InnerError { | ||
| 2020 | { | ||
| 2021 | errdefer err_msg.destroy(self.allocator); | ||
| 2022 | try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); | ||
| 2023 | try self.failed_files.ensureCapacity(self.failed_files.size + 1); | ||
| 2024 | } | ||
| 2025 | switch (scope.tag) { | ||
| 2026 | .decl => { | ||
| 2027 | const decl = scope.cast(Scope.DeclAnalysis).?.decl; | ||
| 2028 | switch (decl.analysis) { | ||
| 2029 | .initial_in_progress => decl.analysis = .initial_sema_failure, | ||
| 2030 | .repeat_in_progress => decl.analysis = .repeat_sema_failure, | ||
| 2031 | else => unreachable, | ||
| 2032 | } | ||
| 2033 | self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg); | ||
| 2034 | }, | ||
| 2035 | .block => { | ||
| 2036 | const block = scope.cast(Scope.Block).?; | ||
| 2037 | block.func.analysis = .sema_failure; | ||
| 2038 | self.failed_decls.putAssumeCapacityNoClobber(block.decl, err_msg); | ||
| 2039 | }, | ||
| 2040 | .zir_module => { | ||
| 2041 | const zir_module = scope.cast(Scope.ZIRModule).?; | ||
| 2042 | zir_module.status = .loaded_sema_failure; | ||
| 2043 | self.failed_files.putAssumeCapacityNoClobber(zir_module, err_msg); | ||
| 2044 | }, | ||
| 2045 | } | ||
| 2046 | return error.AnalysisFail; | ||
| 2047 | } | ||
| 2048 | |||
| 2049 | const InMemoryCoercionResult = enum { | ||
| 2050 | ok, | ||
| 2051 | no_match, | ||
| 2052 | }; | ||
| 2053 | |||
| 2054 | fn coerceInMemoryAllowed(dest_type: Type, src_type: Type) InMemoryCoercionResult { | ||
| 2055 | if (dest_type.eql(src_type)) | ||
| 2056 | return .ok; | ||
| 2057 | |||
| 2058 | // TODO: implement more of this function | ||
| 2059 | |||
| 2060 | return .no_match; | ||
| 2061 | } | ||
| 2062 | |||
| 2063 | pub const ErrorMsg = struct { | ||
| 2064 | byte_offset: usize, | ||
| 2065 | msg: []const u8, | ||
| 2066 | |||
| 2067 | pub fn create(allocator: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !*ErrorMsg { | ||
| 2068 | const self = try allocator.create(ErrorMsg); | ||
| 2069 | errdefer allocator.destroy(self); | ||
| 2070 | self.* = try init(allocator, byte_offset, format, args); | ||
| 2071 | return self; | ||
| 2072 | } | ||
| 2073 | |||
| 2074 | /// Assumes the ErrorMsg struct and msg were both allocated with allocator. | ||
| 2075 | pub fn destroy(self: *ErrorMsg, allocator: *Allocator) void { | ||
| 2076 | self.deinit(allocator); | ||
| 2077 | allocator.destroy(self); | ||
| 2078 | } | ||
| 2079 | |||
| 2080 | pub fn init(allocator: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !ErrorMsg { | ||
| 2081 | return ErrorMsg{ | ||
| 2082 | .byte_offset = byte_offset, | ||
| 2083 | .msg = try std.fmt.allocPrint(allocator, format, args), | ||
| 2084 | }; | ||
| 2085 | } | ||
| 2086 | |||
| 2087 | pub fn deinit(self: *ErrorMsg, allocator: *Allocator) void { | ||
| 2088 | allocator.free(self.msg); | ||
| 2089 | self.* = undefined; | ||
| 2090 | } | ||
| 2091 | }; | ||
src-self-hosted/Package.zig created+53| ... | @@ -0,0 +1,53 @@ | ||
| 1 | pub const Table = std.StringHashMap(*Package); | ||
| 2 | |||
| 3 | root_src_dir: std.fs.Dir, | ||
| 4 | /// Relative to `root_src_dir`. | ||
| 5 | root_src_path: []const u8, | ||
| 6 | table: Table, | ||
| 7 | |||
| 8 | /// No references to `root_src_dir` and `root_src_path` are kept. | ||
| 9 | pub fn create( | ||
| 10 | allocator: *mem.Allocator, | ||
| 11 | base_dir: std.fs.Dir, | ||
| 12 | /// Relative to `base_dir`. | ||
| 13 | root_src_dir: []const u8, | ||
| 14 | /// Relative to `root_src_dir`. | ||
| 15 | root_src_path: []const u8, | ||
| 16 | ) !*Package { | ||
| 17 | const ptr = try allocator.create(Package); | ||
| 18 | errdefer allocator.destroy(ptr); | ||
| 19 | const root_src_path_dupe = try mem.dupe(allocator, u8, root_src_path); | ||
| 20 | errdefer allocator.free(root_src_path_dupe); | ||
| 21 | ptr.* = .{ | ||
| 22 | .root_src_dir = try base_dir.openDir(root_src_dir, .{}), | ||
| 23 | .root_src_path = root_src_path_dupe, | ||
| 24 | .table = Table.init(allocator), | ||
| 25 | }; | ||
| 26 | return ptr; | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn destroy(self: *Package) void { | ||
| 30 | const allocator = self.table.allocator; | ||
| 31 | self.root_src_dir.close(); | ||
| 32 | allocator.free(self.root_src_path); | ||
| 33 | { | ||
| 34 | var it = self.table.iterator(); | ||
| 35 | while (it.next()) |kv| { | ||
| 36 | allocator.free(kv.key); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | self.table.deinit(); | ||
| 40 | allocator.destroy(self); | ||
| 41 | } | ||
| 42 | |||
| 43 | pub fn add(self: *Package, name: []const u8, package: *Package) !void { | ||
| 44 | const name_dupe = try mem.dupe(self.table.allocator, u8, name); | ||
| 45 | errdefer self.table.allocator.deinit(name_dupe); | ||
| 46 | const entry = try self.table.put(name_dupe, package); | ||
| 47 | assert(entry == null); | ||
| 48 | } | ||
| 49 | |||
| 50 | const std = @import("std"); | ||
| 51 | const mem = std.mem; | ||
| 52 | const assert = std.debug.assert; | ||
| 53 | const Package = @This(); | ||
src-self-hosted/TypedValue.zig created+23| ... | @@ -0,0 +1,23 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Type = @import("type.zig").Type; | ||
| 3 | const Value = @import("value.zig").Value; | ||
| 4 | const Allocator = std.mem.Allocator; | ||
| 5 | const TypedValue = @This(); | ||
| 6 | |||
| 7 | ty: Type, | ||
| 8 | val: Value, | ||
| 9 | |||
| 10 | /// Memory management for TypedValue. The main purpose of this type | ||
| 11 | /// is to be small and have a deinit() function to free associated resources. | ||
| 12 | pub const Managed = struct { | ||
| 13 | /// If the tag value is less than Tag.no_payload_count, then no pointer | ||
| 14 | /// dereference is needed. | ||
| 15 | typed_value: TypedValue, | ||
| 16 | /// If this is `null` then there is no memory management needed. | ||
| 17 | arena: ?*std.heap.ArenaAllocator.State = null, | ||
| 18 | |||
| 19 | pub fn deinit(self: *Managed, allocator: *Allocator) void { | ||
| 20 | if (self.arena) |a| a.promote(allocator).deinit(); | ||
| 21 | self.* = undefined; | ||
| 22 | } | ||
| 23 | }; | ||
src-self-hosted/c.zig deleted-7| ... | @@ -1,7 +0,0 @@ | ||
| 1 | pub usingnamespace @cImport({ | ||
| 2 | @cDefine("__STDC_CONSTANT_MACROS", ""); | ||
| 3 | @cDefine("__STDC_LIMIT_MACROS", ""); | ||
| 4 | @cInclude("inttypes.h"); | ||
| 5 | @cInclude("config.h"); | ||
| 6 | @cInclude("zig_llvm.h"); | ||
| 7 | }); | ||
src-self-hosted/codegen.zig+349-295| ... | @@ -4,64 +4,155 @@ const assert = std.debug.assert; | ... | @@ -4,64 +4,155 @@ const assert = std.debug.assert; |
| 4 | const ir = @import("ir.zig"); | 4 | const ir = @import("ir.zig"); |
| 5 | const Type = @import("type.zig").Type; | 5 | const Type = @import("type.zig").Type; |
| 6 | const Value = @import("value.zig").Value; | 6 | const Value = @import("value.zig").Value; |
| 7 | const TypedValue = @import("TypedValue.zig"); | ||
| 8 | const link = @import("link.zig"); | ||
| 9 | const Module = @import("Module.zig"); | ||
| 10 | const ErrorMsg = Module.ErrorMsg; | ||
| 7 | const Target = std.Target; | 11 | const Target = std.Target; |
| 8 | 12 | const Allocator = mem.Allocator; | |
| 9 | pub const ErrorMsg = struct { | 13 | |
| 10 | byte_offset: usize, | 14 | pub const Result = union(enum) { |
| 11 | msg: []const u8, | 15 | /// The `code` parameter passed to `generateSymbol` has the value appended. |
| 12 | }; | 16 | appended: void, |
| 13 | 17 | /// The value is available externally, `code` is unused. | |
| 14 | pub const Symbol = struct { | 18 | externally_managed: []const u8, |
| 15 | errors: []ErrorMsg, | 19 | fail: *Module.ErrorMsg, |
| 16 | |||
| 17 | pub fn deinit(self: *Symbol, allocator: *mem.Allocator) void { | ||
| 18 | for (self.errors) |err| { | ||
| 19 | allocator.free(err.msg); | ||
| 20 | } | ||
| 21 | allocator.free(self.errors); | ||
| 22 | self.* = undefined; | ||
| 23 | } | ||
| 24 | }; | 20 | }; |
| 25 | 21 | ||
| 26 | pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std.ArrayList(u8)) !Symbol { | 22 | pub fn generateSymbol( |
| 23 | bin_file: *link.ElfFile, | ||
| 24 | src: usize, | ||
| 25 | typed_value: TypedValue, | ||
| 26 | code: *std.ArrayList(u8), | ||
| 27 | ) error{ | ||
| 28 | OutOfMemory, | ||
| 29 | /// A Decl that this symbol depends on had a semantic analysis failure. | ||
| 30 | AnalysisFail, | ||
| 31 | }!Result { | ||
| 27 | switch (typed_value.ty.zigTypeTag()) { | 32 | switch (typed_value.ty.zigTypeTag()) { |
| 28 | .Fn => { | 33 | .Fn => { |
| 29 | const index = typed_value.val.cast(Value.Payload.Function).?.index; | 34 | const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; |
| 30 | const module_fn = module.fns[index]; | ||
| 31 | 35 | ||
| 32 | var function = Function{ | 36 | var function = Function{ |
| 33 | .module = &module, | 37 | .target = &bin_file.options.target, |
| 34 | .mod_fn = &module_fn, | 38 | .bin_file = bin_file, |
| 39 | .mod_fn = module_fn, | ||
| 35 | .code = code, | 40 | .code = code, |
| 36 | .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(code.allocator), | 41 | .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(bin_file.allocator), |
| 37 | .errors = std.ArrayList(ErrorMsg).init(code.allocator), | 42 | .err_msg = null, |
| 38 | }; | 43 | }; |
| 39 | defer function.inst_table.deinit(); | 44 | defer function.inst_table.deinit(); |
| 40 | defer function.errors.deinit(); | ||
| 41 | 45 | ||
| 42 | for (module_fn.body.instructions) |inst| { | 46 | for (module_fn.analysis.success.instructions) |inst| { |
| 43 | const new_inst = function.genFuncInst(inst) catch |err| switch (err) { | 47 | const new_inst = function.genFuncInst(inst) catch |err| switch (err) { |
| 44 | error.CodegenFail => { | 48 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, |
| 45 | assert(function.errors.items.len != 0); | ||
| 46 | break; | ||
| 47 | }, | ||
| 48 | else => |e| return e, | 49 | else => |e| return e, |
| 49 | }; | 50 | }; |
| 50 | try function.inst_table.putNoClobber(inst, new_inst); | 51 | try function.inst_table.putNoClobber(inst, new_inst); |
| 51 | } | 52 | } |
| 52 | 53 | ||
| 53 | return Symbol{ .errors = function.errors.toOwnedSlice() }; | 54 | if (function.err_msg) |em| { |
| 55 | return Result{ .fail = em }; | ||
| 56 | } else { | ||
| 57 | return Result{ .appended = {} }; | ||
| 58 | } | ||
| 59 | }, | ||
| 60 | .Array => { | ||
| 61 | if (typed_value.val.cast(Value.Payload.Bytes)) |payload| { | ||
| 62 | if (typed_value.ty.arraySentinel()) |sentinel| { | ||
| 63 | try code.ensureCapacity(code.items.len + payload.data.len + 1); | ||
| 64 | code.appendSliceAssumeCapacity(payload.data); | ||
| 65 | const prev_len = code.items.len; | ||
| 66 | switch (try generateSymbol(bin_file, src, .{ | ||
| 67 | .ty = typed_value.ty.elemType(), | ||
| 68 | .val = sentinel, | ||
| 69 | }, code)) { | ||
| 70 | .appended => return Result{ .appended = {} }, | ||
| 71 | .externally_managed => |slice| { | ||
| 72 | code.appendSliceAssumeCapacity(slice); | ||
| 73 | return Result{ .appended = {} }; | ||
| 74 | }, | ||
| 75 | .fail => |em| return Result{ .fail = em }, | ||
| 76 | } | ||
| 77 | } else { | ||
| 78 | return Result{ .externally_managed = payload.data }; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | return Result{ | ||
| 82 | .fail = try ErrorMsg.create( | ||
| 83 | bin_file.allocator, | ||
| 84 | src, | ||
| 85 | "TODO implement generateSymbol for more kinds of arrays", | ||
| 86 | .{}, | ||
| 87 | ), | ||
| 88 | }; | ||
| 89 | }, | ||
| 90 | .Pointer => { | ||
| 91 | if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| { | ||
| 92 | const decl = payload.decl; | ||
| 93 | if (decl.analysis != .complete) return error.AnalysisFail; | ||
| 94 | assert(decl.link.local_sym_index != 0); | ||
| 95 | // TODO handle the dependency of this symbol on the decl's vaddr. | ||
| 96 | // If the decl changes vaddr, then this symbol needs to get regenerated. | ||
| 97 | const vaddr = bin_file.local_symbols.items[decl.link.local_sym_index].st_value; | ||
| 98 | const endian = bin_file.options.target.cpu.arch.endian(); | ||
| 99 | switch (bin_file.ptr_width) { | ||
| 100 | .p32 => { | ||
| 101 | try code.resize(4); | ||
| 102 | mem.writeInt(u32, code.items[0..4], @intCast(u32, vaddr), endian); | ||
| 103 | }, | ||
| 104 | .p64 => { | ||
| 105 | try code.resize(8); | ||
| 106 | mem.writeInt(u64, code.items[0..8], vaddr, endian); | ||
| 107 | }, | ||
| 108 | } | ||
| 109 | return Result{ .appended = {} }; | ||
| 110 | } | ||
| 111 | return Result{ | ||
| 112 | .fail = try ErrorMsg.create( | ||
| 113 | bin_file.allocator, | ||
| 114 | src, | ||
| 115 | "TODO implement generateSymbol for pointer {}", | ||
| 116 | .{typed_value.val}, | ||
| 117 | ), | ||
| 118 | }; | ||
| 119 | }, | ||
| 120 | .Int => { | ||
| 121 | const info = typed_value.ty.intInfo(bin_file.options.target); | ||
| 122 | if (info.bits == 8 and !info.signed) { | ||
| 123 | const x = typed_value.val.toUnsignedInt(); | ||
| 124 | try code.append(@intCast(u8, x)); | ||
| 125 | return Result{ .appended = {} }; | ||
| 126 | } | ||
| 127 | return Result{ | ||
| 128 | .fail = try ErrorMsg.create( | ||
| 129 | bin_file.allocator, | ||
| 130 | src, | ||
| 131 | "TODO implement generateSymbol for int type '{}'", | ||
| 132 | .{typed_value.ty}, | ||
| 133 | ), | ||
| 134 | }; | ||
| 135 | }, | ||
| 136 | else => |t| { | ||
| 137 | return Result{ | ||
| 138 | .fail = try ErrorMsg.create( | ||
| 139 | bin_file.allocator, | ||
| 140 | src, | ||
| 141 | "TODO implement generateSymbol for type '{}'", | ||
| 142 | .{@tagName(t)}, | ||
| 143 | ), | ||
| 144 | }; | ||
| 54 | }, | 145 | }, |
| 55 | else => @panic("TODO implement generateSymbol for non-function types"), | ||
| 56 | } | 146 | } |
| 57 | } | 147 | } |
| 58 | 148 | ||
| 59 | const Function = struct { | 149 | const Function = struct { |
| 60 | module: *const ir.Module, | 150 | bin_file: *link.ElfFile, |
| 61 | mod_fn: *const ir.Module.Fn, | 151 | target: *const std.Target, |
| 152 | mod_fn: *const Module.Fn, | ||
| 62 | code: *std.ArrayList(u8), | 153 | code: *std.ArrayList(u8), |
| 63 | inst_table: std.AutoHashMap(*ir.Inst, MCValue), | 154 | inst_table: std.AutoHashMap(*ir.Inst, MCValue), |
| 64 | errors: std.ArrayList(ErrorMsg), | 155 | err_msg: ?*ErrorMsg, |
| 65 | 156 | ||
| 66 | const MCValue = union(enum) { | 157 | const MCValue = union(enum) { |
| 67 | none, | 158 | none, |
| ... | @@ -73,11 +164,14 @@ const Function = struct { | ... | @@ -73,11 +164,14 @@ const Function = struct { |
| 73 | /// The value is in a target-specific register. The value can | 164 | /// The value is in a target-specific register. The value can |
| 74 | /// be @intToEnum casted to the respective Reg enum. | 165 | /// be @intToEnum casted to the respective Reg enum. |
| 75 | register: usize, | 166 | register: usize, |
| 167 | /// The value is in memory at a hard-coded address. | ||
| 168 | memory: u64, | ||
| 76 | }; | 169 | }; |
| 77 | 170 | ||
| 78 | fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { | 171 | fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { |
| 79 | switch (inst.tag) { | 172 | switch (inst.tag) { |
| 80 | .breakpoint => return self.genBreakpoint(inst.src), | 173 | .breakpoint => return self.genBreakpoint(inst.src), |
| 174 | .call => return self.genCall(inst.cast(ir.Inst.Call).?), | ||
| 81 | .unreach => return MCValue{ .unreach = {} }, | 175 | .unreach => return MCValue{ .unreach = {} }, |
| 82 | .constant => unreachable, // excluded from function bodies | 176 | .constant => unreachable, // excluded from function bodies |
| 83 | .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), | 177 | .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), |
| ... | @@ -92,54 +186,76 @@ const Function = struct { | ... | @@ -92,54 +186,76 @@ const Function = struct { |
| 92 | } | 186 | } |
| 93 | 187 | ||
| 94 | fn genBreakpoint(self: *Function, src: usize) !MCValue { | 188 | fn genBreakpoint(self: *Function, src: usize) !MCValue { |
| 95 | switch (self.module.target.cpu.arch) { | 189 | switch (self.target.cpu.arch) { |
| 96 | .i386, .x86_64 => { | 190 | .i386, .x86_64 => { |
| 97 | try self.code.append(0xcc); // int3 | 191 | try self.code.append(0xcc); // int3 |
| 98 | }, | 192 | }, |
| 99 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.module.target.cpu.arch}), | 193 | else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), |
| 194 | } | ||
| 195 | return .none; | ||
| 196 | } | ||
| 197 | |||
| 198 | fn genCall(self: *Function, inst: *ir.Inst.Call) !MCValue { | ||
| 199 | if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| { | ||
| 200 | if (inst.args.args.len != 0) { | ||
| 201 | return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{}); | ||
| 202 | } | ||
| 203 | |||
| 204 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { | ||
| 205 | const func = func_val.func; | ||
| 206 | return self.fail(inst.base.src, "TODO implement calling function", .{}); | ||
| 207 | } else { | ||
| 208 | return self.fail(inst.base.src, "TODO implement calling weird function values", .{}); | ||
| 209 | } | ||
| 210 | } else { | ||
| 211 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | ||
| 212 | } | ||
| 213 | |||
| 214 | switch (self.target.cpu.arch) { | ||
| 215 | else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), | ||
| 100 | } | 216 | } |
| 101 | return .unreach; | ||
| 102 | } | 217 | } |
| 103 | 218 | ||
| 104 | fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue { | 219 | fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue { |
| 105 | switch (self.module.target.cpu.arch) { | 220 | switch (self.target.cpu.arch) { |
| 106 | .i386, .x86_64 => { | 221 | .i386, .x86_64 => { |
| 107 | try self.code.append(0xc3); // ret | 222 | try self.code.append(0xc3); // ret |
| 108 | }, | 223 | }, |
| 109 | else => return self.fail(inst.base.src, "TODO implement return for {}", .{self.module.target.cpu.arch}), | 224 | else => return self.fail(inst.base.src, "TODO implement return for {}", .{self.target.cpu.arch}), |
| 110 | } | 225 | } |
| 111 | return .unreach; | 226 | return .unreach; |
| 112 | } | 227 | } |
| 113 | 228 | ||
| 114 | fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue { | 229 | fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue { |
| 115 | switch (self.module.target.cpu.arch) { | 230 | switch (self.target.cpu.arch) { |
| 116 | else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.module.target.cpu.arch}), | 231 | else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}), |
| 117 | } | 232 | } |
| 118 | } | 233 | } |
| 119 | 234 | ||
| 120 | fn genCondBr(self: *Function, inst: *ir.Inst.CondBr) !MCValue { | 235 | fn genCondBr(self: *Function, inst: *ir.Inst.CondBr) !MCValue { |
| 121 | switch (self.module.target.cpu.arch) { | 236 | switch (self.target.cpu.arch) { |
| 122 | else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.module.target.cpu.arch}), | 237 | else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}), |
| 123 | } | 238 | } |
| 124 | } | 239 | } |
| 125 | 240 | ||
| 126 | fn genIsNull(self: *Function, inst: *ir.Inst.IsNull) !MCValue { | 241 | fn genIsNull(self: *Function, inst: *ir.Inst.IsNull) !MCValue { |
| 127 | switch (self.module.target.cpu.arch) { | 242 | switch (self.target.cpu.arch) { |
| 128 | else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.module.target.cpu.arch}), | 243 | else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.target.cpu.arch}), |
| 129 | } | 244 | } |
| 130 | } | 245 | } |
| 131 | 246 | ||
| 132 | fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull) !MCValue { | 247 | fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull) !MCValue { |
| 133 | // Here you can specialize this instruction if it makes sense to, otherwise the default | 248 | // Here you can specialize this instruction if it makes sense to, otherwise the default |
| 134 | // will call genIsNull and invert the result. | 249 | // will call genIsNull and invert the result. |
| 135 | switch (self.module.target.cpu.arch) { | 250 | switch (self.target.cpu.arch) { |
| 136 | else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}), | 251 | else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}), |
| 137 | } | 252 | } |
| 138 | } | 253 | } |
| 139 | 254 | ||
| 140 | fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void { | 255 | fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void { |
| 141 | switch (self.module.target.cpu.arch) { | 256 | switch (self.target.cpu.arch) { |
| 142 | .i386, .x86_64 => { | 257 | .i386, .x86_64 => { |
| 258 | // TODO x86 treats the operands as signed | ||
| 143 | if (amount <= std.math.maxInt(u8)) { | 259 | if (amount <= std.math.maxInt(u8)) { |
| 144 | try self.code.resize(self.code.items.len + 2); | 260 | try self.code.resize(self.code.items.len + 2); |
| 145 | self.code.items[self.code.items.len - 2] = 0xeb; | 261 | self.code.items[self.code.items.len - 2] = 0xeb; |
| ... | @@ -151,13 +267,13 @@ const Function = struct { | ... | @@ -151,13 +267,13 @@ const Function = struct { |
| 151 | mem.writeIntLittle(u32, imm_ptr, amount); | 267 | mem.writeIntLittle(u32, imm_ptr, amount); |
| 152 | } | 268 | } |
| 153 | }, | 269 | }, |
| 154 | else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.module.target.cpu.arch}), | 270 | else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.target.cpu.arch}), |
| 155 | } | 271 | } |
| 156 | } | 272 | } |
| 157 | 273 | ||
| 158 | fn genAsm(self: *Function, inst: *ir.Inst.Assembly) !MCValue { | 274 | fn genAsm(self: *Function, inst: *ir.Inst.Assembly) !MCValue { |
| 159 | // TODO convert to inline function | 275 | // TODO convert to inline function |
| 160 | switch (self.module.target.cpu.arch) { | 276 | switch (self.target.cpu.arch) { |
| 161 | .arm => return self.genAsmArch(.arm, inst), | 277 | .arm => return self.genAsmArch(.arm, inst), |
| 162 | .armeb => return self.genAsmArch(.armeb, inst), | 278 | .armeb => return self.genAsmArch(.armeb, inst), |
| 163 | .aarch64 => return self.genAsmArch(.aarch64, inst), | 279 | .aarch64 => return self.genAsmArch(.aarch64, inst), |
| ... | @@ -246,128 +362,182 @@ const Function = struct { | ... | @@ -246,128 +362,182 @@ const Function = struct { |
| 246 | } | 362 | } |
| 247 | } | 363 | } |
| 248 | 364 | ||
| 249 | fn genSetReg(self: *Function, src: usize, comptime arch: Target.Cpu.Arch, reg: Reg(arch), mcv: MCValue) !void { | 365 | fn genSetReg(self: *Function, src: usize, comptime arch: Target.Cpu.Arch, reg: Reg(arch), mcv: MCValue) error{ CodegenFail, OutOfMemory }!void { |
| 250 | switch (arch) { | 366 | switch (arch) { |
| 251 | .x86_64 => switch (reg) { | 367 | .x86_64 => switch (mcv) { |
| 252 | .rax => switch (mcv) { | 368 | .none, .unreach => unreachable, |
| 253 | .none, .unreach => unreachable, | 369 | .immediate => |x| { |
| 254 | .immediate => |x| { | 370 | if (reg.size() != 64) { |
| 255 | // Setting the eax register zeroes the upper part of rax, so if the number is small | 371 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); |
| 256 | // enough, that is preferable. | 372 | } |
| 257 | // Best case: zero | 373 | // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit |
| 258 | // 31 c0 xor eax,eax | 374 | // register is the fastest way to zero a register. |
| 259 | if (x == 0) { | 375 | if (x == 0) { |
| 260 | return self.code.appendSlice(&[_]u8{ 0x31, 0xc0 }); | 376 | // The encoding for `xor r32, r32` is `0x31 /r`. |
| 377 | // Section 3.1.1.1 of the Intel x64 Manual states that "/r indicates that the | ||
| 378 | // ModR/M byte of the instruction contains a register operand and an r/m operand." | ||
| 379 | // | ||
| 380 | // R/M bytes are composed of two bits for the mode, then three bits for the register, | ||
| 381 | // then three bits for the operand. Since we're zeroing a register, the two three-bit | ||
| 382 | // values will be identical, and the mode is three (the raw register value). | ||
| 383 | // | ||
| 384 | if (reg.isExtended()) { | ||
| 385 | // If we're accessing e.g. r8d, we need to use a REX prefix before the actual operation. Since | ||
| 386 | // this is a 32-bit operation, the W flag is set to zero. X is also zero, as we're not using a SIB. | ||
| 387 | // Both R and B are set, as we're extending, in effect, the register bits *and* the operand. | ||
| 388 | // | ||
| 389 | // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. In this case, that's | ||
| 390 | // b01000101, or 0x45. | ||
| 391 | return self.code.appendSlice(&[_]u8{ | ||
| 392 | 0x45, | ||
| 393 | 0x31, | ||
| 394 | 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()), | ||
| 395 | }); | ||
| 396 | } else { | ||
| 397 | return self.code.appendSlice(&[_]u8{ | ||
| 398 | 0x31, | ||
| 399 | 0xC0 | (@as(u8, reg.id()) << 3) | reg.id(), | ||
| 400 | }); | ||
| 261 | } | 401 | } |
| 262 | // Next best case: set eax with 4 bytes | 402 | } |
| 263 | // b8 04 03 02 01 mov eax,0x01020304 | 403 | if (x <= std.math.maxInt(u32)) { |
| 264 | if (x <= std.math.maxInt(u32)) { | 404 | // Next best case: if we set the lower four bytes, the upper four will be zeroed. |
| 405 | // | ||
| 406 | // The encoding for `mov IMM32 -> REG` is (0xB8 + R) IMM. | ||
| 407 | if (reg.isExtended()) { | ||
| 408 | // Just as with XORing, we need a REX prefix. This time though, we only | ||
| 409 | // need the B bit set, as we're extending the opcode's register field, | ||
| 410 | // and there is no Mod R/M byte. | ||
| 411 | // | ||
| 412 | // Thus, we need b01000001, or 0x41. | ||
| 413 | try self.code.resize(self.code.items.len + 6); | ||
| 414 | self.code.items[self.code.items.len - 6] = 0x41; | ||
| 415 | } else { | ||
| 265 | try self.code.resize(self.code.items.len + 5); | 416 | try self.code.resize(self.code.items.len + 5); |
| 266 | self.code.items[self.code.items.len - 5] = 0xb8; | ||
| 267 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | ||
| 268 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); | ||
| 269 | return; | ||
| 270 | } | 417 | } |
| 271 | // Worst case: set rax with 8 bytes | 418 | self.code.items[self.code.items.len - 5] = 0xB8 | @as(u8, reg.id() & 0b111); |
| 272 | // 48 b8 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708 | 419 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; |
| 273 | try self.code.resize(self.code.items.len + 10); | 420 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); |
| 274 | self.code.items[self.code.items.len - 10] = 0x48; | ||
| 275 | self.code.items[self.code.items.len - 9] = 0xb8; | ||
| 276 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; | ||
| 277 | mem.writeIntLittle(u64, imm_ptr, x); | ||
| 278 | return; | 421 | return; |
| 279 | }, | 422 | } |
| 280 | .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rax = embedded_in_code", .{}), | 423 | // Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls |
| 281 | .register => return self.fail(src, "TODO implement x86_64 genSetReg %rax = register", .{}), | 424 | // this `movabs`, though this is officially just a different variant of the plain `mov` |
| 425 | // instruction. | ||
| 426 | // | ||
| 427 | // This encoding is, in fact, the *same* as the one used for 32-bit loads. The only | ||
| 428 | // difference is that we set REX.W before the instruction, which extends the load to | ||
| 429 | // 64-bit and uses the full bit-width of the register. | ||
| 430 | // | ||
| 431 | // Since we always need a REX here, let's just check if we also need to set REX.B. | ||
| 432 | // | ||
| 433 | // In this case, the encoding of the REX byte is 0b0100100B | ||
| 434 | const REX = 0x48 | (if (reg.isExtended()) @as(u8, 0x01) else 0); | ||
| 435 | try self.code.resize(self.code.items.len + 10); | ||
| 436 | self.code.items[self.code.items.len - 10] = REX; | ||
| 437 | self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111); | ||
| 438 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; | ||
| 439 | mem.writeIntLittle(u64, imm_ptr, x); | ||
| 282 | }, | 440 | }, |
| 283 | .rdx => switch (mcv) { | 441 | .embedded_in_code => |code_offset| { |
| 284 | .none, .unreach => unreachable, | 442 | if (reg.size() != 64) { |
| 285 | .immediate => |x| { | 443 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); |
| 286 | // Setting the edx register zeroes the upper part of rdx, so if the number is small | 444 | } |
| 287 | // enough, that is preferable. | 445 | // We need the offset from RIP in a signed i32 twos complement. |
| 288 | // Best case: zero | 446 | // The instruction is 7 bytes long and RIP points to the next instruction. |
| 289 | // 31 d2 xor edx,edx | 447 | // |
| 290 | if (x == 0) { | 448 | // 64-bit LEA is encoded as REX.W 8D /r. If the register is extended, the REX byte is modified, |
| 291 | return self.code.appendSlice(&[_]u8{ 0x31, 0xd2 }); | 449 | // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three |
| 292 | } | 450 | // bits as five. |
| 293 | // Next best case: set edx with 4 bytes | 451 | // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id. |
| 294 | // ba 04 03 02 01 mov edx,0x1020304 | 452 | try self.code.resize(self.code.items.len + 7); |
| 295 | if (x <= std.math.maxInt(u32)) { | 453 | const REX = 0x48 | if (reg.isExtended()) @as(u8, 1) else 0; |
| 296 | try self.code.resize(self.code.items.len + 5); | 454 | const rip = self.code.items.len; |
| 297 | self.code.items[self.code.items.len - 5] = 0xba; | 455 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); |
| 298 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | 456 | const offset = @intCast(i32, big_offset); |
| 299 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); | 457 | self.code.items[self.code.items.len - 7] = REX; |
| 300 | return; | 458 | self.code.items[self.code.items.len - 6] = 0x8D; |
| 301 | } | 459 | self.code.items[self.code.items.len - 5] = 0b101 | (@as(u8, reg.id() & 0b111) << 3); |
| 302 | // Worst case: set rdx with 8 bytes | 460 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; |
| 303 | // 48 ba 08 07 06 05 04 03 02 01 movabs rdx,0x0102030405060708 | 461 | mem.writeIntLittle(i32, imm_ptr, offset); |
| 304 | try self.code.resize(self.code.items.len + 10); | ||
| 305 | self.code.items[self.code.items.len - 10] = 0x48; | ||
| 306 | self.code.items[self.code.items.len - 9] = 0xba; | ||
| 307 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; | ||
| 308 | mem.writeIntLittle(u64, imm_ptr, x); | ||
| 309 | return; | ||
| 310 | }, | ||
| 311 | .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = embedded_in_code", .{}), | ||
| 312 | .register => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = register", .{}), | ||
| 313 | }, | 462 | }, |
| 314 | .rdi => switch (mcv) { | 463 | .register => |r| { |
| 315 | .none, .unreach => unreachable, | 464 | if (reg.size() != 64) { |
| 316 | .immediate => |x| { | 465 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); |
| 317 | // Setting the edi register zeroes the upper part of rdi, so if the number is small | 466 | } |
| 318 | // enough, that is preferable. | 467 | const src_reg = @intToEnum(Reg(arch), @intCast(u8, r)); |
| 319 | // Best case: zero | 468 | // This is a varient of 8B /r. Since we're using 64-bit moves, we require a REX. |
| 320 | // 31 ff xor edi,edi | 469 | // This is thus three bytes: REX 0x8B R/M. |
| 321 | if (x == 0) { | 470 | // If the destination is extended, the R field must be 1. |
| 322 | return self.code.appendSlice(&[_]u8{ 0x31, 0xff }); | 471 | // If the *source* is extended, the B field must be 1. |
| 323 | } | 472 | // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle |
| 324 | // Next best case: set edi with 4 bytes | 473 | // three bits) contain the destination, and the R/M field (the lower three bits) contain the source. |
| 325 | // bf 04 03 02 01 mov edi,0x1020304 | 474 | const REX = 0x48 | (if (reg.isExtended()) @as(u8, 4) else 0) | (if (src_reg.isExtended()) @as(u8, 1) else 0); |
| 326 | if (x <= std.math.maxInt(u32)) { | 475 | const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, src_reg.id()); |
| 327 | try self.code.resize(self.code.items.len + 5); | 476 | try self.code.appendSlice(&[_]u8{ REX, 0x8B, R }); |
| 328 | self.code.items[self.code.items.len - 5] = 0xbf; | ||
| 329 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | ||
| 330 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); | ||
| 331 | return; | ||
| 332 | } | ||
| 333 | // Worst case: set rdi with 8 bytes | ||
| 334 | // 48 bf 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708 | ||
| 335 | try self.code.resize(self.code.items.len + 10); | ||
| 336 | self.code.items[self.code.items.len - 10] = 0x48; | ||
| 337 | self.code.items[self.code.items.len - 9] = 0xbf; | ||
| 338 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; | ||
| 339 | mem.writeIntLittle(u64, imm_ptr, x); | ||
| 340 | return; | ||
| 341 | }, | ||
| 342 | .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = embedded_in_code", .{}), | ||
| 343 | .register => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = register", .{}), | ||
| 344 | }, | 477 | }, |
| 345 | .rsi => switch (mcv) { | 478 | .memory => |x| { |
| 346 | .none, .unreach => unreachable, | 479 | if (reg.size() != 64) { |
| 347 | .immediate => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = immediate", .{}), | 480 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); |
| 348 | .embedded_in_code => |code_offset| { | 481 | } |
| 349 | // Examples: | 482 | if (x <= std.math.maxInt(u32)) { |
| 350 | // lea rsi, [rip + 0x01020304] | 483 | // Moving from memory to a register is a variant of `8B /r`. |
| 351 | // lea rsi, [rip - 7] | 484 | // Since we're using 64-bit moves, we require a REX. |
| 352 | // f: 48 8d 35 04 03 02 01 lea rsi,[rip+0x1020304] # 102031a <_start+0x102031a> | 485 | // This variant also requires a SIB, as it would otherwise be RIP-relative. |
| 353 | // 16: 48 8d 35 f9 ff ff ff lea rsi,[rip+0xfffffffffffffff9] # 16 <_start+0x16> | 486 | // We want mode zero with the lower three bits set to four to indicate an SIB with no other displacement. |
| 354 | // | 487 | // The SIB must be 0x25, to indicate a disp32 with no scaled index. |
| 355 | // We need the offset from RIP in a signed i32 twos complement. | 488 | // 0b00RRR100, where RRR is the lower three bits of the register ID. |
| 356 | // The instruction is 7 bytes long and RIP points to the next instruction. | 489 | // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32. |
| 357 | try self.code.resize(self.code.items.len + 7); | 490 | try self.code.resize(self.code.items.len + 8); |
| 358 | const rip = self.code.items.len; | 491 | const REX = 0x48 | if (reg.isExtended()) @as(u8, 1) else 0; |
| 359 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); | 492 | const r = 0x04 | (@as(u8, reg.id() & 0b111) << 3); |
| 360 | const offset = @intCast(i32, big_offset); | 493 | self.code.items[self.code.items.len - 8] = REX; |
| 361 | self.code.items[self.code.items.len - 7] = 0x48; | 494 | self.code.items[self.code.items.len - 7] = 0x8B; |
| 362 | self.code.items[self.code.items.len - 6] = 0x8d; | 495 | self.code.items[self.code.items.len - 6] = r; |
| 363 | self.code.items[self.code.items.len - 5] = 0x35; | 496 | self.code.items[self.code.items.len - 5] = 0x25; |
| 364 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | 497 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; |
| 365 | mem.writeIntLittle(i32, imm_ptr, offset); | 498 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); |
| 366 | return; | 499 | } else { |
| 367 | }, | 500 | // If this is RAX, we can use a direct load; otherwise, we need to load the address, then indirectly load |
| 368 | .register => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = register", .{}), | 501 | // the value. |
| 502 | if (reg.id() == 0) { | ||
| 503 | // REX.W 0xA1 moffs64* | ||
| 504 | // moffs64* is a 64-bit offset "relative to segment base", which really just means the | ||
| 505 | // absolute address for all practical purposes. | ||
| 506 | try self.code.resize(self.code.items.len + 10); | ||
| 507 | // REX.W == 0x48 | ||
| 508 | self.code.items[self.code.items.len - 10] = 0x48; | ||
| 509 | self.code.items[self.code.items.len - 9] = 0xA1; | ||
| 510 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; | ||
| 511 | mem.writeIntLittle(u64, imm_ptr, x); | ||
| 512 | } else { | ||
| 513 | // This requires two instructions; a move imm as used above, followed by an indirect load using the register | ||
| 514 | // as the address and the register as the destination. | ||
| 515 | // | ||
| 516 | // This cannot be used if the lower three bits of the id are equal to four or five, as there | ||
| 517 | // is no way to possibly encode it. This means that RSP, RBP, R12, and R13 cannot be used with | ||
| 518 | // this instruction. | ||
| 519 | const id3 = @truncate(u3, reg.id()); | ||
| 520 | std.debug.assert(id3 != 4 and id3 != 5); | ||
| 521 | |||
| 522 | // Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue. | ||
| 523 | try self.genSetReg(src, arch, reg, MCValue{ .immediate = x }); | ||
| 524 | |||
| 525 | // Now, the register contains the address of the value to load into it | ||
| 526 | // Currently, we're only allowing 64-bit registers, so we need the `REX.W 8B /r` variant. | ||
| 527 | // TODO: determine whether to allow other sized registers, and if so, handle them properly. | ||
| 528 | // This operation requires three bytes: REX 0x8B R/M | ||
| 529 | // | ||
| 530 | // For this operation, we want R/M mode *zero* (use register indirectly), and the two register | ||
| 531 | // values must match. Thus, it's 00ABCABC where ABC is the lower three bits of the register ID. | ||
| 532 | // | ||
| 533 | // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both* | ||
| 534 | // register operands need to be marked as extended. | ||
| 535 | const REX = 0x48 | if (reg.isExtended()) @as(u8, 0b0101) else 0; | ||
| 536 | const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); | ||
| 537 | try self.code.appendSlice(&[_]u8{ REX, 0x8B, RM }); | ||
| 538 | } | ||
| 539 | } | ||
| 369 | }, | 540 | }, |
| 370 | else => return self.fail(src, "TODO implement genSetReg for x86_64 '{}'", .{@tagName(reg)}), | ||
| 371 | }, | 541 | }, |
| 372 | else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}), | 542 | else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}), |
| 373 | } | 543 | } |
| ... | @@ -396,30 +566,22 @@ const Function = struct { | ... | @@ -396,30 +566,22 @@ const Function = struct { |
| 396 | } | 566 | } |
| 397 | } | 567 | } |
| 398 | 568 | ||
| 399 | fn genTypedValue(self: *Function, src: usize, typed_value: ir.TypedValue) !MCValue { | 569 | fn genTypedValue(self: *Function, src: usize, typed_value: TypedValue) !MCValue { |
| 570 | const ptr_bits = self.target.cpu.arch.ptrBitWidth(); | ||
| 571 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); | ||
| 572 | const allocator = self.code.allocator; | ||
| 400 | switch (typed_value.ty.zigTypeTag()) { | 573 | switch (typed_value.ty.zigTypeTag()) { |
| 401 | .Pointer => { | 574 | .Pointer => { |
| 402 | const ptr_elem_type = typed_value.ty.elemType(); | 575 | if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| { |
| 403 | switch (ptr_elem_type.zigTypeTag()) { | 576 | const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?]; |
| 404 | .Array => { | 577 | const decl = payload.decl; |
| 405 | // TODO more checks to make sure this can be emitted as a string literal | 578 | const got_addr = got.p_vaddr + decl.link.offset_table_index * ptr_bytes; |
| 406 | const bytes = try typed_value.val.toAllocatedBytes(self.code.allocator); | 579 | return MCValue{ .memory = got_addr }; |
| 407 | defer self.code.allocator.free(bytes); | ||
| 408 | const smaller_len = std.math.cast(u32, bytes.len) catch | ||
| 409 | return self.fail(src, "TODO handle a larger string constant", .{}); | ||
| 410 | |||
| 411 | // Emit the string literal directly into the code; jump over it. | ||
| 412 | try self.genRelativeFwdJump(src, smaller_len); | ||
| 413 | const offset = self.code.items.len; | ||
| 414 | try self.code.appendSlice(bytes); | ||
| 415 | return MCValue{ .embedded_in_code = offset }; | ||
| 416 | }, | ||
| 417 | else => |t| return self.fail(src, "TODO implement emitTypedValue for pointer to '{}'", .{@tagName(t)}), | ||
| 418 | } | 580 | } |
| 581 | return self.fail(src, "TODO codegen more kinds of const pointers", .{}); | ||
| 419 | }, | 582 | }, |
| 420 | .Int => { | 583 | .Int => { |
| 421 | const info = typed_value.ty.intInfo(self.module.target); | 584 | const info = typed_value.ty.intInfo(self.target.*); |
| 422 | const ptr_bits = self.module.target.cpu.arch.ptrBitWidth(); | ||
| 423 | if (info.bits > ptr_bits or info.signed) { | 585 | if (info.bits > ptr_bits or info.signed) { |
| 424 | return self.fail(src, "TODO const int bigger than ptr and signed int", .{}); | 586 | return self.fail(src, "TODO const int bigger than ptr and signed int", .{}); |
| 425 | } | 587 | } |
| ... | @@ -433,127 +595,19 @@ const Function = struct { | ... | @@ -433,127 +595,19 @@ const Function = struct { |
| 433 | 595 | ||
| 434 | fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } { | 596 | fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } { |
| 435 | @setCold(true); | 597 | @setCold(true); |
| 436 | const msg = try std.fmt.allocPrint(self.errors.allocator, format, args); | 598 | assert(self.err_msg == null); |
| 437 | { | 599 | self.err_msg = try ErrorMsg.create(self.code.allocator, src, format, args); |
| 438 | errdefer self.errors.allocator.free(msg); | ||
| 439 | (try self.errors.addOne()).* = .{ | ||
| 440 | .byte_offset = src, | ||
| 441 | .msg = msg, | ||
| 442 | }; | ||
| 443 | } | ||
| 444 | return error.CodegenFail; | 600 | return error.CodegenFail; |
| 445 | } | 601 | } |
| 446 | }; | 602 | }; |
| 447 | 603 | ||
| 604 | const x86_64 = @import("codegen/x86_64.zig"); | ||
| 605 | const x86 = @import("codegen/x86.zig"); | ||
| 606 | |||
| 448 | fn Reg(comptime arch: Target.Cpu.Arch) type { | 607 | fn Reg(comptime arch: Target.Cpu.Arch) type { |
| 449 | return switch (arch) { | 608 | return switch (arch) { |
| 450 | .i386 => enum { | 609 | .i386 => x86.Register, |
| 451 | eax, | 610 | .x86_64 => x86_64.Register, |
| 452 | ebx, | ||
| 453 | ecx, | ||
| 454 | edx, | ||
| 455 | ebp, | ||
| 456 | esp, | ||
| 457 | esi, | ||
| 458 | edi, | ||
| 459 | |||
| 460 | ax, | ||
| 461 | bx, | ||
| 462 | cx, | ||
| 463 | dx, | ||
| 464 | bp, | ||
| 465 | sp, | ||
| 466 | si, | ||
| 467 | di, | ||
| 468 | |||
| 469 | ah, | ||
| 470 | bh, | ||
| 471 | ch, | ||
| 472 | dh, | ||
| 473 | |||
| 474 | al, | ||
| 475 | bl, | ||
| 476 | cl, | ||
| 477 | dl, | ||
| 478 | }, | ||
| 479 | .x86_64 => enum { | ||
| 480 | rax, | ||
| 481 | rbx, | ||
| 482 | rcx, | ||
| 483 | rdx, | ||
| 484 | rbp, | ||
| 485 | rsp, | ||
| 486 | rsi, | ||
| 487 | rdi, | ||
| 488 | r8, | ||
| 489 | r9, | ||
| 490 | r10, | ||
| 491 | r11, | ||
| 492 | r12, | ||
| 493 | r13, | ||
| 494 | r14, | ||
| 495 | r15, | ||
| 496 | |||
| 497 | eax, | ||
| 498 | ebx, | ||
| 499 | ecx, | ||
| 500 | edx, | ||
| 501 | ebp, | ||
| 502 | esp, | ||
| 503 | esi, | ||
| 504 | edi, | ||
| 505 | r8d, | ||
| 506 | r9d, | ||
| 507 | r10d, | ||
| 508 | r11d, | ||
| 509 | r12d, | ||
| 510 | r13d, | ||
| 511 | r14d, | ||
| 512 | r15d, | ||
| 513 | |||
| 514 | ax, | ||
| 515 | bx, | ||
| 516 | cx, | ||
| 517 | dx, | ||
| 518 | bp, | ||
| 519 | sp, | ||
| 520 | si, | ||
| 521 | di, | ||
| 522 | r8w, | ||
| 523 | r9w, | ||
| 524 | r10w, | ||
| 525 | r11w, | ||
| 526 | r12w, | ||
| 527 | r13w, | ||
| 528 | r14w, | ||
| 529 | r15w, | ||
| 530 | |||
| 531 | ah, | ||
| 532 | bh, | ||
| 533 | ch, | ||
| 534 | dh, | ||
| 535 | bph, | ||
| 536 | sph, | ||
| 537 | sih, | ||
| 538 | dih, | ||
| 539 | |||
| 540 | al, | ||
| 541 | bl, | ||
| 542 | cl, | ||
| 543 | dl, | ||
| 544 | bpl, | ||
| 545 | spl, | ||
| 546 | sil, | ||
| 547 | dil, | ||
| 548 | r8b, | ||
| 549 | r9b, | ||
| 550 | r10b, | ||
| 551 | r11b, | ||
| 552 | r12b, | ||
| 553 | r13b, | ||
| 554 | r14b, | ||
| 555 | r15b, | ||
| 556 | }, | ||
| 557 | else => @compileError("TODO add more register enums"), | 611 | else => @compileError("TODO add more register enums"), |
| 558 | }; | 612 | }; |
| 559 | } | 613 | } |
src-self-hosted/codegen/x86.zig created+30| ... | @@ -0,0 +1,30 @@ | ||
| 1 | // zig fmt: off | ||
| 2 | pub const Register = enum(u8) { | ||
| 3 | // 0 through 7, 32-bit registers. id is int value | ||
| 4 | eax, ecx, edx, ebx, esp, ebp, esi, edi, | ||
| 5 | |||
| 6 | // 8-15, 16-bit registers. id is int value - 8. | ||
| 7 | ax, cx, dx, bx, sp, bp, si, di, | ||
| 8 | |||
| 9 | // 16-23, 8-bit registers. id is int value - 16. | ||
| 10 | al, bl, cl, dl, ah, ch, dh, bh, | ||
| 11 | |||
| 12 | /// Returns the bit-width of the register. | ||
| 13 | pub fn size(self: @This()) u7 { | ||
| 14 | return switch (@enumToInt(self)) { | ||
| 15 | 0...7 => 32, | ||
| 16 | 8...15 => 16, | ||
| 17 | 16...23 => 8, | ||
| 18 | else => unreachable, | ||
| 19 | }; | ||
| 20 | } | ||
| 21 | |||
| 22 | /// Returns the register's id. This is used in practically every opcode the | ||
| 23 | /// x86 has. It is embedded in some instructions, such as the `B8 +rd` move | ||
| 24 | /// instruction, and is used in the R/M byte. | ||
| 25 | pub fn id(self: @This()) u3 { | ||
| 26 | return @truncate(u3, @enumToInt(self)); | ||
| 27 | } | ||
| 28 | }; | ||
| 29 | |||
| 30 | // zig fmt: on | ||
src-self-hosted/codegen/x86_64.zig created+53| ... | @@ -0,0 +1,53 @@ | ||
| 1 | // zig fmt: off | ||
| 2 | pub const Register = enum(u8) { | ||
| 3 | // 0 through 15, 64-bit registers. 8-15 are extended. | ||
| 4 | // id is just the int value. | ||
| 5 | rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, | ||
| 6 | r8, r9, r10, r11, r12, r13, r14, r15, | ||
| 7 | |||
| 8 | // 16 through 31, 32-bit registers. 24-31 are extended. | ||
| 9 | // id is int value - 16. | ||
| 10 | eax, ecx, edx, ebx, esp, ebp, esi, edi, | ||
| 11 | r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d, | ||
| 12 | |||
| 13 | // 32-47, 16-bit registers. 40-47 are extended. | ||
| 14 | // id is int value - 32. | ||
| 15 | ax, cx, dx, bx, sp, bp, si, di, | ||
| 16 | r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w, | ||
| 17 | |||
| 18 | // 48-63, 8-bit registers. 56-63 are extended. | ||
| 19 | // id is int value - 48. | ||
| 20 | al, bl, cl, dl, ah, ch, dh, bh, | ||
| 21 | r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b, | ||
| 22 | |||
| 23 | /// Returns the bit-width of the register. | ||
| 24 | pub fn size(self: @This()) u7 { | ||
| 25 | return switch (@enumToInt(self)) { | ||
| 26 | 0...15 => 64, | ||
| 27 | 16...31 => 32, | ||
| 28 | 32...47 => 16, | ||
| 29 | 48...64 => 8, | ||
| 30 | else => unreachable, | ||
| 31 | }; | ||
| 32 | } | ||
| 33 | |||
| 34 | /// Returns whether the register is *extended*. Extended registers are the | ||
| 35 | /// new registers added with amd64, r8 through r15. This also includes any | ||
| 36 | /// other variant of access to those registers, such as r8b, r15d, and so | ||
| 37 | /// on. This is needed because access to these registers requires special | ||
| 38 | /// handling via the REX prefix, via the B or R bits, depending on context. | ||
| 39 | pub fn isExtended(self: @This()) bool { | ||
| 40 | return @enumToInt(self) & 0x08 != 0; | ||
| 41 | } | ||
| 42 | |||
| 43 | /// This returns the 4-bit register ID, which is used in practically every | ||
| 44 | /// opcode. Note that bit 3 (the highest bit) is *never* used directly in | ||
| 45 | /// an instruction (@see isExtended), and requires special handling. The | ||
| 46 | /// lower three bits are often embedded directly in instructions (such as | ||
| 47 | /// the B8 variant of moves), or used in R/M bytes. | ||
| 48 | pub fn id(self: @This()) u4 { | ||
| 49 | return @truncate(u4, @enumToInt(self)); | ||
| 50 | } | ||
| 51 | }; | ||
| 52 | |||
| 53 | // zig fmt: on | ||
src-self-hosted/compilation.zig deleted-1434| ... | @@ -1,1434 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const io = std.io; | ||
| 3 | const mem = std.mem; | ||
| 4 | const Allocator = mem.Allocator; | ||
| 5 | const ArrayListSentineled = std.ArrayListSentineled; | ||
| 6 | const llvm = @import("llvm.zig"); | ||
| 7 | const c = @import("c.zig"); | ||
| 8 | const builtin = std.builtin; | ||
| 9 | const Target = std.Target; | ||
| 10 | const warn = std.debug.warn; | ||
| 11 | const Token = std.zig.Token; | ||
| 12 | const ArrayList = std.ArrayList; | ||
| 13 | const errmsg = @import("errmsg.zig"); | ||
| 14 | const ast = std.zig.ast; | ||
| 15 | const event = std.event; | ||
| 16 | const assert = std.debug.assert; | ||
| 17 | const AtomicRmwOp = builtin.AtomicRmwOp; | ||
| 18 | const AtomicOrder = builtin.AtomicOrder; | ||
| 19 | const Scope = @import("scope.zig").Scope; | ||
| 20 | const Decl = @import("decl.zig").Decl; | ||
| 21 | const ir = @import("ir.zig"); | ||
| 22 | const Visib = @import("visib.zig").Visib; | ||
| 23 | const Value = @import("value.zig").Value; | ||
| 24 | const Type = Value.Type; | ||
| 25 | const Span = errmsg.Span; | ||
| 26 | const Msg = errmsg.Msg; | ||
| 27 | const codegen = @import("codegen.zig"); | ||
| 28 | const Package = @import("package.zig").Package; | ||
| 29 | const link = @import("link.zig").link; | ||
| 30 | const LibCInstallation = @import("libc_installation.zig").LibCInstallation; | ||
| 31 | const CInt = @import("c_int.zig").CInt; | ||
| 32 | const fs = std.fs; | ||
| 33 | const util = @import("util.zig"); | ||
| 34 | |||
| 35 | const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB | ||
| 36 | |||
| 37 | /// Data that is local to the event loop. | ||
| 38 | pub const ZigCompiler = struct { | ||
| 39 | llvm_handle_pool: std.atomic.Stack(*llvm.Context), | ||
| 40 | lld_lock: event.Lock, | ||
| 41 | allocator: *Allocator, | ||
| 42 | |||
| 43 | /// TODO pool these so that it doesn't have to lock | ||
| 44 | prng: event.Locked(std.rand.DefaultPrng), | ||
| 45 | |||
| 46 | native_libc: event.Future(LibCInstallation), | ||
| 47 | |||
| 48 | var lazy_init_targets = std.once(util.initializeAllTargets); | ||
| 49 | |||
| 50 | pub fn init(allocator: *Allocator) !ZigCompiler { | ||
| 51 | lazy_init_targets.call(); | ||
| 52 | |||
| 53 | var seed_bytes: [@sizeOf(u64)]u8 = undefined; | ||
| 54 | try std.crypto.randomBytes(seed_bytes[0..]); | ||
| 55 | const seed = mem.readIntNative(u64, &seed_bytes); | ||
| 56 | |||
| 57 | return ZigCompiler{ | ||
| 58 | .allocator = allocator, | ||
| 59 | .lld_lock = event.Lock.init(), | ||
| 60 | .llvm_handle_pool = std.atomic.Stack(*llvm.Context).init(), | ||
| 61 | .prng = event.Locked(std.rand.DefaultPrng).init(std.rand.DefaultPrng.init(seed)), | ||
| 62 | .native_libc = event.Future(LibCInstallation).init(), | ||
| 63 | }; | ||
| 64 | } | ||
| 65 | |||
| 66 | /// Must be called only after EventLoop.run completes. | ||
| 67 | fn deinit(self: *ZigCompiler) void { | ||
| 68 | self.lld_lock.deinit(); | ||
| 69 | while (self.llvm_handle_pool.pop()) |node| { | ||
| 70 | llvm.ContextDispose(node.data); | ||
| 71 | self.allocator.destroy(node); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | /// Gets an exclusive handle on any LlvmContext. | ||
| 76 | /// Caller must release the handle when done. | ||
| 77 | pub fn getAnyLlvmContext(self: *ZigCompiler) !LlvmHandle { | ||
| 78 | if (self.llvm_handle_pool.pop()) |node| return LlvmHandle{ .node = node }; | ||
| 79 | |||
| 80 | const context_ref = llvm.ContextCreate() orelse return error.OutOfMemory; | ||
| 81 | errdefer llvm.ContextDispose(context_ref); | ||
| 82 | |||
| 83 | const node = try self.allocator.create(std.atomic.Stack(*llvm.Context).Node); | ||
| 84 | node.* = std.atomic.Stack(*llvm.Context).Node{ | ||
| 85 | .next = undefined, | ||
| 86 | .data = context_ref, | ||
| 87 | }; | ||
| 88 | errdefer self.allocator.destroy(node); | ||
| 89 | |||
| 90 | return LlvmHandle{ .node = node }; | ||
| 91 | } | ||
| 92 | |||
| 93 | pub fn getNativeLibC(self: *ZigCompiler) !*LibCInstallation { | ||
| 94 | if (self.native_libc.start()) |ptr| return ptr; | ||
| 95 | self.native_libc.data = try LibCInstallation.findNative(.{ .allocator = self.allocator }); | ||
| 96 | self.native_libc.resolve(); | ||
| 97 | return &self.native_libc.data; | ||
| 98 | } | ||
| 99 | |||
| 100 | /// Must be called only once, ever. Sets global state. | ||
| 101 | pub fn setLlvmArgv(allocator: *Allocator, llvm_argv: []const []const u8) !void { | ||
| 102 | if (llvm_argv.len != 0) { | ||
| 103 | var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(allocator, &[_][]const []const u8{ | ||
| 104 | &[_][]const u8{"zig (LLVM option parsing)"}, | ||
| 105 | llvm_argv, | ||
| 106 | }); | ||
| 107 | defer c_compatible_args.deinit(); | ||
| 108 | c.ZigLLVMParseCommandLineOptions(llvm_argv.len + 1, c_compatible_args.ptr); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | }; | ||
| 112 | |||
| 113 | pub const LlvmHandle = struct { | ||
| 114 | node: *std.atomic.Stack(*llvm.Context).Node, | ||
| 115 | |||
| 116 | pub fn release(self: LlvmHandle, zig_compiler: *ZigCompiler) void { | ||
| 117 | zig_compiler.llvm_handle_pool.push(self.node); | ||
| 118 | } | ||
| 119 | }; | ||
| 120 | |||
| 121 | pub const Compilation = struct { | ||
| 122 | zig_compiler: *ZigCompiler, | ||
| 123 | name: ArrayListSentineled(u8, 0), | ||
| 124 | llvm_triple: ArrayListSentineled(u8, 0), | ||
| 125 | root_src_path: ?[]const u8, | ||
| 126 | target: std.Target, | ||
| 127 | llvm_target: *llvm.Target, | ||
| 128 | build_mode: builtin.Mode, | ||
| 129 | zig_lib_dir: []const u8, | ||
| 130 | zig_std_dir: []const u8, | ||
| 131 | |||
| 132 | /// lazily created when we need it | ||
| 133 | tmp_dir: event.Future(BuildError![]u8) = event.Future(BuildError![]u8).init(), | ||
| 134 | |||
| 135 | version: builtin.Version = builtin.Version{ .major = 0, .minor = 0, .patch = 0 }, | ||
| 136 | |||
| 137 | linker_script: ?[]const u8 = null, | ||
| 138 | out_h_path: ?[]const u8 = null, | ||
| 139 | |||
| 140 | is_test: bool = false, | ||
| 141 | strip: bool = false, | ||
| 142 | is_static: bool, | ||
| 143 | linker_rdynamic: bool = false, | ||
| 144 | |||
| 145 | clang_argv: []const []const u8 = &[_][]const u8{}, | ||
| 146 | assembly_files: []const []const u8 = &[_][]const u8{}, | ||
| 147 | |||
| 148 | /// paths that are explicitly provided by the user to link against | ||
| 149 | link_objects: []const []const u8 = &[_][]const u8{}, | ||
| 150 | |||
| 151 | /// functions that have their own objects that we need to link | ||
| 152 | /// it uses an optional pointer so that tombstone removals are possible | ||
| 153 | fn_link_set: event.Locked(FnLinkSet) = event.Locked(FnLinkSet).init(FnLinkSet.init()), | ||
| 154 | |||
| 155 | pub const FnLinkSet = std.TailQueue(?*Value.Fn); | ||
| 156 | |||
| 157 | link_libs_list: ArrayList(*LinkLib), | ||
| 158 | libc_link_lib: ?*LinkLib = null, | ||
| 159 | |||
| 160 | err_color: errmsg.Color = .Auto, | ||
| 161 | |||
| 162 | verbose_tokenize: bool = false, | ||
| 163 | verbose_ast_tree: bool = false, | ||
| 164 | verbose_ast_fmt: bool = false, | ||
| 165 | verbose_cimport: bool = false, | ||
| 166 | verbose_ir: bool = false, | ||
| 167 | verbose_llvm_ir: bool = false, | ||
| 168 | verbose_link: bool = false, | ||
| 169 | |||
| 170 | link_eh_frame_hdr: bool = false, | ||
| 171 | |||
| 172 | darwin_version_min: DarwinVersionMin = .None, | ||
| 173 | |||
| 174 | test_filters: []const []const u8 = &[_][]const u8{}, | ||
| 175 | test_name_prefix: ?[]const u8 = null, | ||
| 176 | |||
| 177 | emit_bin: bool = true, | ||
| 178 | emit_asm: bool = false, | ||
| 179 | emit_llvm_ir: bool = false, | ||
| 180 | emit_h: bool = false, | ||
| 181 | |||
| 182 | kind: Kind, | ||
| 183 | |||
| 184 | events: *event.Channel(Event), | ||
| 185 | |||
| 186 | exported_symbol_names: event.Locked(Decl.Table), | ||
| 187 | |||
| 188 | /// Before code generation starts, must wait on this group to make sure | ||
| 189 | /// the build is complete. | ||
| 190 | prelink_group: event.Group(BuildError!void), | ||
| 191 | |||
| 192 | compile_errors: event.Locked(CompileErrList), | ||
| 193 | |||
| 194 | meta_type: *Type.MetaType, | ||
| 195 | void_type: *Type.Void, | ||
| 196 | bool_type: *Type.Bool, | ||
| 197 | noreturn_type: *Type.NoReturn, | ||
| 198 | comptime_int_type: *Type.ComptimeInt, | ||
| 199 | u8_type: *Type.Int, | ||
| 200 | |||
| 201 | void_value: *Value.Void, | ||
| 202 | true_value: *Value.Bool, | ||
| 203 | false_value: *Value.Bool, | ||
| 204 | noreturn_value: *Value.NoReturn, | ||
| 205 | |||
| 206 | target_machine: *llvm.TargetMachine, | ||
| 207 | target_data_ref: *llvm.TargetData, | ||
| 208 | target_layout_str: [*:0]u8, | ||
| 209 | target_ptr_bits: u32, | ||
| 210 | |||
| 211 | /// for allocating things which have the same lifetime as this Compilation | ||
| 212 | arena_allocator: std.heap.ArenaAllocator, | ||
| 213 | |||
| 214 | root_package: *Package, | ||
| 215 | std_package: *Package, | ||
| 216 | |||
| 217 | override_libc: ?*LibCInstallation = null, | ||
| 218 | |||
| 219 | /// need to wait on this group before deinitializing | ||
| 220 | deinit_group: event.Group(void), | ||
| 221 | |||
| 222 | destroy_frame: *@Frame(createAsync), | ||
| 223 | main_loop_frame: *@Frame(Compilation.mainLoop), | ||
| 224 | main_loop_future: event.Future(void) = event.Future(void).init(), | ||
| 225 | |||
| 226 | have_err_ret_tracing: bool = false, | ||
| 227 | |||
| 228 | /// not locked because it is read-only | ||
| 229 | primitive_type_table: TypeTable, | ||
| 230 | |||
| 231 | int_type_table: event.Locked(IntTypeTable), | ||
| 232 | array_type_table: event.Locked(ArrayTypeTable), | ||
| 233 | ptr_type_table: event.Locked(PtrTypeTable), | ||
| 234 | fn_type_table: event.Locked(FnTypeTable), | ||
| 235 | |||
| 236 | c_int_types: [CInt.list.len]*Type.Int, | ||
| 237 | |||
| 238 | fs_watch: *fs.Watch(*Scope.Root), | ||
| 239 | |||
| 240 | cancelled: bool = false, | ||
| 241 | |||
| 242 | const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql); | ||
| 243 | const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql); | ||
| 244 | const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql); | ||
| 245 | const FnTypeTable = std.HashMap(*const Type.Fn.Key, *Type.Fn, Type.Fn.Key.hash, Type.Fn.Key.eql); | ||
| 246 | const TypeTable = std.StringHashMap(*Type); | ||
| 247 | |||
| 248 | const CompileErrList = std.ArrayList(*Msg); | ||
| 249 | |||
| 250 | // TODO handle some of these earlier and report them in a way other than error codes | ||
| 251 | pub const BuildError = error{ | ||
| 252 | OutOfMemory, | ||
| 253 | EndOfStream, | ||
| 254 | IsDir, | ||
| 255 | Unexpected, | ||
| 256 | SystemResources, | ||
| 257 | SharingViolation, | ||
| 258 | PathAlreadyExists, | ||
| 259 | FileNotFound, | ||
| 260 | AccessDenied, | ||
| 261 | PipeBusy, | ||
| 262 | FileTooBig, | ||
| 263 | SymLinkLoop, | ||
| 264 | ProcessFdQuotaExceeded, | ||
| 265 | NameTooLong, | ||
| 266 | SystemFdQuotaExceeded, | ||
| 267 | NoDevice, | ||
| 268 | NoSpaceLeft, | ||
| 269 | NotDir, | ||
| 270 | FileSystem, | ||
| 271 | OperationAborted, | ||
| 272 | IoPending, | ||
| 273 | BrokenPipe, | ||
| 274 | WouldBlock, | ||
| 275 | FileClosed, | ||
| 276 | DestinationAddressRequired, | ||
| 277 | DiskQuota, | ||
| 278 | InputOutput, | ||
| 279 | NoStdHandles, | ||
| 280 | Overflow, | ||
| 281 | NotSupported, | ||
| 282 | BufferTooSmall, | ||
| 283 | Unimplemented, // TODO remove this one | ||
| 284 | SemanticAnalysisFailed, // TODO remove this one | ||
| 285 | ReadOnlyFileSystem, | ||
| 286 | LinkQuotaExceeded, | ||
| 287 | EnvironmentVariableNotFound, | ||
| 288 | AppDataDirUnavailable, | ||
| 289 | LinkFailed, | ||
| 290 | LibCRequiredButNotProvidedOrFound, | ||
| 291 | LibCMissingDynamicLinker, | ||
| 292 | InvalidDarwinVersionString, | ||
| 293 | UnsupportedLinkArchitecture, | ||
| 294 | UserResourceLimitReached, | ||
| 295 | InvalidUtf8, | ||
| 296 | BadPathName, | ||
| 297 | DeviceBusy, | ||
| 298 | CurrentWorkingDirectoryUnlinked, | ||
| 299 | }; | ||
| 300 | |||
| 301 | pub const Event = union(enum) { | ||
| 302 | Ok, | ||
| 303 | Error: BuildError, | ||
| 304 | Fail: []*Msg, | ||
| 305 | }; | ||
| 306 | |||
| 307 | pub const DarwinVersionMin = union(enum) { | ||
| 308 | None, | ||
| 309 | MacOS: []const u8, | ||
| 310 | Ios: []const u8, | ||
| 311 | }; | ||
| 312 | |||
| 313 | pub const Kind = enum { | ||
| 314 | Exe, | ||
| 315 | Lib, | ||
| 316 | Obj, | ||
| 317 | }; | ||
| 318 | |||
| 319 | pub const LinkLib = struct { | ||
| 320 | name: []const u8, | ||
| 321 | path: ?[]const u8, | ||
| 322 | |||
| 323 | /// the list of symbols we depend on from this lib | ||
| 324 | symbols: ArrayList([]u8), | ||
| 325 | provided_explicitly: bool, | ||
| 326 | }; | ||
| 327 | |||
| 328 | pub const Emit = enum { | ||
| 329 | Binary, | ||
| 330 | Assembly, | ||
| 331 | LlvmIr, | ||
| 332 | }; | ||
| 333 | |||
| 334 | pub fn create( | ||
| 335 | zig_compiler: *ZigCompiler, | ||
| 336 | name: []const u8, | ||
| 337 | root_src_path: ?[]const u8, | ||
| 338 | target: std.zig.CrossTarget, | ||
| 339 | kind: Kind, | ||
| 340 | build_mode: builtin.Mode, | ||
| 341 | is_static: bool, | ||
| 342 | zig_lib_dir: []const u8, | ||
| 343 | ) !*Compilation { | ||
| 344 | var optional_comp: ?*Compilation = null; | ||
| 345 | var frame = try zig_compiler.allocator.create(@Frame(createAsync)); | ||
| 346 | errdefer zig_compiler.allocator.destroy(frame); | ||
| 347 | frame.* = async createAsync( | ||
| 348 | &optional_comp, | ||
| 349 | zig_compiler, | ||
| 350 | name, | ||
| 351 | root_src_path, | ||
| 352 | target, | ||
| 353 | kind, | ||
| 354 | build_mode, | ||
| 355 | is_static, | ||
| 356 | zig_lib_dir, | ||
| 357 | ); | ||
| 358 | // TODO causes segfault | ||
| 359 | // return optional_comp orelse if (await frame) |_| unreachable else |err| err; | ||
| 360 | if (optional_comp) |comp| { | ||
| 361 | return comp; | ||
| 362 | } else if (await frame) |_| unreachable else |err| return err; | ||
| 363 | } | ||
| 364 | |||
| 365 | async fn createAsync( | ||
| 366 | out_comp: *?*Compilation, | ||
| 367 | zig_compiler: *ZigCompiler, | ||
| 368 | name: []const u8, | ||
| 369 | root_src_path: ?[]const u8, | ||
| 370 | cross_target: std.zig.CrossTarget, | ||
| 371 | kind: Kind, | ||
| 372 | build_mode: builtin.Mode, | ||
| 373 | is_static: bool, | ||
| 374 | zig_lib_dir: []const u8, | ||
| 375 | ) !void { | ||
| 376 | const allocator = zig_compiler.allocator; | ||
| 377 | |||
| 378 | // TODO merge this line with stage2.zig crossTargetToTarget | ||
| 379 | const target_info = try std.zig.system.NativeTargetInfo.detect(std.heap.c_allocator, cross_target); | ||
| 380 | const target = target_info.target; | ||
| 381 | |||
| 382 | var comp = Compilation{ | ||
| 383 | .arena_allocator = std.heap.ArenaAllocator.init(allocator), | ||
| 384 | .zig_compiler = zig_compiler, | ||
| 385 | .events = undefined, | ||
| 386 | .root_src_path = root_src_path, | ||
| 387 | .target = target, | ||
| 388 | .llvm_target = undefined, | ||
| 389 | .kind = kind, | ||
| 390 | .build_mode = build_mode, | ||
| 391 | .zig_lib_dir = zig_lib_dir, | ||
| 392 | .zig_std_dir = undefined, | ||
| 393 | .destroy_frame = @frame(), | ||
| 394 | .main_loop_frame = undefined, | ||
| 395 | |||
| 396 | .name = undefined, | ||
| 397 | .llvm_triple = undefined, | ||
| 398 | .is_static = is_static, | ||
| 399 | .link_libs_list = undefined, | ||
| 400 | .exported_symbol_names = event.Locked(Decl.Table).init(Decl.Table.init(allocator)), | ||
| 401 | .prelink_group = event.Group(BuildError!void).init(allocator), | ||
| 402 | .deinit_group = event.Group(void).init(allocator), | ||
| 403 | .compile_errors = event.Locked(CompileErrList).init(CompileErrList.init(allocator)), | ||
| 404 | .int_type_table = event.Locked(IntTypeTable).init(IntTypeTable.init(allocator)), | ||
| 405 | .array_type_table = event.Locked(ArrayTypeTable).init(ArrayTypeTable.init(allocator)), | ||
| 406 | .ptr_type_table = event.Locked(PtrTypeTable).init(PtrTypeTable.init(allocator)), | ||
| 407 | .fn_type_table = event.Locked(FnTypeTable).init(FnTypeTable.init(allocator)), | ||
| 408 | .c_int_types = undefined, | ||
| 409 | |||
| 410 | .meta_type = undefined, | ||
| 411 | .void_type = undefined, | ||
| 412 | .void_value = undefined, | ||
| 413 | .bool_type = undefined, | ||
| 414 | .true_value = undefined, | ||
| 415 | .false_value = undefined, | ||
| 416 | .noreturn_type = undefined, | ||
| 417 | .noreturn_value = undefined, | ||
| 418 | .comptime_int_type = undefined, | ||
| 419 | .u8_type = undefined, | ||
| 420 | |||
| 421 | .target_machine = undefined, | ||
| 422 | .target_data_ref = undefined, | ||
| 423 | .target_layout_str = undefined, | ||
| 424 | .target_ptr_bits = target.cpu.arch.ptrBitWidth(), | ||
| 425 | |||
| 426 | .root_package = undefined, | ||
| 427 | .std_package = undefined, | ||
| 428 | |||
| 429 | .primitive_type_table = undefined, | ||
| 430 | |||
| 431 | .fs_watch = undefined, | ||
| 432 | }; | ||
| 433 | comp.link_libs_list = ArrayList(*LinkLib).init(comp.arena()); | ||
| 434 | comp.primitive_type_table = TypeTable.init(comp.arena()); | ||
| 435 | |||
| 436 | defer { | ||
| 437 | comp.int_type_table.private_data.deinit(); | ||
| 438 | comp.array_type_table.private_data.deinit(); | ||
| 439 | comp.ptr_type_table.private_data.deinit(); | ||
| 440 | comp.fn_type_table.private_data.deinit(); | ||
| 441 | comp.arena_allocator.deinit(); | ||
| 442 | } | ||
| 443 | |||
| 444 | comp.name = try ArrayListSentineled(u8, 0).init(comp.arena(), name); | ||
| 445 | comp.llvm_triple = try util.getLLVMTriple(comp.arena(), target); | ||
| 446 | comp.llvm_target = try util.llvmTargetFromTriple(comp.llvm_triple); | ||
| 447 | comp.zig_std_dir = try fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" }); | ||
| 448 | |||
| 449 | const opt_level = switch (build_mode) { | ||
| 450 | .Debug => llvm.CodeGenLevelNone, | ||
| 451 | else => llvm.CodeGenLevelAggressive, | ||
| 452 | }; | ||
| 453 | |||
| 454 | const reloc_mode = if (is_static) llvm.RelocStatic else llvm.RelocPIC; | ||
| 455 | |||
| 456 | var target_specific_cpu_args: ?[*:0]u8 = null; | ||
| 457 | var target_specific_cpu_features: ?[*:0]u8 = null; | ||
| 458 | defer llvm.DisposeMessage(target_specific_cpu_args); | ||
| 459 | defer llvm.DisposeMessage(target_specific_cpu_features); | ||
| 460 | |||
| 461 | // TODO detect native CPU & features here | ||
| 462 | |||
| 463 | comp.target_machine = llvm.CreateTargetMachine( | ||
| 464 | comp.llvm_target, | ||
| 465 | comp.llvm_triple.span(), | ||
| 466 | target_specific_cpu_args orelse "", | ||
| 467 | target_specific_cpu_features orelse "", | ||
| 468 | opt_level, | ||
| 469 | reloc_mode, | ||
| 470 | llvm.CodeModelDefault, | ||
| 471 | false, // TODO: add -ffunction-sections option | ||
| 472 | ) orelse return error.OutOfMemory; | ||
| 473 | defer llvm.DisposeTargetMachine(comp.target_machine); | ||
| 474 | |||
| 475 | comp.target_data_ref = llvm.CreateTargetDataLayout(comp.target_machine) orelse return error.OutOfMemory; | ||
| 476 | defer llvm.DisposeTargetData(comp.target_data_ref); | ||
| 477 | |||
| 478 | comp.target_layout_str = llvm.CopyStringRepOfTargetData(comp.target_data_ref) orelse return error.OutOfMemory; | ||
| 479 | defer llvm.DisposeMessage(comp.target_layout_str); | ||
| 480 | |||
| 481 | comp.events = try allocator.create(event.Channel(Event)); | ||
| 482 | defer allocator.destroy(comp.events); | ||
| 483 | |||
| 484 | comp.events.init(&[0]Event{}); | ||
| 485 | defer comp.events.deinit(); | ||
| 486 | |||
| 487 | if (root_src_path) |root_src| { | ||
| 488 | const dirname = fs.path.dirname(root_src) orelse "."; | ||
| 489 | const basename = fs.path.basename(root_src); | ||
| 490 | |||
| 491 | comp.root_package = try Package.create(comp.arena(), dirname, basename); | ||
| 492 | comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig"); | ||
| 493 | try comp.root_package.add("std", comp.std_package); | ||
| 494 | } else { | ||
| 495 | comp.root_package = try Package.create(comp.arena(), ".", ""); | ||
| 496 | } | ||
| 497 | |||
| 498 | comp.fs_watch = try fs.Watch(*Scope.Root).init(allocator, 16); | ||
| 499 | defer comp.fs_watch.deinit(); | ||
| 500 | |||
| 501 | try comp.initTypes(); | ||
| 502 | defer comp.primitive_type_table.deinit(); | ||
| 503 | |||
| 504 | comp.main_loop_frame = try allocator.create(@Frame(mainLoop)); | ||
| 505 | defer allocator.destroy(comp.main_loop_frame); | ||
| 506 | |||
| 507 | comp.main_loop_frame.* = async comp.mainLoop(); | ||
| 508 | // Set this to indicate that initialization completed successfully. | ||
| 509 | // from here on out we must not return an error. | ||
| 510 | // This must occur before the first suspend/await. | ||
| 511 | out_comp.* = &comp; | ||
| 512 | // This suspend is resumed by destroy() | ||
| 513 | suspend; | ||
| 514 | // From here on is cleanup. | ||
| 515 | |||
| 516 | comp.deinit_group.wait(); | ||
| 517 | |||
| 518 | if (comp.tmp_dir.getOrNull()) |tmp_dir_result| | ||
| 519 | if (tmp_dir_result.*) |tmp_dir| { | ||
| 520 | fs.cwd().deleteTree(tmp_dir) catch {}; | ||
| 521 | } else |_| {}; | ||
| 522 | } | ||
| 523 | |||
| 524 | /// it does ref the result because it could be an arbitrary integer size | ||
| 525 | pub fn getPrimitiveType(comp: *Compilation, name: []const u8) !?*Type { | ||
| 526 | if (name.len >= 2) { | ||
| 527 | switch (name[0]) { | ||
| 528 | 'i', 'u' => blk: { | ||
| 529 | for (name[1..]) |byte| | ||
| 530 | switch (byte) { | ||
| 531 | '0'...'9' => {}, | ||
| 532 | else => break :blk, | ||
| 533 | }; | ||
| 534 | const is_signed = name[0] == 'i'; | ||
| 535 | const bit_count = std.fmt.parseUnsigned(u32, name[1..], 10) catch |err| switch (err) { | ||
| 536 | error.Overflow => return error.Overflow, | ||
| 537 | error.InvalidCharacter => unreachable, // we just checked the characters above | ||
| 538 | }; | ||
| 539 | const int_type = try Type.Int.get(comp, Type.Int.Key{ | ||
| 540 | .bit_count = bit_count, | ||
| 541 | .is_signed = is_signed, | ||
| 542 | }); | ||
| 543 | errdefer int_type.base.base.deref(); | ||
| 544 | return &int_type.base; | ||
| 545 | }, | ||
| 546 | else => {}, | ||
| 547 | } | ||
| 548 | } | ||
| 549 | |||
| 550 | if (comp.primitive_type_table.get(name)) |entry| { | ||
| 551 | entry.value.base.ref(); | ||
| 552 | return entry.value; | ||
| 553 | } | ||
| 554 | |||
| 555 | return null; | ||
| 556 | } | ||
| 557 | |||
| 558 | fn initTypes(comp: *Compilation) !void { | ||
| 559 | comp.meta_type = try comp.arena().create(Type.MetaType); | ||
| 560 | comp.meta_type.* = Type.MetaType{ | ||
| 561 | .base = Type{ | ||
| 562 | .name = "type", | ||
| 563 | .base = Value{ | ||
| 564 | .id = .Type, | ||
| 565 | .typ = undefined, | ||
| 566 | .ref_count = std.atomic.Int(usize).init(3), // 3 because it references itself twice | ||
| 567 | }, | ||
| 568 | .id = .Type, | ||
| 569 | .abi_alignment = Type.AbiAlignment.init(), | ||
| 570 | }, | ||
| 571 | .value = undefined, | ||
| 572 | }; | ||
| 573 | comp.meta_type.value = &comp.meta_type.base; | ||
| 574 | comp.meta_type.base.base.typ = &comp.meta_type.base; | ||
| 575 | assert((try comp.primitive_type_table.put(comp.meta_type.base.name, &comp.meta_type.base)) == null); | ||
| 576 | |||
| 577 | comp.void_type = try comp.arena().create(Type.Void); | ||
| 578 | comp.void_type.* = Type.Void{ | ||
| 579 | .base = Type{ | ||
| 580 | .name = "void", | ||
| 581 | .base = Value{ | ||
| 582 | .id = .Type, | ||
| 583 | .typ = &Type.MetaType.get(comp).base, | ||
| 584 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 585 | }, | ||
| 586 | .id = .Void, | ||
| 587 | .abi_alignment = Type.AbiAlignment.init(), | ||
| 588 | }, | ||
| 589 | }; | ||
| 590 | assert((try comp.primitive_type_table.put(comp.void_type.base.name, &comp.void_type.base)) == null); | ||
| 591 | |||
| 592 | comp.noreturn_type = try comp.arena().create(Type.NoReturn); | ||
| 593 | comp.noreturn_type.* = Type.NoReturn{ | ||
| 594 | .base = Type{ | ||
| 595 | .name = "noreturn", | ||
| 596 | .base = Value{ | ||
| 597 | .id = .Type, | ||
| 598 | .typ = &Type.MetaType.get(comp).base, | ||
| 599 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 600 | }, | ||
| 601 | .id = .NoReturn, | ||
| 602 | .abi_alignment = Type.AbiAlignment.init(), | ||
| 603 | }, | ||
| 604 | }; | ||
| 605 | assert((try comp.primitive_type_table.put(comp.noreturn_type.base.name, &comp.noreturn_type.base)) == null); | ||
| 606 | |||
| 607 | comp.comptime_int_type = try comp.arena().create(Type.ComptimeInt); | ||
| 608 | comp.comptime_int_type.* = Type.ComptimeInt{ | ||
| 609 | .base = Type{ | ||
| 610 | .name = "comptime_int", | ||
| 611 | .base = Value{ | ||
| 612 | .id = .Type, | ||
| 613 | .typ = &Type.MetaType.get(comp).base, | ||
| 614 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 615 | }, | ||
| 616 | .id = .ComptimeInt, | ||
| 617 | .abi_alignment = Type.AbiAlignment.init(), | ||
| 618 | }, | ||
| 619 | }; | ||
| 620 | assert((try comp.primitive_type_table.put(comp.comptime_int_type.base.name, &comp.comptime_int_type.base)) == null); | ||
| 621 | |||
| 622 | comp.bool_type = try comp.arena().create(Type.Bool); | ||
| 623 | comp.bool_type.* = Type.Bool{ | ||
| 624 | .base = Type{ | ||
| 625 | .name = "bool", | ||
| 626 | .base = Value{ | ||
| 627 | .id = .Type, | ||
| 628 | .typ = &Type.MetaType.get(comp).base, | ||
| 629 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 630 | }, | ||
| 631 | .id = .Bool, | ||
| 632 | .abi_alignment = Type.AbiAlignment.init(), | ||
| 633 | }, | ||
| 634 | }; | ||
| 635 | assert((try comp.primitive_type_table.put(comp.bool_type.base.name, &comp.bool_type.base)) == null); | ||
| 636 | |||
| 637 | comp.void_value = try comp.arena().create(Value.Void); | ||
| 638 | comp.void_value.* = Value.Void{ | ||
| 639 | .base = Value{ | ||
| 640 | .id = .Void, | ||
| 641 | .typ = &Type.Void.get(comp).base, | ||
| 642 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 643 | }, | ||
| 644 | }; | ||
| 645 | |||
| 646 | comp.true_value = try comp.arena().create(Value.Bool); | ||
| 647 | comp.true_value.* = Value.Bool{ | ||
| 648 | .base = Value{ | ||
| 649 | .id = .Bool, | ||
| 650 | .typ = &Type.Bool.get(comp).base, | ||
| 651 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 652 | }, | ||
| 653 | .x = true, | ||
| 654 | }; | ||
| 655 | |||
| 656 | comp.false_value = try comp.arena().create(Value.Bool); | ||
| 657 | comp.false_value.* = Value.Bool{ | ||
| 658 | .base = Value{ | ||
| 659 | .id = .Bool, | ||
| 660 | .typ = &Type.Bool.get(comp).base, | ||
| 661 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 662 | }, | ||
| 663 | .x = false, | ||
| 664 | }; | ||
| 665 | |||
| 666 | comp.noreturn_value = try comp.arena().create(Value.NoReturn); | ||
| 667 | comp.noreturn_value.* = Value.NoReturn{ | ||
| 668 | .base = Value{ | ||
| 669 | .id = .NoReturn, | ||
| 670 | .typ = &Type.NoReturn.get(comp).base, | ||
| 671 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 672 | }, | ||
| 673 | }; | ||
| 674 | |||
| 675 | for (CInt.list) |cint, i| { | ||
| 676 | const c_int_type = try comp.arena().create(Type.Int); | ||
| 677 | c_int_type.* = Type.Int{ | ||
| 678 | .base = Type{ | ||
| 679 | .name = cint.zig_name, | ||
| 680 | .base = Value{ | ||
| 681 | .id = .Type, | ||
| 682 | .typ = &Type.MetaType.get(comp).base, | ||
| 683 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 684 | }, | ||
| 685 | .id = .Int, | ||
| 686 | .abi_alignment = Type.AbiAlignment.init(), | ||
| 687 | }, | ||
| 688 | .key = Type.Int.Key{ | ||
| 689 | .is_signed = cint.is_signed, | ||
| 690 | .bit_count = cint.sizeInBits(comp.target), | ||
| 691 | }, | ||
| 692 | .garbage_node = undefined, | ||
| 693 | }; | ||
| 694 | comp.c_int_types[i] = c_int_type; | ||
| 695 | assert((try comp.primitive_type_table.put(cint.zig_name, &c_int_type.base)) == null); | ||
| 696 | } | ||
| 697 | comp.u8_type = try comp.arena().create(Type.Int); | ||
| 698 | comp.u8_type.* = Type.Int{ | ||
| 699 | .base = Type{ | ||
| 700 | .name = "u8", | ||
| 701 | .base = Value{ | ||
| 702 | .id = .Type, | ||
| 703 | .typ = &Type.MetaType.get(comp).base, | ||
| 704 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 705 | }, | ||
| 706 | .id = .Int, | ||
| 707 | .abi_alignment = Type.AbiAlignment.init(), | ||
| 708 | }, | ||
| 709 | .key = Type.Int.Key{ | ||
| 710 | .is_signed = false, | ||
| 711 | .bit_count = 8, | ||
| 712 | }, | ||
| 713 | .garbage_node = undefined, | ||
| 714 | }; | ||
| 715 | assert((try comp.primitive_type_table.put(comp.u8_type.base.name, &comp.u8_type.base)) == null); | ||
| 716 | } | ||
| 717 | |||
| 718 | pub fn destroy(self: *Compilation) void { | ||
| 719 | const allocator = self.gpa(); | ||
| 720 | self.cancelled = true; | ||
| 721 | await self.main_loop_frame; | ||
| 722 | resume self.destroy_frame; | ||
| 723 | allocator.destroy(self.destroy_frame); | ||
| 724 | } | ||
| 725 | |||
| 726 | fn start(self: *Compilation) void { | ||
| 727 | self.main_loop_future.resolve(); | ||
| 728 | } | ||
| 729 | |||
| 730 | async fn mainLoop(self: *Compilation) void { | ||
| 731 | // wait until start() is called | ||
| 732 | _ = self.main_loop_future.get(); | ||
| 733 | |||
| 734 | var build_result = self.initialCompile(); | ||
| 735 | |||
| 736 | while (!self.cancelled) { | ||
| 737 | const link_result = if (build_result) blk: { | ||
| 738 | break :blk self.maybeLink(); | ||
| 739 | } else |err| err; | ||
| 740 | // this makes a handy error return trace and stack trace in debug mode | ||
| 741 | if (std.debug.runtime_safety) { | ||
| 742 | link_result catch unreachable; | ||
| 743 | } | ||
| 744 | |||
| 745 | const compile_errors = blk: { | ||
| 746 | const held = self.compile_errors.acquire(); | ||
| 747 | defer held.release(); | ||
| 748 | break :blk held.value.toOwnedSlice(); | ||
| 749 | }; | ||
| 750 | |||
| 751 | if (link_result) |_| { | ||
| 752 | if (compile_errors.len == 0) { | ||
| 753 | self.events.put(Event.Ok); | ||
| 754 | } else { | ||
| 755 | self.events.put(Event{ .Fail = compile_errors }); | ||
| 756 | } | ||
| 757 | } else |err| { | ||
| 758 | // if there's an error then the compile errors have dangling references | ||
| 759 | self.gpa().free(compile_errors); | ||
| 760 | |||
| 761 | self.events.put(Event{ .Error = err }); | ||
| 762 | } | ||
| 763 | |||
| 764 | // First, get an item from the watch channel, waiting on the channel. | ||
| 765 | var group = event.Group(BuildError!void).init(self.gpa()); | ||
| 766 | { | ||
| 767 | const ev = (self.fs_watch.channel.get()) catch |err| { | ||
| 768 | build_result = err; | ||
| 769 | continue; | ||
| 770 | }; | ||
| 771 | const root_scope = ev.data; | ||
| 772 | group.call(rebuildFile, .{ self, root_scope }) catch |err| { | ||
| 773 | build_result = err; | ||
| 774 | continue; | ||
| 775 | }; | ||
| 776 | } | ||
| 777 | // Next, get all the items from the channel that are buffered up. | ||
| 778 | while (self.fs_watch.channel.getOrNull()) |ev_or_err| { | ||
| 779 | if (ev_or_err) |ev| { | ||
| 780 | const root_scope = ev.data; | ||
| 781 | group.call(rebuildFile, .{ self, root_scope }) catch |err| { | ||
| 782 | build_result = err; | ||
| 783 | continue; | ||
| 784 | }; | ||
| 785 | } else |err| { | ||
| 786 | build_result = err; | ||
| 787 | continue; | ||
| 788 | } | ||
| 789 | } | ||
| 790 | build_result = group.wait(); | ||
| 791 | } | ||
| 792 | } | ||
| 793 | |||
| 794 | async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void { | ||
| 795 | const tree_scope = blk: { | ||
| 796 | const source_code = fs.cwd().readFileAlloc( | ||
| 797 | self.gpa(), | ||
| 798 | root_scope.realpath, | ||
| 799 | max_src_size, | ||
| 800 | ) catch |err| { | ||
| 801 | try self.addCompileErrorCli(root_scope.realpath, "unable to open: {}", .{@errorName(err)}); | ||
| 802 | return; | ||
| 803 | }; | ||
| 804 | errdefer self.gpa().free(source_code); | ||
| 805 | |||
| 806 | const tree = try std.zig.parse(self.gpa(), source_code); | ||
| 807 | errdefer { | ||
| 808 | tree.deinit(); | ||
| 809 | } | ||
| 810 | |||
| 811 | break :blk try Scope.AstTree.create(self, tree, root_scope); | ||
| 812 | }; | ||
| 813 | defer tree_scope.base.deref(self); | ||
| 814 | |||
| 815 | var error_it = tree_scope.tree.errors.iterator(0); | ||
| 816 | while (error_it.next()) |parse_error| { | ||
| 817 | const msg = try Msg.createFromParseErrorAndScope(self, tree_scope, parse_error); | ||
| 818 | errdefer msg.destroy(); | ||
| 819 | |||
| 820 | try self.addCompileErrorAsync(msg); | ||
| 821 | } | ||
| 822 | if (tree_scope.tree.errors.len != 0) { | ||
| 823 | return; | ||
| 824 | } | ||
| 825 | |||
| 826 | const locked_table = root_scope.decls.table.acquireWrite(); | ||
| 827 | defer locked_table.release(); | ||
| 828 | |||
| 829 | var decl_group = event.Group(BuildError!void).init(self.gpa()); | ||
| 830 | |||
| 831 | try self.rebuildChangedDecls( | ||
| 832 | &decl_group, | ||
| 833 | locked_table.value, | ||
| 834 | root_scope.decls, | ||
| 835 | &tree_scope.tree.root_node.decls, | ||
| 836 | tree_scope, | ||
| 837 | ); | ||
| 838 | |||
| 839 | try decl_group.wait(); | ||
| 840 | } | ||
| 841 | |||
| 842 | fn rebuildChangedDecls( | ||
| 843 | self: *Compilation, | ||
| 844 | group: *event.Group(BuildError!void), | ||
| 845 | locked_table: *Decl.Table, | ||
| 846 | decl_scope: *Scope.Decls, | ||
| 847 | ast_decls: *ast.Node.Root.DeclList, | ||
| 848 | tree_scope: *Scope.AstTree, | ||
| 849 | ) !void { | ||
| 850 | var existing_decls = try locked_table.clone(); | ||
| 851 | defer existing_decls.deinit(); | ||
| 852 | |||
| 853 | var ast_it = ast_decls.iterator(0); | ||
| 854 | while (ast_it.next()) |decl_ptr| { | ||
| 855 | const decl = decl_ptr.*; | ||
| 856 | switch (decl.id) { | ||
| 857 | .Comptime => { | ||
| 858 | const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl); | ||
| 859 | |||
| 860 | // TODO connect existing comptime decls to updated source files | ||
| 861 | |||
| 862 | try self.prelink_group.call(addCompTimeBlock, .{ self, tree_scope, &decl_scope.base, comptime_node }); | ||
| 863 | }, | ||
| 864 | .VarDecl => @panic("TODO"), | ||
| 865 | .FnProto => { | ||
| 866 | const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); | ||
| 867 | |||
| 868 | const name = if (fn_proto.name_token) |name_token| tree_scope.tree.tokenSlice(name_token) else { | ||
| 869 | try self.addCompileError(tree_scope, Span{ | ||
| 870 | .first = fn_proto.fn_token, | ||
| 871 | .last = fn_proto.fn_token + 1, | ||
| 872 | }, "missing function name", .{}); | ||
| 873 | continue; | ||
| 874 | }; | ||
| 875 | |||
| 876 | if (existing_decls.remove(name)) |entry| { | ||
| 877 | // compare new code to existing | ||
| 878 | if (entry.value.cast(Decl.Fn)) |existing_fn_decl| { | ||
| 879 | // Just compare the old bytes to the new bytes of the top level decl. | ||
| 880 | // Even if the AST is technically the same, we want error messages to display | ||
| 881 | // from the most recent source. | ||
| 882 | const old_decl_src = existing_fn_decl.base.tree_scope.tree.getNodeSource( | ||
| 883 | &existing_fn_decl.fn_proto.base, | ||
| 884 | ); | ||
| 885 | const new_decl_src = tree_scope.tree.getNodeSource(&fn_proto.base); | ||
| 886 | if (mem.eql(u8, old_decl_src, new_decl_src)) { | ||
| 887 | // it's the same, we can skip this decl | ||
| 888 | continue; | ||
| 889 | } else { | ||
| 890 | @panic("TODO decl changed implementation"); | ||
| 891 | // Add the new thing before dereferencing the old thing. This way we don't end | ||
| 892 | // up pointlessly re-creating things we end up using in the new thing. | ||
| 893 | } | ||
| 894 | } else { | ||
| 895 | @panic("TODO decl changed kind"); | ||
| 896 | } | ||
| 897 | } else { | ||
| 898 | // add new decl | ||
| 899 | const fn_decl = try self.gpa().create(Decl.Fn); | ||
| 900 | fn_decl.* = Decl.Fn{ | ||
| 901 | .base = Decl{ | ||
| 902 | .id = Decl.Id.Fn, | ||
| 903 | .name = name, | ||
| 904 | .visib = parseVisibToken(tree_scope.tree, fn_proto.visib_token), | ||
| 905 | .resolution = event.Future(BuildError!void).init(), | ||
| 906 | .parent_scope = &decl_scope.base, | ||
| 907 | .tree_scope = tree_scope, | ||
| 908 | }, | ||
| 909 | .value = .Unresolved, | ||
| 910 | .fn_proto = fn_proto, | ||
| 911 | }; | ||
| 912 | tree_scope.base.ref(); | ||
| 913 | errdefer self.gpa().destroy(fn_decl); | ||
| 914 | |||
| 915 | try group.call(addTopLevelDecl, .{ self, &fn_decl.base, locked_table }); | ||
| 916 | } | ||
| 917 | }, | ||
| 918 | .TestDecl => @panic("TODO"), | ||
| 919 | else => unreachable, | ||
| 920 | } | ||
| 921 | } | ||
| 922 | |||
| 923 | var existing_decl_it = existing_decls.iterator(); | ||
| 924 | while (existing_decl_it.next()) |entry| { | ||
| 925 | // this decl was deleted | ||
| 926 | const existing_decl = entry.value; | ||
| 927 | @panic("TODO handle decl deletion"); | ||
| 928 | } | ||
| 929 | } | ||
| 930 | |||
| 931 | fn initialCompile(self: *Compilation) !void { | ||
| 932 | if (self.root_src_path) |root_src_path| { | ||
| 933 | const root_scope = blk: { | ||
| 934 | // TODO async/await fs.realpath | ||
| 935 | const root_src_real_path = fs.realpathAlloc(self.gpa(), root_src_path) catch |err| { | ||
| 936 | try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)}); | ||
| 937 | return; | ||
| 938 | }; | ||
| 939 | errdefer self.gpa().free(root_src_real_path); | ||
| 940 | |||
| 941 | break :blk try Scope.Root.create(self, root_src_real_path); | ||
| 942 | }; | ||
| 943 | defer root_scope.base.deref(self); | ||
| 944 | |||
| 945 | // assert((try self.fs_watch.addFile(root_scope.realpath, root_scope)) == null); | ||
| 946 | try self.rebuildFile(root_scope); | ||
| 947 | } | ||
| 948 | } | ||
| 949 | |||
| 950 | fn maybeLink(self: *Compilation) !void { | ||
| 951 | (self.prelink_group.wait()) catch |err| switch (err) { | ||
| 952 | error.SemanticAnalysisFailed => {}, | ||
| 953 | else => return err, | ||
| 954 | }; | ||
| 955 | |||
| 956 | const any_prelink_errors = blk: { | ||
| 957 | const compile_errors = self.compile_errors.acquire(); | ||
| 958 | defer compile_errors.release(); | ||
| 959 | |||
| 960 | break :blk compile_errors.value.len != 0; | ||
| 961 | }; | ||
| 962 | |||
| 963 | if (!any_prelink_errors) { | ||
| 964 | try link(self); | ||
| 965 | } | ||
| 966 | } | ||
| 967 | |||
| 968 | /// caller takes ownership of resulting Code | ||
| 969 | async fn genAndAnalyzeCode( | ||
| 970 | comp: *Compilation, | ||
| 971 | tree_scope: *Scope.AstTree, | ||
| 972 | scope: *Scope, | ||
| 973 | node: *ast.Node, | ||
| 974 | expected_type: ?*Type, | ||
| 975 | ) !*ir.Code { | ||
| 976 | const unanalyzed_code = try ir.gen( | ||
| 977 | comp, | ||
| 978 | node, | ||
| 979 | tree_scope, | ||
| 980 | scope, | ||
| 981 | ); | ||
| 982 | defer unanalyzed_code.destroy(comp.gpa()); | ||
| 983 | |||
| 984 | if (comp.verbose_ir) { | ||
| 985 | std.debug.warn("unanalyzed:\n", .{}); | ||
| 986 | unanalyzed_code.dump(); | ||
| 987 | } | ||
| 988 | |||
| 989 | const analyzed_code = try ir.analyze( | ||
| 990 | comp, | ||
| 991 | unanalyzed_code, | ||
| 992 | expected_type, | ||
| 993 | ); | ||
| 994 | errdefer analyzed_code.destroy(comp.gpa()); | ||
| 995 | |||
| 996 | if (comp.verbose_ir) { | ||
| 997 | std.debug.warn("analyzed:\n", .{}); | ||
| 998 | analyzed_code.dump(); | ||
| 999 | } | ||
| 1000 | |||
| 1001 | return analyzed_code; | ||
| 1002 | } | ||
| 1003 | |||
| 1004 | async fn addCompTimeBlock( | ||
| 1005 | comp: *Compilation, | ||
| 1006 | tree_scope: *Scope.AstTree, | ||
| 1007 | scope: *Scope, | ||
| 1008 | comptime_node: *ast.Node.Comptime, | ||
| 1009 | ) BuildError!void { | ||
| 1010 | const void_type = Type.Void.get(comp); | ||
| 1011 | defer void_type.base.base.deref(comp); | ||
| 1012 | |||
| 1013 | const analyzed_code = genAndAnalyzeCode( | ||
| 1014 | comp, | ||
| 1015 | tree_scope, | ||
| 1016 | scope, | ||
| 1017 | comptime_node.expr, | ||
| 1018 | &void_type.base, | ||
| 1019 | ) catch |err| switch (err) { | ||
| 1020 | // This poison value should not cause the errdefers to run. It simply means | ||
| 1021 | // that comp.compile_errors is populated. | ||
| 1022 | error.SemanticAnalysisFailed => return {}, | ||
| 1023 | else => return err, | ||
| 1024 | }; | ||
| 1025 | analyzed_code.destroy(comp.gpa()); | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | async fn addTopLevelDecl( | ||
| 1029 | self: *Compilation, | ||
| 1030 | decl: *Decl, | ||
| 1031 | locked_table: *Decl.Table, | ||
| 1032 | ) BuildError!void { | ||
| 1033 | const is_export = decl.isExported(decl.tree_scope.tree); | ||
| 1034 | |||
| 1035 | if (is_export) { | ||
| 1036 | try self.prelink_group.call(verifyUniqueSymbol, .{ self, decl }); | ||
| 1037 | try self.prelink_group.call(resolveDecl, .{ self, decl }); | ||
| 1038 | } | ||
| 1039 | |||
| 1040 | const gop = try locked_table.getOrPut(decl.name); | ||
| 1041 | if (gop.found_existing) { | ||
| 1042 | try self.addCompileError(decl.tree_scope, decl.getSpan(), "redefinition of '{}'", .{decl.name}); | ||
| 1043 | // TODO note: other definition here | ||
| 1044 | } else { | ||
| 1045 | gop.kv.value = decl; | ||
| 1046 | } | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | fn addCompileError(self: *Compilation, tree_scope: *Scope.AstTree, span: Span, comptime fmt: []const u8, args: var) !void { | ||
| 1050 | const text = try std.fmt.allocPrint(self.gpa(), fmt, args); | ||
| 1051 | errdefer self.gpa().free(text); | ||
| 1052 | |||
| 1053 | const msg = try Msg.createFromScope(self, tree_scope, span, text); | ||
| 1054 | errdefer msg.destroy(); | ||
| 1055 | |||
| 1056 | try self.prelink_group.call(addCompileErrorAsync, .{ self, msg }); | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | fn addCompileErrorCli(self: *Compilation, realpath: []const u8, comptime fmt: []const u8, args: var) !void { | ||
| 1060 | const text = try std.fmt.allocPrint(self.gpa(), fmt, args); | ||
| 1061 | errdefer self.gpa().free(text); | ||
| 1062 | |||
| 1063 | const msg = try Msg.createFromCli(self, realpath, text); | ||
| 1064 | errdefer msg.destroy(); | ||
| 1065 | |||
| 1066 | try self.prelink_group.call(addCompileErrorAsync, .{ self, msg }); | ||
| 1067 | } | ||
| 1068 | |||
| 1069 | async fn addCompileErrorAsync( | ||
| 1070 | self: *Compilation, | ||
| 1071 | msg: *Msg, | ||
| 1072 | ) BuildError!void { | ||
| 1073 | errdefer msg.destroy(); | ||
| 1074 | |||
| 1075 | const compile_errors = self.compile_errors.acquire(); | ||
| 1076 | defer compile_errors.release(); | ||
| 1077 | |||
| 1078 | try compile_errors.value.append(msg); | ||
| 1079 | } | ||
| 1080 | |||
| 1081 | async fn verifyUniqueSymbol(self: *Compilation, decl: *Decl) BuildError!void { | ||
| 1082 | const exported_symbol_names = self.exported_symbol_names.acquire(); | ||
| 1083 | defer exported_symbol_names.release(); | ||
| 1084 | |||
| 1085 | if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| { | ||
| 1086 | try self.addCompileError(decl.tree_scope, decl.getSpan(), "exported symbol collision: '{}'", .{ | ||
| 1087 | decl.name, | ||
| 1088 | }); | ||
| 1089 | // TODO add error note showing location of other symbol | ||
| 1090 | } | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | pub fn haveLibC(self: *Compilation) bool { | ||
| 1094 | return self.libc_link_lib != null; | ||
| 1095 | } | ||
| 1096 | |||
| 1097 | pub fn addLinkLib(self: *Compilation, name: []const u8, provided_explicitly: bool) !*LinkLib { | ||
| 1098 | const is_libc = mem.eql(u8, name, "c"); | ||
| 1099 | |||
| 1100 | if (is_libc) { | ||
| 1101 | if (self.libc_link_lib) |libc_link_lib| { | ||
| 1102 | return libc_link_lib; | ||
| 1103 | } | ||
| 1104 | } | ||
| 1105 | |||
| 1106 | for (self.link_libs_list.span()) |existing_lib| { | ||
| 1107 | if (mem.eql(u8, name, existing_lib.name)) { | ||
| 1108 | return existing_lib; | ||
| 1109 | } | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | const link_lib = try self.gpa().create(LinkLib); | ||
| 1113 | link_lib.* = LinkLib{ | ||
| 1114 | .name = name, | ||
| 1115 | .path = null, | ||
| 1116 | .provided_explicitly = provided_explicitly, | ||
| 1117 | .symbols = ArrayList([]u8).init(self.gpa()), | ||
| 1118 | }; | ||
| 1119 | try self.link_libs_list.append(link_lib); | ||
| 1120 | if (is_libc) { | ||
| 1121 | self.libc_link_lib = link_lib; | ||
| 1122 | |||
| 1123 | // get a head start on looking for the native libc | ||
| 1124 | // TODO this is missing a bunch of logic related to whether the target is native | ||
| 1125 | // and whether we can build libc | ||
| 1126 | if (self.override_libc == null) { | ||
| 1127 | try self.deinit_group.call(startFindingNativeLibC, .{self}); | ||
| 1128 | } | ||
| 1129 | } | ||
| 1130 | return link_lib; | ||
| 1131 | } | ||
| 1132 | |||
| 1133 | async fn startFindingNativeLibC(self: *Compilation) void { | ||
| 1134 | event.Loop.startCpuBoundOperation(); | ||
| 1135 | // we don't care if it fails, we're just trying to kick off the future resolution | ||
| 1136 | _ = self.zig_compiler.getNativeLibC() catch return; | ||
| 1137 | } | ||
| 1138 | |||
| 1139 | /// General Purpose Allocator. Must free when done. | ||
| 1140 | fn gpa(self: Compilation) *mem.Allocator { | ||
| 1141 | return self.zig_compiler.allocator; | ||
| 1142 | } | ||
| 1143 | |||
| 1144 | /// Arena Allocator. Automatically freed when the Compilation is destroyed. | ||
| 1145 | fn arena(self: *Compilation) *mem.Allocator { | ||
| 1146 | return &self.arena_allocator.allocator; | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | /// If the temporary directory for this compilation has not been created, it creates it. | ||
| 1150 | /// Then it creates a random file name in that dir and returns it. | ||
| 1151 | pub fn createRandomOutputPath(self: *Compilation, suffix: []const u8) !ArrayListSentineled(u8, 0) { | ||
| 1152 | const tmp_dir = try self.getTmpDir(); | ||
| 1153 | const file_prefix = self.getRandomFileName(); | ||
| 1154 | |||
| 1155 | const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix }); | ||
| 1156 | defer self.gpa().free(file_name); | ||
| 1157 | |||
| 1158 | const full_path = try fs.path.join(self.gpa(), &[_][]const u8{ tmp_dir, file_name[0..] }); | ||
| 1159 | errdefer self.gpa().free(full_path); | ||
| 1160 | |||
| 1161 | return ArrayListSentineled(u8, 0).fromOwnedSlice(self.gpa(), full_path); | ||
| 1162 | } | ||
| 1163 | |||
| 1164 | /// If the temporary directory for this Compilation has not been created, creates it. | ||
| 1165 | /// Then returns it. The directory is unique to this Compilation and cleaned up when | ||
| 1166 | /// the Compilation deinitializes. | ||
| 1167 | fn getTmpDir(self: *Compilation) ![]const u8 { | ||
| 1168 | if (self.tmp_dir.start()) |ptr| return ptr.*; | ||
| 1169 | self.tmp_dir.data = self.getTmpDirImpl(); | ||
| 1170 | self.tmp_dir.resolve(); | ||
| 1171 | return self.tmp_dir.data; | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | fn getTmpDirImpl(self: *Compilation) ![]u8 { | ||
| 1175 | const comp_dir_name = self.getRandomFileName(); | ||
| 1176 | const zig_dir_path = try getZigDir(self.gpa()); | ||
| 1177 | defer self.gpa().free(zig_dir_path); | ||
| 1178 | |||
| 1179 | const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] }); | ||
| 1180 | try fs.cwd().makePath(tmp_dir); | ||
| 1181 | return tmp_dir; | ||
| 1182 | } | ||
| 1183 | |||
| 1184 | fn getRandomFileName(self: *Compilation) [12]u8 { | ||
| 1185 | // here we replace the standard +/ with -_ so that it can be used in a file name | ||
| 1186 | const b64_fs_encoder = std.base64.Base64Encoder.init( | ||
| 1187 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", | ||
| 1188 | std.base64.standard_pad_char, | ||
| 1189 | ); | ||
| 1190 | |||
| 1191 | var rand_bytes: [9]u8 = undefined; | ||
| 1192 | |||
| 1193 | { | ||
| 1194 | const held = self.zig_compiler.prng.acquire(); | ||
| 1195 | defer held.release(); | ||
| 1196 | |||
| 1197 | held.value.random.bytes(rand_bytes[0..]); | ||
| 1198 | } | ||
| 1199 | |||
| 1200 | var result: [12]u8 = undefined; | ||
| 1201 | b64_fs_encoder.encode(result[0..], &rand_bytes); | ||
| 1202 | return result; | ||
| 1203 | } | ||
| 1204 | |||
| 1205 | fn registerGarbage(comp: *Compilation, comptime T: type, node: *std.atomic.Stack(*T).Node) void { | ||
| 1206 | // TODO put the garbage somewhere | ||
| 1207 | } | ||
| 1208 | |||
| 1209 | /// Returns a value which has been ref()'d once | ||
| 1210 | fn analyzeConstValue( | ||
| 1211 | comp: *Compilation, | ||
| 1212 | tree_scope: *Scope.AstTree, | ||
| 1213 | scope: *Scope, | ||
| 1214 | node: *ast.Node, | ||
| 1215 | expected_type: *Type, | ||
| 1216 | ) !*Value { | ||
| 1217 | var frame = try comp.gpa().create(@Frame(genAndAnalyzeCode)); | ||
| 1218 | defer comp.gpa().destroy(frame); | ||
| 1219 | frame.* = async comp.genAndAnalyzeCode(tree_scope, scope, node, expected_type); | ||
| 1220 | const analyzed_code = try await frame; | ||
| 1221 | defer analyzed_code.destroy(comp.gpa()); | ||
| 1222 | |||
| 1223 | return analyzed_code.getCompTimeResult(comp); | ||
| 1224 | } | ||
| 1225 | |||
| 1226 | fn analyzeTypeExpr(comp: *Compilation, tree_scope: *Scope.AstTree, scope: *Scope, node: *ast.Node) !*Type { | ||
| 1227 | const meta_type = &Type.MetaType.get(comp).base; | ||
| 1228 | defer meta_type.base.deref(comp); | ||
| 1229 | |||
| 1230 | const result_val = try comp.analyzeConstValue(tree_scope, scope, node, meta_type); | ||
| 1231 | errdefer result_val.base.deref(comp); | ||
| 1232 | |||
| 1233 | return result_val.cast(Type).?; | ||
| 1234 | } | ||
| 1235 | |||
| 1236 | /// This declaration has been blessed as going into the final code generation. | ||
| 1237 | pub async fn resolveDecl(comp: *Compilation, decl: *Decl) BuildError!void { | ||
| 1238 | if (decl.resolution.start()) |ptr| return ptr.*; | ||
| 1239 | |||
| 1240 | decl.resolution.data = try generateDecl(comp, decl); | ||
| 1241 | decl.resolution.resolve(); | ||
| 1242 | return decl.resolution.data; | ||
| 1243 | } | ||
| 1244 | }; | ||
| 1245 | |||
| 1246 | fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib { | ||
| 1247 | if (optional_token_index) |token_index| { | ||
| 1248 | const token = tree.tokens.at(token_index); | ||
| 1249 | assert(token.id == Token.Id.Keyword_pub); | ||
| 1250 | return Visib.Pub; | ||
| 1251 | } else { | ||
| 1252 | return Visib.Private; | ||
| 1253 | } | ||
| 1254 | } | ||
| 1255 | |||
| 1256 | /// The function that actually does the generation. | ||
| 1257 | fn generateDecl(comp: *Compilation, decl: *Decl) !void { | ||
| 1258 | switch (decl.id) { | ||
| 1259 | .Var => @panic("TODO"), | ||
| 1260 | .Fn => { | ||
| 1261 | const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl); | ||
| 1262 | return generateDeclFn(comp, fn_decl); | ||
| 1263 | }, | ||
| 1264 | .CompTime => @panic("TODO"), | ||
| 1265 | } | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void { | ||
| 1269 | const tree_scope = fn_decl.base.tree_scope; | ||
| 1270 | |||
| 1271 | const body_node = fn_decl.fn_proto.body_node orelse return generateDeclFnProto(comp, fn_decl); | ||
| 1272 | |||
| 1273 | const fndef_scope = try Scope.FnDef.create(comp, fn_decl.base.parent_scope); | ||
| 1274 | defer fndef_scope.base.deref(comp); | ||
| 1275 | |||
| 1276 | const fn_type = try analyzeFnType(comp, tree_scope, fn_decl.base.parent_scope, fn_decl.fn_proto); | ||
| 1277 | defer fn_type.base.base.deref(comp); | ||
| 1278 | |||
| 1279 | var symbol_name = try std.ArrayListSentineled(u8, 0).init(comp.gpa(), fn_decl.base.name); | ||
| 1280 | var symbol_name_consumed = false; | ||
| 1281 | errdefer if (!symbol_name_consumed) symbol_name.deinit(); | ||
| 1282 | |||
| 1283 | // The Decl.Fn owns the initial 1 reference count | ||
| 1284 | const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name); | ||
| 1285 | fn_decl.value = .{ .Fn = fn_val }; | ||
| 1286 | symbol_name_consumed = true; | ||
| 1287 | |||
| 1288 | // Define local parameter variables | ||
| 1289 | for (fn_type.key.data.Normal.params) |param, i| { | ||
| 1290 | //AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i); | ||
| 1291 | const param_decl = @fieldParentPtr(ast.Node.ParamDecl, "base", fn_decl.fn_proto.params.at(i).*); | ||
| 1292 | const name_token = param_decl.name_token orelse { | ||
| 1293 | try comp.addCompileError(tree_scope, Span{ | ||
| 1294 | .first = param_decl.firstToken(), | ||
| 1295 | .last = param_decl.type_node.firstToken(), | ||
| 1296 | }, "missing parameter name", .{}); | ||
| 1297 | return error.SemanticAnalysisFailed; | ||
| 1298 | }; | ||
| 1299 | const param_name = tree_scope.tree.tokenSlice(name_token); | ||
| 1300 | |||
| 1301 | // if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) { | ||
| 1302 | // add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter")); | ||
| 1303 | // } | ||
| 1304 | |||
| 1305 | // TODO check for shadowing | ||
| 1306 | |||
| 1307 | const var_scope = try Scope.Var.createParam( | ||
| 1308 | comp, | ||
| 1309 | fn_val.child_scope, | ||
| 1310 | param_name, | ||
| 1311 | &param_decl.base, | ||
| 1312 | i, | ||
| 1313 | param.typ, | ||
| 1314 | ); | ||
| 1315 | fn_val.child_scope = &var_scope.base; | ||
| 1316 | |||
| 1317 | try fn_type.non_key.Normal.variable_list.append(var_scope); | ||
| 1318 | } | ||
| 1319 | |||
| 1320 | var frame = try comp.gpa().create(@Frame(Compilation.genAndAnalyzeCode)); | ||
| 1321 | defer comp.gpa().destroy(frame); | ||
| 1322 | frame.* = async comp.genAndAnalyzeCode( | ||
| 1323 | tree_scope, | ||
| 1324 | fn_val.child_scope, | ||
| 1325 | body_node, | ||
| 1326 | fn_type.key.data.Normal.return_type, | ||
| 1327 | ); | ||
| 1328 | const analyzed_code = try await frame; | ||
| 1329 | errdefer analyzed_code.destroy(comp.gpa()); | ||
| 1330 | |||
| 1331 | assert(fn_val.block_scope != null); | ||
| 1332 | |||
| 1333 | // Kick off rendering to LLVM module, but it doesn't block the fn decl | ||
| 1334 | // analysis from being complete. | ||
| 1335 | try comp.prelink_group.call(codegen.renderToLlvm, .{ comp, fn_val, analyzed_code }); | ||
| 1336 | try comp.prelink_group.call(addFnToLinkSet, .{ comp, fn_val }); | ||
| 1337 | } | ||
| 1338 | |||
| 1339 | async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) Compilation.BuildError!void { | ||
| 1340 | fn_val.base.ref(); | ||
| 1341 | defer fn_val.base.deref(comp); | ||
| 1342 | |||
| 1343 | fn_val.link_set_node.data = fn_val; | ||
| 1344 | |||
| 1345 | const held = comp.fn_link_set.acquire(); | ||
| 1346 | defer held.release(); | ||
| 1347 | |||
| 1348 | held.value.append(fn_val.link_set_node); | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | fn getZigDir(allocator: *mem.Allocator) ![]u8 { | ||
| 1352 | return fs.getAppDataDir(allocator, "zig"); | ||
| 1353 | } | ||
| 1354 | |||
| 1355 | fn analyzeFnType( | ||
| 1356 | comp: *Compilation, | ||
| 1357 | tree_scope: *Scope.AstTree, | ||
| 1358 | scope: *Scope, | ||
| 1359 | fn_proto: *ast.Node.FnProto, | ||
| 1360 | ) !*Type.Fn { | ||
| 1361 | const return_type_node = switch (fn_proto.return_type) { | ||
| 1362 | .Explicit => |n| n, | ||
| 1363 | .InferErrorSet => |n| n, | ||
| 1364 | }; | ||
| 1365 | const return_type = try comp.analyzeTypeExpr(tree_scope, scope, return_type_node); | ||
| 1366 | return_type.base.deref(comp); | ||
| 1367 | |||
| 1368 | var params = ArrayList(Type.Fn.Param).init(comp.gpa()); | ||
| 1369 | var params_consumed = false; | ||
| 1370 | defer if (!params_consumed) { | ||
| 1371 | for (params.span()) |param| { | ||
| 1372 | param.typ.base.deref(comp); | ||
| 1373 | } | ||
| 1374 | params.deinit(); | ||
| 1375 | }; | ||
| 1376 | |||
| 1377 | { | ||
| 1378 | var it = fn_proto.params.iterator(0); | ||
| 1379 | while (it.next()) |param_node_ptr| { | ||
| 1380 | const param_node = param_node_ptr.*.cast(ast.Node.ParamDecl).?; | ||
| 1381 | const param_type = try comp.analyzeTypeExpr(tree_scope, scope, param_node.type_node); | ||
| 1382 | errdefer param_type.base.deref(comp); | ||
| 1383 | try params.append(Type.Fn.Param{ | ||
| 1384 | .typ = param_type, | ||
| 1385 | .is_noalias = param_node.noalias_token != null, | ||
| 1386 | }); | ||
| 1387 | } | ||
| 1388 | } | ||
| 1389 | |||
| 1390 | const key = Type.Fn.Key{ | ||
| 1391 | .alignment = null, | ||
| 1392 | .data = Type.Fn.Key.Data{ | ||
| 1393 | .Normal = Type.Fn.Key.Normal{ | ||
| 1394 | .return_type = return_type, | ||
| 1395 | .params = params.toOwnedSlice(), | ||
| 1396 | .is_var_args = false, // TODO | ||
| 1397 | .cc = .Unspecified, // TODO | ||
| 1398 | }, | ||
| 1399 | }, | ||
| 1400 | }; | ||
| 1401 | params_consumed = true; | ||
| 1402 | var key_consumed = false; | ||
| 1403 | defer if (!key_consumed) { | ||
| 1404 | for (key.data.Normal.params) |param| { | ||
| 1405 | param.typ.base.deref(comp); | ||
| 1406 | } | ||
| 1407 | comp.gpa().free(key.data.Normal.params); | ||
| 1408 | }; | ||
| 1409 | |||
| 1410 | const fn_type = try Type.Fn.get(comp, key); | ||
| 1411 | key_consumed = true; | ||
| 1412 | errdefer fn_type.base.base.deref(comp); | ||
| 1413 | |||
| 1414 | return fn_type; | ||
| 1415 | } | ||
| 1416 | |||
| 1417 | fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void { | ||
| 1418 | const fn_type = try analyzeFnType( | ||
| 1419 | comp, | ||
| 1420 | fn_decl.base.tree_scope, | ||
| 1421 | fn_decl.base.parent_scope, | ||
| 1422 | fn_decl.fn_proto, | ||
| 1423 | ); | ||
| 1424 | defer fn_type.base.base.deref(comp); | ||
| 1425 | |||
| 1426 | var symbol_name = try std.ArrayListSentineled(u8, 0).init(comp.gpa(), fn_decl.base.name); | ||
| 1427 | var symbol_name_consumed = false; | ||
| 1428 | defer if (!symbol_name_consumed) symbol_name.deinit(); | ||
| 1429 | |||
| 1430 | // The Decl.Fn owns the initial 1 reference count | ||
| 1431 | const fn_proto_val = try Value.FnProto.create(comp, fn_type, symbol_name); | ||
| 1432 | fn_decl.value = .{ .FnProto = fn_proto_val }; | ||
| 1433 | symbol_name_consumed = true; | ||
| 1434 | } | ||
src-self-hosted/decl.zig deleted-102| ... | @@ -1,102 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Allocator = mem.Allocator; | ||
| 3 | const mem = std.mem; | ||
| 4 | const ast = std.zig.ast; | ||
| 5 | const Visib = @import("visib.zig").Visib; | ||
| 6 | const event = std.event; | ||
| 7 | const Value = @import("value.zig").Value; | ||
| 8 | const Token = std.zig.Token; | ||
| 9 | const errmsg = @import("errmsg.zig"); | ||
| 10 | const Scope = @import("scope.zig").Scope; | ||
| 11 | const Compilation = @import("compilation.zig").Compilation; | ||
| 12 | |||
| 13 | pub const Decl = struct { | ||
| 14 | id: Id, | ||
| 15 | name: []const u8, | ||
| 16 | visib: Visib, | ||
| 17 | resolution: event.Future(Compilation.BuildError!void), | ||
| 18 | parent_scope: *Scope, | ||
| 19 | |||
| 20 | // TODO when we destroy the decl, deref the tree scope | ||
| 21 | tree_scope: *Scope.AstTree, | ||
| 22 | |||
| 23 | pub const Table = std.StringHashMap(*Decl); | ||
| 24 | |||
| 25 | pub fn cast(base: *Decl, comptime T: type) ?*T { | ||
| 26 | if (base.id != @field(Id, @typeName(T))) return null; | ||
| 27 | return @fieldParentPtr(T, "base", base); | ||
| 28 | } | ||
| 29 | |||
| 30 | pub fn isExported(base: *const Decl, tree: *ast.Tree) bool { | ||
| 31 | switch (base.id) { | ||
| 32 | .Fn => { | ||
| 33 | const fn_decl = @fieldParentPtr(Fn, "base", base); | ||
| 34 | return fn_decl.isExported(tree); | ||
| 35 | }, | ||
| 36 | else => return false, | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | pub fn getSpan(base: *const Decl) errmsg.Span { | ||
| 41 | switch (base.id) { | ||
| 42 | .Fn => { | ||
| 43 | const fn_decl = @fieldParentPtr(Fn, "base", base); | ||
| 44 | const fn_proto = fn_decl.fn_proto; | ||
| 45 | const start = fn_proto.fn_token; | ||
| 46 | const end = fn_proto.name_token orelse start; | ||
| 47 | return errmsg.Span{ | ||
| 48 | .first = start, | ||
| 49 | .last = end + 1, | ||
| 50 | }; | ||
| 51 | }, | ||
| 52 | else => @panic("TODO"), | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | pub fn findRootScope(base: *const Decl) *Scope.Root { | ||
| 57 | return base.parent_scope.findRoot(); | ||
| 58 | } | ||
| 59 | |||
| 60 | pub const Id = enum { | ||
| 61 | Var, | ||
| 62 | Fn, | ||
| 63 | CompTime, | ||
| 64 | }; | ||
| 65 | |||
| 66 | pub const Var = struct { | ||
| 67 | base: Decl, | ||
| 68 | }; | ||
| 69 | |||
| 70 | pub const Fn = struct { | ||
| 71 | base: Decl, | ||
| 72 | value: union(enum) { | ||
| 73 | Unresolved, | ||
| 74 | Fn: *Value.Fn, | ||
| 75 | FnProto: *Value.FnProto, | ||
| 76 | }, | ||
| 77 | fn_proto: *ast.Node.FnProto, | ||
| 78 | |||
| 79 | pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 { | ||
| 80 | return if (self.fn_proto.extern_export_inline_token) |tok_index| x: { | ||
| 81 | const token = tree.tokens.at(tok_index); | ||
| 82 | break :x switch (token.id) { | ||
| 83 | .Extern => tree.tokenSlicePtr(token), | ||
| 84 | else => null, | ||
| 85 | }; | ||
| 86 | } else null; | ||
| 87 | } | ||
| 88 | |||
| 89 | pub fn isExported(self: Fn, tree: *ast.Tree) bool { | ||
| 90 | if (self.fn_proto.extern_export_inline_token) |tok_index| { | ||
| 91 | const token = tree.tokens.at(tok_index); | ||
| 92 | return token.id == .Keyword_export; | ||
| 93 | } else { | ||
| 94 | return false; | ||
| 95 | } | ||
| 96 | } | ||
| 97 | }; | ||
| 98 | |||
| 99 | pub const CompTime = struct { | ||
| 100 | base: Decl, | ||
| 101 | }; | ||
| 102 | }; | ||
src-self-hosted/errmsg.zig deleted-284| ... | @@ -1,284 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const mem = std.mem; | ||
| 3 | const fs = std.fs; | ||
| 4 | const process = std.process; | ||
| 5 | const Token = std.zig.Token; | ||
| 6 | const ast = std.zig.ast; | ||
| 7 | const TokenIndex = std.zig.ast.TokenIndex; | ||
| 8 | const Compilation = @import("compilation.zig").Compilation; | ||
| 9 | const Scope = @import("scope.zig").Scope; | ||
| 10 | |||
| 11 | pub const Color = enum { | ||
| 12 | Auto, | ||
| 13 | Off, | ||
| 14 | On, | ||
| 15 | }; | ||
| 16 | |||
| 17 | pub const Span = struct { | ||
| 18 | first: ast.TokenIndex, | ||
| 19 | last: ast.TokenIndex, | ||
| 20 | |||
| 21 | pub fn token(i: TokenIndex) Span { | ||
| 22 | return Span{ | ||
| 23 | .first = i, | ||
| 24 | .last = i, | ||
| 25 | }; | ||
| 26 | } | ||
| 27 | |||
| 28 | pub fn node(n: *ast.Node) Span { | ||
| 29 | return Span{ | ||
| 30 | .first = n.firstToken(), | ||
| 31 | .last = n.lastToken(), | ||
| 32 | }; | ||
| 33 | } | ||
| 34 | }; | ||
| 35 | |||
| 36 | pub const Msg = struct { | ||
| 37 | text: []u8, | ||
| 38 | realpath: []u8, | ||
| 39 | data: Data, | ||
| 40 | |||
| 41 | const Data = union(enum) { | ||
| 42 | Cli: Cli, | ||
| 43 | PathAndTree: PathAndTree, | ||
| 44 | ScopeAndComp: ScopeAndComp, | ||
| 45 | }; | ||
| 46 | |||
| 47 | const PathAndTree = struct { | ||
| 48 | span: Span, | ||
| 49 | tree: *ast.Tree, | ||
| 50 | allocator: *mem.Allocator, | ||
| 51 | }; | ||
| 52 | |||
| 53 | const ScopeAndComp = struct { | ||
| 54 | span: Span, | ||
| 55 | tree_scope: *Scope.AstTree, | ||
| 56 | compilation: *Compilation, | ||
| 57 | }; | ||
| 58 | |||
| 59 | const Cli = struct { | ||
| 60 | allocator: *mem.Allocator, | ||
| 61 | }; | ||
| 62 | |||
| 63 | pub fn destroy(self: *Msg) void { | ||
| 64 | switch (self.data) { | ||
| 65 | .Cli => |cli| { | ||
| 66 | cli.allocator.free(self.text); | ||
| 67 | cli.allocator.free(self.realpath); | ||
| 68 | cli.allocator.destroy(self); | ||
| 69 | }, | ||
| 70 | .PathAndTree => |path_and_tree| { | ||
| 71 | path_and_tree.allocator.free(self.text); | ||
| 72 | path_and_tree.allocator.free(self.realpath); | ||
| 73 | path_and_tree.allocator.destroy(self); | ||
| 74 | }, | ||
| 75 | .ScopeAndComp => |scope_and_comp| { | ||
| 76 | scope_and_comp.tree_scope.base.deref(scope_and_comp.compilation); | ||
| 77 | scope_and_comp.compilation.gpa().free(self.text); | ||
| 78 | scope_and_comp.compilation.gpa().free(self.realpath); | ||
| 79 | scope_and_comp.compilation.gpa().destroy(self); | ||
| 80 | }, | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | fn getAllocator(self: *const Msg) *mem.Allocator { | ||
| 85 | switch (self.data) { | ||
| 86 | .Cli => |cli| return cli.allocator, | ||
| 87 | .PathAndTree => |path_and_tree| { | ||
| 88 | return path_and_tree.allocator; | ||
| 89 | }, | ||
| 90 | .ScopeAndComp => |scope_and_comp| { | ||
| 91 | return scope_and_comp.compilation.gpa(); | ||
| 92 | }, | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | pub fn getTree(self: *const Msg) *ast.Tree { | ||
| 97 | switch (self.data) { | ||
| 98 | .Cli => unreachable, | ||
| 99 | .PathAndTree => |path_and_tree| { | ||
| 100 | return path_and_tree.tree; | ||
| 101 | }, | ||
| 102 | .ScopeAndComp => |scope_and_comp| { | ||
| 103 | return scope_and_comp.tree_scope.tree; | ||
| 104 | }, | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | pub fn getSpan(self: *const Msg) Span { | ||
| 109 | return switch (self.data) { | ||
| 110 | .Cli => unreachable, | ||
| 111 | .PathAndTree => |path_and_tree| path_and_tree.span, | ||
| 112 | .ScopeAndComp => |scope_and_comp| scope_and_comp.span, | ||
| 113 | }; | ||
| 114 | } | ||
| 115 | |||
| 116 | /// Takes ownership of text | ||
| 117 | /// References tree_scope, and derefs when the msg is freed | ||
| 118 | pub fn createFromScope(comp: *Compilation, tree_scope: *Scope.AstTree, span: Span, text: []u8) !*Msg { | ||
| 119 | const realpath = try mem.dupe(comp.gpa(), u8, tree_scope.root().realpath); | ||
| 120 | errdefer comp.gpa().free(realpath); | ||
| 121 | |||
| 122 | const msg = try comp.gpa().create(Msg); | ||
| 123 | msg.* = Msg{ | ||
| 124 | .text = text, | ||
| 125 | .realpath = realpath, | ||
| 126 | .data = Data{ | ||
| 127 | .ScopeAndComp = ScopeAndComp{ | ||
| 128 | .tree_scope = tree_scope, | ||
| 129 | .compilation = comp, | ||
| 130 | .span = span, | ||
| 131 | }, | ||
| 132 | }, | ||
| 133 | }; | ||
| 134 | tree_scope.base.ref(); | ||
| 135 | return msg; | ||
| 136 | } | ||
| 137 | |||
| 138 | /// Caller owns returned Msg and must free with `allocator` | ||
| 139 | /// allocator will additionally be used for printing messages later. | ||
| 140 | pub fn createFromCli(comp: *Compilation, realpath: []const u8, text: []u8) !*Msg { | ||
| 141 | const realpath_copy = try mem.dupe(comp.gpa(), u8, realpath); | ||
| 142 | errdefer comp.gpa().free(realpath_copy); | ||
| 143 | |||
| 144 | const msg = try comp.gpa().create(Msg); | ||
| 145 | msg.* = Msg{ | ||
| 146 | .text = text, | ||
| 147 | .realpath = realpath_copy, | ||
| 148 | .data = Data{ | ||
| 149 | .Cli = Cli{ .allocator = comp.gpa() }, | ||
| 150 | }, | ||
| 151 | }; | ||
| 152 | return msg; | ||
| 153 | } | ||
| 154 | |||
| 155 | pub fn createFromParseErrorAndScope( | ||
| 156 | comp: *Compilation, | ||
| 157 | tree_scope: *Scope.AstTree, | ||
| 158 | parse_error: *const ast.Error, | ||
| 159 | ) !*Msg { | ||
| 160 | const loc_token = parse_error.loc(); | ||
| 161 | var text_buf = std.ArrayList(u8).init(comp.gpa()); | ||
| 162 | defer text_buf.deinit(); | ||
| 163 | |||
| 164 | const realpath_copy = try mem.dupe(comp.gpa(), u8, tree_scope.root().realpath); | ||
| 165 | errdefer comp.gpa().free(realpath_copy); | ||
| 166 | |||
| 167 | try parse_error.render(&tree_scope.tree.tokens, text_buf.outStream()); | ||
| 168 | |||
| 169 | const msg = try comp.gpa().create(Msg); | ||
| 170 | msg.* = Msg{ | ||
| 171 | .text = undefined, | ||
| 172 | .realpath = realpath_copy, | ||
| 173 | .data = Data{ | ||
| 174 | .ScopeAndComp = ScopeAndComp{ | ||
| 175 | .tree_scope = tree_scope, | ||
| 176 | .compilation = comp, | ||
| 177 | .span = Span{ | ||
| 178 | .first = loc_token, | ||
| 179 | .last = loc_token, | ||
| 180 | }, | ||
| 181 | }, | ||
| 182 | }, | ||
| 183 | }; | ||
| 184 | tree_scope.base.ref(); | ||
| 185 | msg.text = text_buf.toOwnedSlice(); | ||
| 186 | return msg; | ||
| 187 | } | ||
| 188 | |||
| 189 | /// `realpath` must outlive the returned Msg | ||
| 190 | /// `tree` must outlive the returned Msg | ||
| 191 | /// Caller owns returned Msg and must free with `allocator` | ||
| 192 | /// allocator will additionally be used for printing messages later. | ||
| 193 | pub fn createFromParseError( | ||
| 194 | allocator: *mem.Allocator, | ||
| 195 | parse_error: *const ast.Error, | ||
| 196 | tree: *ast.Tree, | ||
| 197 | realpath: []const u8, | ||
| 198 | ) !*Msg { | ||
| 199 | const loc_token = parse_error.loc(); | ||
| 200 | var text_buf = std.ArrayList(u8).init(allocator); | ||
| 201 | defer text_buf.deinit(); | ||
| 202 | |||
| 203 | const realpath_copy = try mem.dupe(allocator, u8, realpath); | ||
| 204 | errdefer allocator.free(realpath_copy); | ||
| 205 | |||
| 206 | try parse_error.render(&tree.tokens, text_buf.outStream()); | ||
| 207 | |||
| 208 | const msg = try allocator.create(Msg); | ||
| 209 | msg.* = Msg{ | ||
| 210 | .text = undefined, | ||
| 211 | .realpath = realpath_copy, | ||
| 212 | .data = Data{ | ||
| 213 | .PathAndTree = PathAndTree{ | ||
| 214 | .allocator = allocator, | ||
| 215 | .tree = tree, | ||
| 216 | .span = Span{ | ||
| 217 | .first = loc_token, | ||
| 218 | .last = loc_token, | ||
| 219 | }, | ||
| 220 | }, | ||
| 221 | }, | ||
| 222 | }; | ||
| 223 | msg.text = text_buf.toOwnedSlice(); | ||
| 224 | errdefer allocator.destroy(msg); | ||
| 225 | |||
| 226 | return msg; | ||
| 227 | } | ||
| 228 | |||
| 229 | pub fn printToStream(msg: *const Msg, stream: var, color_on: bool) !void { | ||
| 230 | switch (msg.data) { | ||
| 231 | .Cli => { | ||
| 232 | try stream.print("{}:-:-: error: {}\n", .{ msg.realpath, msg.text }); | ||
| 233 | return; | ||
| 234 | }, | ||
| 235 | else => {}, | ||
| 236 | } | ||
| 237 | |||
| 238 | const allocator = msg.getAllocator(); | ||
| 239 | const tree = msg.getTree(); | ||
| 240 | |||
| 241 | const cwd = try process.getCwdAlloc(allocator); | ||
| 242 | defer allocator.free(cwd); | ||
| 243 | |||
| 244 | const relpath = try fs.path.relative(allocator, cwd, msg.realpath); | ||
| 245 | defer allocator.free(relpath); | ||
| 246 | |||
| 247 | const path = if (relpath.len < msg.realpath.len) relpath else msg.realpath; | ||
| 248 | const span = msg.getSpan(); | ||
| 249 | |||
| 250 | const first_token = tree.tokens.at(span.first); | ||
| 251 | const last_token = tree.tokens.at(span.last); | ||
| 252 | const start_loc = tree.tokenLocationPtr(0, first_token); | ||
| 253 | const end_loc = tree.tokenLocationPtr(first_token.end, last_token); | ||
| 254 | if (!color_on) { | ||
| 255 | try stream.print("{}:{}:{}: error: {}\n", .{ | ||
| 256 | path, | ||
| 257 | start_loc.line + 1, | ||
| 258 | start_loc.column + 1, | ||
| 259 | msg.text, | ||
| 260 | }); | ||
| 261 | return; | ||
| 262 | } | ||
| 263 | |||
| 264 | try stream.print("{}:{}:{}: error: {}\n{}\n", .{ | ||
| 265 | path, | ||
| 266 | start_loc.line + 1, | ||
| 267 | start_loc.column + 1, | ||
| 268 | msg.text, | ||
| 269 | tree.source[start_loc.line_start..start_loc.line_end], | ||
| 270 | }); | ||
| 271 | try stream.writeByteNTimes(' ', start_loc.column); | ||
| 272 | try stream.writeByteNTimes('~', last_token.end - first_token.start); | ||
| 273 | try stream.writeAll("\n"); | ||
| 274 | } | ||
| 275 | |||
| 276 | pub fn printToFile(msg: *const Msg, file: fs.File, color: Color) !void { | ||
| 277 | const color_on = switch (color) { | ||
| 278 | .Auto => file.isTty(), | ||
| 279 | .On => true, | ||
| 280 | .Off => false, | ||
| 281 | }; | ||
| 282 | return msg.printToStream(file.outStream(), color_on); | ||
| 283 | } | ||
| 284 | }; | ||
src-self-hosted/ir.zig+12-1206| ... | @@ -1,16 +1,9 @@ | ... | @@ -1,16 +1,9 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const mem = std.mem; | ||
| 3 | const Allocator = std.mem.Allocator; | ||
| 4 | const Value = @import("value.zig").Value; | 2 | const Value = @import("value.zig").Value; |
| 5 | const Type = @import("type.zig").Type; | 3 | const Type = @import("type.zig").Type; |
| 6 | const assert = std.debug.assert; | 4 | const Module = @import("Module.zig"); |
| 7 | const BigIntConst = std.math.big.int.Const; | ||
| 8 | const BigIntMutable = std.math.big.int.Mutable; | ||
| 9 | const Target = std.Target; | ||
| 10 | 5 | ||
| 11 | pub const text = @import("ir/text.zig"); | 6 | /// These are in-memory, analyzed instructions. See `zir.Inst` for the representation |
| 12 | |||
| 13 | /// These are in-memory, analyzed instructions. See `text.Inst` for the representation | ||
| 14 | /// of instructions that correspond to the ZIR text format. | 7 | /// of instructions that correspond to the ZIR text format. |
| 15 | /// This struct owns the `Value` and `Type` memory. When the struct is deallocated, | 8 | /// This struct owns the `Value` and `Type` memory. When the struct is deallocated, |
| 16 | /// so are the `Value` and `Type`. The value of a constant must be copied into | 9 | /// so are the `Value` and `Type`. The value of a constant must be copied into |
| ... | @@ -25,6 +18,7 @@ pub const Inst = struct { | ... | @@ -25,6 +18,7 @@ pub const Inst = struct { |
| 25 | assembly, | 18 | assembly, |
| 26 | bitcast, | 19 | bitcast, |
| 27 | breakpoint, | 20 | breakpoint, |
| 21 | call, | ||
| 28 | cmp, | 22 | cmp, |
| 29 | condbr, | 23 | condbr, |
| 30 | constant, | 24 | constant, |
| ... | @@ -84,6 +78,15 @@ pub const Inst = struct { | ... | @@ -84,6 +78,15 @@ pub const Inst = struct { |
| 84 | args: void, | 78 | args: void, |
| 85 | }; | 79 | }; |
| 86 | 80 | ||
| 81 | pub const Call = struct { | ||
| 82 | pub const base_tag = Tag.call; | ||
| 83 | base: Inst, | ||
| 84 | args: struct { | ||
| 85 | func: *Inst, | ||
| 86 | args: []const *Inst, | ||
| 87 | }, | ||
| 88 | }; | ||
| 89 | |||
| 87 | pub const Cmp = struct { | 90 | pub const Cmp = struct { |
| 88 | pub const base_tag = Tag.cmp; | 91 | pub const base_tag = Tag.cmp; |
| 89 | 92 | ||
| ... | @@ -152,1200 +155,3 @@ pub const Inst = struct { | ... | @@ -152,1200 +155,3 @@ pub const Inst = struct { |
| 152 | args: void, | 155 | args: void, |
| 153 | }; | 156 | }; |
| 154 | }; | 157 | }; |
| 155 | |||
| 156 | pub const TypedValue = struct { | ||
| 157 | ty: Type, | ||
| 158 | val: Value, | ||
| 159 | }; | ||
| 160 | |||
| 161 | pub const Module = struct { | ||
| 162 | exports: []Export, | ||
| 163 | errors: []ErrorMsg, | ||
| 164 | arena: std.heap.ArenaAllocator, | ||
| 165 | fns: []Fn, | ||
| 166 | target: Target, | ||
| 167 | link_mode: std.builtin.LinkMode, | ||
| 168 | output_mode: std.builtin.OutputMode, | ||
| 169 | object_format: std.Target.ObjectFormat, | ||
| 170 | optimize_mode: std.builtin.Mode, | ||
| 171 | |||
| 172 | pub const Export = struct { | ||
| 173 | name: []const u8, | ||
| 174 | typed_value: TypedValue, | ||
| 175 | src: usize, | ||
| 176 | }; | ||
| 177 | |||
| 178 | pub const Fn = struct { | ||
| 179 | analysis_status: enum { in_progress, failure, success }, | ||
| 180 | body: Body, | ||
| 181 | fn_type: Type, | ||
| 182 | }; | ||
| 183 | |||
| 184 | pub const Body = struct { | ||
| 185 | instructions: []*Inst, | ||
| 186 | }; | ||
| 187 | |||
| 188 | pub fn deinit(self: *Module, allocator: *Allocator) void { | ||
| 189 | allocator.free(self.exports); | ||
| 190 | allocator.free(self.errors); | ||
| 191 | for (self.fns) |f| { | ||
| 192 | allocator.free(f.body.instructions); | ||
| 193 | } | ||
| 194 | allocator.free(self.fns); | ||
| 195 | self.arena.deinit(); | ||
| 196 | self.* = undefined; | ||
| 197 | } | ||
| 198 | }; | ||
| 199 | |||
| 200 | pub const ErrorMsg = struct { | ||
| 201 | byte_offset: usize, | ||
| 202 | msg: []const u8, | ||
| 203 | }; | ||
| 204 | |||
| 205 | pub const AnalyzeOptions = struct { | ||
| 206 | target: Target, | ||
| 207 | output_mode: std.builtin.OutputMode, | ||
| 208 | link_mode: std.builtin.LinkMode, | ||
| 209 | object_format: ?std.Target.ObjectFormat = null, | ||
| 210 | optimize_mode: std.builtin.Mode, | ||
| 211 | }; | ||
| 212 | |||
| 213 | pub fn analyze(allocator: *Allocator, old_module: text.Module, options: AnalyzeOptions) !Module { | ||
| 214 | var ctx = Analyze{ | ||
| 215 | .allocator = allocator, | ||
| 216 | .arena = std.heap.ArenaAllocator.init(allocator), | ||
| 217 | .old_module = &old_module, | ||
| 218 | .errors = std.ArrayList(ErrorMsg).init(allocator), | ||
| 219 | .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator), | ||
| 220 | .exports = std.ArrayList(Module.Export).init(allocator), | ||
| 221 | .fns = std.ArrayList(Module.Fn).init(allocator), | ||
| 222 | .target = options.target, | ||
| 223 | .optimize_mode = options.optimize_mode, | ||
| 224 | .link_mode = options.link_mode, | ||
| 225 | .output_mode = options.output_mode, | ||
| 226 | }; | ||
| 227 | defer ctx.errors.deinit(); | ||
| 228 | defer ctx.decl_table.deinit(); | ||
| 229 | defer ctx.exports.deinit(); | ||
| 230 | defer ctx.fns.deinit(); | ||
| 231 | errdefer ctx.arena.deinit(); | ||
| 232 | |||
| 233 | ctx.analyzeRoot() catch |err| switch (err) { | ||
| 234 | error.AnalysisFail => { | ||
| 235 | assert(ctx.errors.items.len != 0); | ||
| 236 | }, | ||
| 237 | else => |e| return e, | ||
| 238 | }; | ||
| 239 | return Module{ | ||
| 240 | .exports = ctx.exports.toOwnedSlice(), | ||
| 241 | .errors = ctx.errors.toOwnedSlice(), | ||
| 242 | .fns = ctx.fns.toOwnedSlice(), | ||
| 243 | .arena = ctx.arena, | ||
| 244 | .target = ctx.target, | ||
| 245 | .link_mode = ctx.link_mode, | ||
| 246 | .output_mode = ctx.output_mode, | ||
| 247 | .object_format = options.object_format orelse ctx.target.getObjectFormat(), | ||
| 248 | .optimize_mode = ctx.optimize_mode, | ||
| 249 | }; | ||
| 250 | } | ||
| 251 | |||
| 252 | const Analyze = struct { | ||
| 253 | allocator: *Allocator, | ||
| 254 | arena: std.heap.ArenaAllocator, | ||
| 255 | old_module: *const text.Module, | ||
| 256 | errors: std.ArrayList(ErrorMsg), | ||
| 257 | decl_table: std.AutoHashMap(*text.Inst, NewDecl), | ||
| 258 | exports: std.ArrayList(Module.Export), | ||
| 259 | fns: std.ArrayList(Module.Fn), | ||
| 260 | target: Target, | ||
| 261 | link_mode: std.builtin.LinkMode, | ||
| 262 | optimize_mode: std.builtin.Mode, | ||
| 263 | output_mode: std.builtin.OutputMode, | ||
| 264 | |||
| 265 | const NewDecl = struct { | ||
| 266 | /// null means a semantic analysis error happened | ||
| 267 | ptr: ?*Inst, | ||
| 268 | }; | ||
| 269 | |||
| 270 | const NewInst = struct { | ||
| 271 | /// null means a semantic analysis error happened | ||
| 272 | ptr: ?*Inst, | ||
| 273 | }; | ||
| 274 | |||
| 275 | const Fn = struct { | ||
| 276 | /// Index into Module fns array | ||
| 277 | fn_index: usize, | ||
| 278 | inner_block: Block, | ||
| 279 | inst_table: std.AutoHashMap(*text.Inst, NewInst), | ||
| 280 | }; | ||
| 281 | |||
| 282 | const Block = struct { | ||
| 283 | func: *Fn, | ||
| 284 | instructions: std.ArrayList(*Inst), | ||
| 285 | }; | ||
| 286 | |||
| 287 | const InnerError = error{ OutOfMemory, AnalysisFail }; | ||
| 288 | |||
| 289 | fn analyzeRoot(self: *Analyze) !void { | ||
| 290 | for (self.old_module.decls) |decl| { | ||
| 291 | if (decl.cast(text.Inst.Export)) |export_inst| { | ||
| 292 | try analyzeExport(self, null, export_inst); | ||
| 293 | } | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | fn resolveInst(self: *Analyze, opt_block: ?*Block, old_inst: *text.Inst) InnerError!*Inst { | ||
| 298 | if (opt_block) |block| { | ||
| 299 | if (block.func.inst_table.get(old_inst)) |kv| { | ||
| 300 | return kv.value.ptr orelse return error.AnalysisFail; | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | if (self.decl_table.get(old_inst)) |kv| { | ||
| 305 | return kv.value.ptr orelse return error.AnalysisFail; | ||
| 306 | } else { | ||
| 307 | const new_inst = self.analyzeInst(null, old_inst) catch |err| switch (err) { | ||
| 308 | error.AnalysisFail => { | ||
| 309 | try self.decl_table.putNoClobber(old_inst, .{ .ptr = null }); | ||
| 310 | return error.AnalysisFail; | ||
| 311 | }, | ||
| 312 | else => |e| return e, | ||
| 313 | }; | ||
| 314 | try self.decl_table.putNoClobber(old_inst, .{ .ptr = new_inst }); | ||
| 315 | return new_inst; | ||
| 316 | } | ||
| 317 | } | ||
| 318 | |||
| 319 | fn requireRuntimeBlock(self: *Analyze, block: ?*Block, src: usize) !*Block { | ||
| 320 | return block orelse return self.fail(src, "instruction illegal outside function body", .{}); | ||
| 321 | } | ||
| 322 | |||
| 323 | fn resolveInstConst(self: *Analyze, block: ?*Block, old_inst: *text.Inst) InnerError!TypedValue { | ||
| 324 | const new_inst = try self.resolveInst(block, old_inst); | ||
| 325 | const val = try self.resolveConstValue(new_inst); | ||
| 326 | return TypedValue{ | ||
| 327 | .ty = new_inst.ty, | ||
| 328 | .val = val, | ||
| 329 | }; | ||
| 330 | } | ||
| 331 | |||
| 332 | fn resolveConstValue(self: *Analyze, base: *Inst) !Value { | ||
| 333 | return (try self.resolveDefinedValue(base)) orelse | ||
| 334 | return self.fail(base.src, "unable to resolve comptime value", .{}); | ||
| 335 | } | ||
| 336 | |||
| 337 | fn resolveDefinedValue(self: *Analyze, base: *Inst) !?Value { | ||
| 338 | if (base.value()) |val| { | ||
| 339 | if (val.isUndef()) { | ||
| 340 | return self.fail(base.src, "use of undefined value here causes undefined behavior", .{}); | ||
| 341 | } | ||
| 342 | return val; | ||
| 343 | } | ||
| 344 | return null; | ||
| 345 | } | ||
| 346 | |||
| 347 | fn resolveConstString(self: *Analyze, block: ?*Block, old_inst: *text.Inst) ![]u8 { | ||
| 348 | const new_inst = try self.resolveInst(block, old_inst); | ||
| 349 | const wanted_type = Type.initTag(.const_slice_u8); | ||
| 350 | const coerced_inst = try self.coerce(block, wanted_type, new_inst); | ||
| 351 | const val = try self.resolveConstValue(coerced_inst); | ||
| 352 | return val.toAllocatedBytes(&self.arena.allocator); | ||
| 353 | } | ||
| 354 | |||
| 355 | fn resolveType(self: *Analyze, block: ?*Block, old_inst: *text.Inst) !Type { | ||
| 356 | const new_inst = try self.resolveInst(block, old_inst); | ||
| 357 | const wanted_type = Type.initTag(.@"type"); | ||
| 358 | const coerced_inst = try self.coerce(block, wanted_type, new_inst); | ||
| 359 | const val = try self.resolveConstValue(coerced_inst); | ||
| 360 | return val.toType(); | ||
| 361 | } | ||
| 362 | |||
| 363 | fn analyzeExport(self: *Analyze, block: ?*Block, export_inst: *text.Inst.Export) !void { | ||
| 364 | const symbol_name = try self.resolveConstString(block, export_inst.positionals.symbol_name); | ||
| 365 | const typed_value = try self.resolveInstConst(block, export_inst.positionals.value); | ||
| 366 | |||
| 367 | switch (typed_value.ty.zigTypeTag()) { | ||
| 368 | .Fn => {}, | ||
| 369 | else => return self.fail( | ||
| 370 | export_inst.positionals.value.src, | ||
| 371 | "unable to export type '{}'", | ||
| 372 | .{typed_value.ty}, | ||
| 373 | ), | ||
| 374 | } | ||
| 375 | try self.exports.append(.{ | ||
| 376 | .name = symbol_name, | ||
| 377 | .typed_value = typed_value, | ||
| 378 | .src = export_inst.base.src, | ||
| 379 | }); | ||
| 380 | } | ||
| 381 | |||
| 382 | /// TODO should not need the cast on the last parameter at the callsites | ||
| 383 | fn addNewInstArgs( | ||
| 384 | self: *Analyze, | ||
| 385 | block: *Block, | ||
| 386 | src: usize, | ||
| 387 | ty: Type, | ||
| 388 | comptime T: type, | ||
| 389 | args: Inst.Args(T), | ||
| 390 | ) !*Inst { | ||
| 391 | const inst = try self.addNewInst(block, src, ty, T); | ||
| 392 | inst.args = args; | ||
| 393 | return &inst.base; | ||
| 394 | } | ||
| 395 | |||
| 396 | fn addNewInst(self: *Analyze, block: *Block, src: usize, ty: Type, comptime T: type) !*T { | ||
| 397 | const inst = try self.arena.allocator.create(T); | ||
| 398 | inst.* = .{ | ||
| 399 | .base = .{ | ||
| 400 | .tag = T.base_tag, | ||
| 401 | .ty = ty, | ||
| 402 | .src = src, | ||
| 403 | }, | ||
| 404 | .args = undefined, | ||
| 405 | }; | ||
| 406 | try block.instructions.append(&inst.base); | ||
| 407 | return inst; | ||
| 408 | } | ||
| 409 | |||
| 410 | fn constInst(self: *Analyze, src: usize, typed_value: TypedValue) !*Inst { | ||
| 411 | const const_inst = try self.arena.allocator.create(Inst.Constant); | ||
| 412 | const_inst.* = .{ | ||
| 413 | .base = .{ | ||
| 414 | .tag = Inst.Constant.base_tag, | ||
| 415 | .ty = typed_value.ty, | ||
| 416 | .src = src, | ||
| 417 | }, | ||
| 418 | .val = typed_value.val, | ||
| 419 | }; | ||
| 420 | return &const_inst.base; | ||
| 421 | } | ||
| 422 | |||
| 423 | fn constStr(self: *Analyze, src: usize, str: []const u8) !*Inst { | ||
| 424 | const array_payload = try self.arena.allocator.create(Type.Payload.Array_u8_Sentinel0); | ||
| 425 | array_payload.* = .{ .len = str.len }; | ||
| 426 | |||
| 427 | const ty_payload = try self.arena.allocator.create(Type.Payload.SingleConstPointer); | ||
| 428 | ty_payload.* = .{ .pointee_type = Type.initPayload(&array_payload.base) }; | ||
| 429 | |||
| 430 | const bytes_payload = try self.arena.allocator.create(Value.Payload.Bytes); | ||
| 431 | bytes_payload.* = .{ .data = str }; | ||
| 432 | |||
| 433 | return self.constInst(src, .{ | ||
| 434 | .ty = Type.initPayload(&ty_payload.base), | ||
| 435 | .val = Value.initPayload(&bytes_payload.base), | ||
| 436 | }); | ||
| 437 | } | ||
| 438 | |||
| 439 | fn constType(self: *Analyze, src: usize, ty: Type) !*Inst { | ||
| 440 | return self.constInst(src, .{ | ||
| 441 | .ty = Type.initTag(.type), | ||
| 442 | .val = try ty.toValue(&self.arena.allocator), | ||
| 443 | }); | ||
| 444 | } | ||
| 445 | |||
| 446 | fn constVoid(self: *Analyze, src: usize) !*Inst { | ||
| 447 | return self.constInst(src, .{ | ||
| 448 | .ty = Type.initTag(.void), | ||
| 449 | .val = Value.initTag(.the_one_possible_value), | ||
| 450 | }); | ||
| 451 | } | ||
| 452 | |||
| 453 | fn constUndef(self: *Analyze, src: usize, ty: Type) !*Inst { | ||
| 454 | return self.constInst(src, .{ | ||
| 455 | .ty = ty, | ||
| 456 | .val = Value.initTag(.undef), | ||
| 457 | }); | ||
| 458 | } | ||
| 459 | |||
| 460 | fn constBool(self: *Analyze, src: usize, v: bool) !*Inst { | ||
| 461 | return self.constInst(src, .{ | ||
| 462 | .ty = Type.initTag(.bool), | ||
| 463 | .val = ([2]Value{ Value.initTag(.bool_false), Value.initTag(.bool_true) })[@boolToInt(v)], | ||
| 464 | }); | ||
| 465 | } | ||
| 466 | |||
| 467 | fn constIntUnsigned(self: *Analyze, src: usize, ty: Type, int: u64) !*Inst { | ||
| 468 | const int_payload = try self.arena.allocator.create(Value.Payload.Int_u64); | ||
| 469 | int_payload.* = .{ .int = int }; | ||
| 470 | |||
| 471 | return self.constInst(src, .{ | ||
| 472 | .ty = ty, | ||
| 473 | .val = Value.initPayload(&int_payload.base), | ||
| 474 | }); | ||
| 475 | } | ||
| 476 | |||
| 477 | fn constIntSigned(self: *Analyze, src: usize, ty: Type, int: i64) !*Inst { | ||
| 478 | const int_payload = try self.arena.allocator.create(Value.Payload.Int_i64); | ||
| 479 | int_payload.* = .{ .int = int }; | ||
| 480 | |||
| 481 | return self.constInst(src, .{ | ||
| 482 | .ty = ty, | ||
| 483 | .val = Value.initPayload(&int_payload.base), | ||
| 484 | }); | ||
| 485 | } | ||
| 486 | |||
| 487 | fn constIntBig(self: *Analyze, src: usize, ty: Type, big_int: BigIntConst) !*Inst { | ||
| 488 | const val_payload = if (big_int.positive) blk: { | ||
| 489 | if (big_int.to(u64)) |x| { | ||
| 490 | return self.constIntUnsigned(src, ty, x); | ||
| 491 | } else |err| switch (err) { | ||
| 492 | error.NegativeIntoUnsigned => unreachable, | ||
| 493 | error.TargetTooSmall => {}, // handled below | ||
| 494 | } | ||
| 495 | const big_int_payload = try self.arena.allocator.create(Value.Payload.IntBigPositive); | ||
| 496 | big_int_payload.* = .{ .limbs = big_int.limbs }; | ||
| 497 | break :blk &big_int_payload.base; | ||
| 498 | } else blk: { | ||
| 499 | if (big_int.to(i64)) |x| { | ||
| 500 | return self.constIntSigned(src, ty, x); | ||
| 501 | } else |err| switch (err) { | ||
| 502 | error.NegativeIntoUnsigned => unreachable, | ||
| 503 | error.TargetTooSmall => {}, // handled below | ||
| 504 | } | ||
| 505 | const big_int_payload = try self.arena.allocator.create(Value.Payload.IntBigNegative); | ||
| 506 | big_int_payload.* = .{ .limbs = big_int.limbs }; | ||
| 507 | break :blk &big_int_payload.base; | ||
| 508 | }; | ||
| 509 | |||
| 510 | return self.constInst(src, .{ | ||
| 511 | .ty = ty, | ||
| 512 | .val = Value.initPayload(val_payload), | ||
| 513 | }); | ||
| 514 | } | ||
| 515 | |||
| 516 | fn analyzeInst(self: *Analyze, block: ?*Block, old_inst: *text.Inst) InnerError!*Inst { | ||
| 517 | switch (old_inst.tag) { | ||
| 518 | .breakpoint => return self.analyzeInstBreakpoint(block, old_inst.cast(text.Inst.Breakpoint).?), | ||
| 519 | .str => { | ||
| 520 | // We can use this reference because Inst.Const's Value is arena-allocated. | ||
| 521 | // The value would get copied to a MemoryCell before the `text.Inst.Str` lifetime ends. | ||
| 522 | const bytes = old_inst.cast(text.Inst.Str).?.positionals.bytes; | ||
| 523 | return self.constStr(old_inst.src, bytes); | ||
| 524 | }, | ||
| 525 | .int => { | ||
| 526 | const big_int = old_inst.cast(text.Inst.Int).?.positionals.int; | ||
| 527 | return self.constIntBig(old_inst.src, Type.initTag(.comptime_int), big_int); | ||
| 528 | }, | ||
| 529 | .ptrtoint => return self.analyzeInstPtrToInt(block, old_inst.cast(text.Inst.PtrToInt).?), | ||
| 530 | .fieldptr => return self.analyzeInstFieldPtr(block, old_inst.cast(text.Inst.FieldPtr).?), | ||
| 531 | .deref => return self.analyzeInstDeref(block, old_inst.cast(text.Inst.Deref).?), | ||
| 532 | .as => return self.analyzeInstAs(block, old_inst.cast(text.Inst.As).?), | ||
| 533 | .@"asm" => return self.analyzeInstAsm(block, old_inst.cast(text.Inst.Asm).?), | ||
| 534 | .@"unreachable" => return self.analyzeInstUnreachable(block, old_inst.cast(text.Inst.Unreachable).?), | ||
| 535 | .@"return" => return self.analyzeInstRet(block, old_inst.cast(text.Inst.Return).?), | ||
| 536 | .@"fn" => return self.analyzeInstFn(block, old_inst.cast(text.Inst.Fn).?), | ||
| 537 | .@"export" => { | ||
| 538 | try self.analyzeExport(block, old_inst.cast(text.Inst.Export).?); | ||
| 539 | return self.constVoid(old_inst.src); | ||
| 540 | }, | ||
| 541 | .primitive => return self.analyzeInstPrimitive(old_inst.cast(text.Inst.Primitive).?), | ||
| 542 | .fntype => return self.analyzeInstFnType(block, old_inst.cast(text.Inst.FnType).?), | ||
| 543 | .intcast => return self.analyzeInstIntCast(block, old_inst.cast(text.Inst.IntCast).?), | ||
| 544 | .bitcast => return self.analyzeInstBitCast(block, old_inst.cast(text.Inst.BitCast).?), | ||
| 545 | .elemptr => return self.analyzeInstElemPtr(block, old_inst.cast(text.Inst.ElemPtr).?), | ||
| 546 | .add => return self.analyzeInstAdd(block, old_inst.cast(text.Inst.Add).?), | ||
| 547 | .cmp => return self.analyzeInstCmp(block, old_inst.cast(text.Inst.Cmp).?), | ||
| 548 | .condbr => return self.analyzeInstCondBr(block, old_inst.cast(text.Inst.CondBr).?), | ||
| 549 | .isnull => return self.analyzeInstIsNull(block, old_inst.cast(text.Inst.IsNull).?), | ||
| 550 | .isnonnull => return self.analyzeInstIsNonNull(block, old_inst.cast(text.Inst.IsNonNull).?), | ||
| 551 | } | ||
| 552 | } | ||
| 553 | |||
| 554 | fn analyzeInstBreakpoint(self: *Analyze, block: ?*Block, inst: *text.Inst.Breakpoint) InnerError!*Inst { | ||
| 555 | const b = try self.requireRuntimeBlock(block, inst.base.src); | ||
| 556 | return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, Inst.Args(Inst.Breakpoint){}); | ||
| 557 | } | ||
| 558 | |||
| 559 | fn analyzeInstFn(self: *Analyze, block: ?*Block, fn_inst: *text.Inst.Fn) InnerError!*Inst { | ||
| 560 | const fn_type = try self.resolveType(block, fn_inst.positionals.fn_type); | ||
| 561 | |||
| 562 | var new_func: Fn = .{ | ||
| 563 | .fn_index = self.fns.items.len, | ||
| 564 | .inner_block = .{ | ||
| 565 | .func = undefined, | ||
| 566 | .instructions = std.ArrayList(*Inst).init(self.allocator), | ||
| 567 | }, | ||
| 568 | .inst_table = std.AutoHashMap(*text.Inst, NewInst).init(self.allocator), | ||
| 569 | }; | ||
| 570 | new_func.inner_block.func = &new_func; | ||
| 571 | defer new_func.inner_block.instructions.deinit(); | ||
| 572 | defer new_func.inst_table.deinit(); | ||
| 573 | // Don't hang on to a reference to this when analyzing body instructions, since the memory | ||
| 574 | // could become invalid. | ||
| 575 | (try self.fns.addOne()).* = .{ | ||
| 576 | .analysis_status = .in_progress, | ||
| 577 | .fn_type = fn_type, | ||
| 578 | .body = undefined, | ||
| 579 | }; | ||
| 580 | |||
| 581 | try self.analyzeBody(&new_func.inner_block, fn_inst.positionals.body); | ||
| 582 | |||
| 583 | const f = &self.fns.items[new_func.fn_index]; | ||
| 584 | f.analysis_status = .success; | ||
| 585 | f.body = .{ .instructions = new_func.inner_block.instructions.toOwnedSlice() }; | ||
| 586 | |||
| 587 | const fn_payload = try self.arena.allocator.create(Value.Payload.Function); | ||
| 588 | fn_payload.* = .{ .index = new_func.fn_index }; | ||
| 589 | |||
| 590 | return self.constInst(fn_inst.base.src, .{ | ||
| 591 | .ty = fn_type, | ||
| 592 | .val = Value.initPayload(&fn_payload.base), | ||
| 593 | }); | ||
| 594 | } | ||
| 595 | |||
| 596 | fn analyzeInstFnType(self: *Analyze, block: ?*Block, fntype: *text.Inst.FnType) InnerError!*Inst { | ||
| 597 | const return_type = try self.resolveType(block, fntype.positionals.return_type); | ||
| 598 | |||
| 599 | if (return_type.zigTypeTag() == .NoReturn and | ||
| 600 | fntype.positionals.param_types.len == 0 and | ||
| 601 | fntype.kw_args.cc == .Naked) | ||
| 602 | { | ||
| 603 | return self.constType(fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args)); | ||
| 604 | } | ||
| 605 | |||
| 606 | if (return_type.zigTypeTag() == .Void and | ||
| 607 | fntype.positionals.param_types.len == 0 and | ||
| 608 | fntype.kw_args.cc == .C) | ||
| 609 | { | ||
| 610 | return self.constType(fntype.base.src, Type.initTag(.fn_ccc_void_no_args)); | ||
| 611 | } | ||
| 612 | |||
| 613 | return self.fail(fntype.base.src, "TODO implement fntype instruction more", .{}); | ||
| 614 | } | ||
| 615 | |||
| 616 | fn analyzeInstPrimitive(self: *Analyze, primitive: *text.Inst.Primitive) InnerError!*Inst { | ||
| 617 | return self.constType(primitive.base.src, primitive.positionals.tag.toType()); | ||
| 618 | } | ||
| 619 | |||
| 620 | fn analyzeInstAs(self: *Analyze, block: ?*Block, as: *text.Inst.As) InnerError!*Inst { | ||
| 621 | const dest_type = try self.resolveType(block, as.positionals.dest_type); | ||
| 622 | const new_inst = try self.resolveInst(block, as.positionals.value); | ||
| 623 | return self.coerce(block, dest_type, new_inst); | ||
| 624 | } | ||
| 625 | |||
| 626 | fn analyzeInstPtrToInt(self: *Analyze, block: ?*Block, ptrtoint: *text.Inst.PtrToInt) InnerError!*Inst { | ||
| 627 | const ptr = try self.resolveInst(block, ptrtoint.positionals.ptr); | ||
| 628 | if (ptr.ty.zigTypeTag() != .Pointer) { | ||
| 629 | return self.fail(ptrtoint.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty}); | ||
| 630 | } | ||
| 631 | // TODO handle known-pointer-address | ||
| 632 | const b = try self.requireRuntimeBlock(block, ptrtoint.base.src); | ||
| 633 | const ty = Type.initTag(.usize); | ||
| 634 | return self.addNewInstArgs(b, ptrtoint.base.src, ty, Inst.PtrToInt, Inst.Args(Inst.PtrToInt){ .ptr = ptr }); | ||
| 635 | } | ||
| 636 | |||
| 637 | fn analyzeInstFieldPtr(self: *Analyze, block: ?*Block, fieldptr: *text.Inst.FieldPtr) InnerError!*Inst { | ||
| 638 | const object_ptr = try self.resolveInst(block, fieldptr.positionals.object_ptr); | ||
| 639 | const field_name = try self.resolveConstString(block, fieldptr.positionals.field_name); | ||
| 640 | |||
| 641 | const elem_ty = switch (object_ptr.ty.zigTypeTag()) { | ||
| 642 | .Pointer => object_ptr.ty.elemType(), | ||
| 643 | else => return self.fail(fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}), | ||
| 644 | }; | ||
| 645 | switch (elem_ty.zigTypeTag()) { | ||
| 646 | .Array => { | ||
| 647 | if (mem.eql(u8, field_name, "len")) { | ||
| 648 | const len_payload = try self.arena.allocator.create(Value.Payload.Int_u64); | ||
| 649 | len_payload.* = .{ .int = elem_ty.arrayLen() }; | ||
| 650 | |||
| 651 | const ref_payload = try self.arena.allocator.create(Value.Payload.RefVal); | ||
| 652 | ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) }; | ||
| 653 | |||
| 654 | return self.constInst(fieldptr.base.src, .{ | ||
| 655 | .ty = Type.initTag(.single_const_pointer_to_comptime_int), | ||
| 656 | .val = Value.initPayload(&ref_payload.base), | ||
| 657 | }); | ||
| 658 | } else { | ||
| 659 | return self.fail( | ||
| 660 | fieldptr.positionals.field_name.src, | ||
| 661 | "no member named '{}' in '{}'", | ||
| 662 | .{ field_name, elem_ty }, | ||
| 663 | ); | ||
| 664 | } | ||
| 665 | }, | ||
| 666 | else => return self.fail(fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}), | ||
| 667 | } | ||
| 668 | } | ||
| 669 | |||
| 670 | fn analyzeInstIntCast(self: *Analyze, block: ?*Block, intcast: *text.Inst.IntCast) InnerError!*Inst { | ||
| 671 | const dest_type = try self.resolveType(block, intcast.positionals.dest_type); | ||
| 672 | const new_inst = try self.resolveInst(block, intcast.positionals.value); | ||
| 673 | |||
| 674 | const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { | ||
| 675 | .ComptimeInt => true, | ||
| 676 | .Int => false, | ||
| 677 | else => return self.fail( | ||
| 678 | intcast.positionals.dest_type.src, | ||
| 679 | "expected integer type, found '{}'", | ||
| 680 | .{ | ||
| 681 | dest_type, | ||
| 682 | }, | ||
| 683 | ), | ||
| 684 | }; | ||
| 685 | |||
| 686 | switch (new_inst.ty.zigTypeTag()) { | ||
| 687 | .ComptimeInt, .Int => {}, | ||
| 688 | else => return self.fail( | ||
| 689 | intcast.positionals.value.src, | ||
| 690 | "expected integer type, found '{}'", | ||
| 691 | .{new_inst.ty}, | ||
| 692 | ), | ||
| 693 | } | ||
| 694 | |||
| 695 | if (dest_is_comptime_int or new_inst.value() != null) { | ||
| 696 | return self.coerce(block, dest_type, new_inst); | ||
| 697 | } | ||
| 698 | |||
| 699 | return self.fail(intcast.base.src, "TODO implement analyze widen or shorten int", .{}); | ||
| 700 | } | ||
| 701 | |||
| 702 | fn analyzeInstBitCast(self: *Analyze, block: ?*Block, inst: *text.Inst.BitCast) InnerError!*Inst { | ||
| 703 | const dest_type = try self.resolveType(block, inst.positionals.dest_type); | ||
| 704 | const operand = try self.resolveInst(block, inst.positionals.operand); | ||
| 705 | return self.bitcast(block, dest_type, operand); | ||
| 706 | } | ||
| 707 | |||
| 708 | fn analyzeInstElemPtr(self: *Analyze, block: ?*Block, inst: *text.Inst.ElemPtr) InnerError!*Inst { | ||
| 709 | const array_ptr = try self.resolveInst(block, inst.positionals.array_ptr); | ||
| 710 | const uncasted_index = try self.resolveInst(block, inst.positionals.index); | ||
| 711 | const elem_index = try self.coerce(block, Type.initTag(.usize), uncasted_index); | ||
| 712 | |||
| 713 | if (array_ptr.ty.isSinglePointer() and array_ptr.ty.elemType().zigTypeTag() == .Array) { | ||
| 714 | if (array_ptr.value()) |array_ptr_val| { | ||
| 715 | if (elem_index.value()) |index_val| { | ||
| 716 | // Both array pointer and index are compile-time known. | ||
| 717 | const index_u64 = index_val.toUnsignedInt(); | ||
| 718 | // @intCast here because it would have been impossible to construct a value that | ||
| 719 | // required a larger index. | ||
| 720 | const elem_val = try array_ptr_val.elemValueAt(&self.arena.allocator, @intCast(usize, index_u64)); | ||
| 721 | |||
| 722 | const ref_payload = try self.arena.allocator.create(Value.Payload.RefVal); | ||
| 723 | ref_payload.* = .{ .val = elem_val }; | ||
| 724 | |||
| 725 | const type_payload = try self.arena.allocator.create(Type.Payload.SingleConstPointer); | ||
| 726 | type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() }; | ||
| 727 | |||
| 728 | return self.constInst(inst.base.src, .{ | ||
| 729 | .ty = Type.initPayload(&type_payload.base), | ||
| 730 | .val = Value.initPayload(&ref_payload.base), | ||
| 731 | }); | ||
| 732 | } | ||
| 733 | } | ||
| 734 | } | ||
| 735 | |||
| 736 | return self.fail(inst.base.src, "TODO implement more analyze elemptr", .{}); | ||
| 737 | } | ||
| 738 | |||
| 739 | fn analyzeInstAdd(self: *Analyze, block: ?*Block, inst: *text.Inst.Add) InnerError!*Inst { | ||
| 740 | const lhs = try self.resolveInst(block, inst.positionals.lhs); | ||
| 741 | const rhs = try self.resolveInst(block, inst.positionals.rhs); | ||
| 742 | |||
| 743 | if (lhs.ty.zigTypeTag() == .Int and rhs.ty.zigTypeTag() == .Int) { | ||
| 744 | if (lhs.value()) |lhs_val| { | ||
| 745 | if (rhs.value()) |rhs_val| { | ||
| 746 | // TODO is this a performance issue? maybe we should try the operation without | ||
| 747 | // resorting to BigInt first. | ||
| 748 | var lhs_space: Value.BigIntSpace = undefined; | ||
| 749 | var rhs_space: Value.BigIntSpace = undefined; | ||
| 750 | const lhs_bigint = lhs_val.toBigInt(&lhs_space); | ||
| 751 | const rhs_bigint = rhs_val.toBigInt(&rhs_space); | ||
| 752 | const limbs = try self.arena.allocator.alloc( | ||
| 753 | std.math.big.Limb, | ||
| 754 | std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, | ||
| 755 | ); | ||
| 756 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | ||
| 757 | result_bigint.add(lhs_bigint, rhs_bigint); | ||
| 758 | const result_limbs = result_bigint.limbs[0..result_bigint.len]; | ||
| 759 | |||
| 760 | if (!lhs.ty.eql(rhs.ty)) { | ||
| 761 | return self.fail(inst.base.src, "TODO implement peer type resolution", .{}); | ||
| 762 | } | ||
| 763 | |||
| 764 | const val_payload = if (result_bigint.positive) blk: { | ||
| 765 | const val_payload = try self.arena.allocator.create(Value.Payload.IntBigPositive); | ||
| 766 | val_payload.* = .{ .limbs = result_limbs }; | ||
| 767 | break :blk &val_payload.base; | ||
| 768 | } else blk: { | ||
| 769 | const val_payload = try self.arena.allocator.create(Value.Payload.IntBigNegative); | ||
| 770 | val_payload.* = .{ .limbs = result_limbs }; | ||
| 771 | break :blk &val_payload.base; | ||
| 772 | }; | ||
| 773 | |||
| 774 | return self.constInst(inst.base.src, .{ | ||
| 775 | .ty = lhs.ty, | ||
| 776 | .val = Value.initPayload(val_payload), | ||
| 777 | }); | ||
| 778 | } | ||
| 779 | } | ||
| 780 | } | ||
| 781 | |||
| 782 | return self.fail(inst.base.src, "TODO implement more analyze add", .{}); | ||
| 783 | } | ||
| 784 | |||
| 785 | fn analyzeInstDeref(self: *Analyze, block: ?*Block, deref: *text.Inst.Deref) InnerError!*Inst { | ||
| 786 | const ptr = try self.resolveInst(block, deref.positionals.ptr); | ||
| 787 | const elem_ty = switch (ptr.ty.zigTypeTag()) { | ||
| 788 | .Pointer => ptr.ty.elemType(), | ||
| 789 | else => return self.fail(deref.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty}), | ||
| 790 | }; | ||
| 791 | if (ptr.value()) |val| { | ||
| 792 | return self.constInst(deref.base.src, .{ | ||
| 793 | .ty = elem_ty, | ||
| 794 | .val = val.pointerDeref(), | ||
| 795 | }); | ||
| 796 | } | ||
| 797 | |||
| 798 | return self.fail(deref.base.src, "TODO implement runtime deref", .{}); | ||
| 799 | } | ||
| 800 | |||
| 801 | fn analyzeInstAsm(self: *Analyze, block: ?*Block, assembly: *text.Inst.Asm) InnerError!*Inst { | ||
| 802 | const return_type = try self.resolveType(block, assembly.positionals.return_type); | ||
| 803 | const asm_source = try self.resolveConstString(block, assembly.positionals.asm_source); | ||
| 804 | const output = if (assembly.kw_args.output) |o| try self.resolveConstString(block, o) else null; | ||
| 805 | |||
| 806 | const inputs = try self.arena.allocator.alloc([]const u8, assembly.kw_args.inputs.len); | ||
| 807 | const clobbers = try self.arena.allocator.alloc([]const u8, assembly.kw_args.clobbers.len); | ||
| 808 | const args = try self.arena.allocator.alloc(*Inst, assembly.kw_args.args.len); | ||
| 809 | |||
| 810 | for (inputs) |*elem, i| { | ||
| 811 | elem.* = try self.resolveConstString(block, assembly.kw_args.inputs[i]); | ||
| 812 | } | ||
| 813 | for (clobbers) |*elem, i| { | ||
| 814 | elem.* = try self.resolveConstString(block, assembly.kw_args.clobbers[i]); | ||
| 815 | } | ||
| 816 | for (args) |*elem, i| { | ||
| 817 | const arg = try self.resolveInst(block, assembly.kw_args.args[i]); | ||
| 818 | elem.* = try self.coerce(block, Type.initTag(.usize), arg); | ||
| 819 | } | ||
| 820 | |||
| 821 | const b = try self.requireRuntimeBlock(block, assembly.base.src); | ||
| 822 | return self.addNewInstArgs(b, assembly.base.src, return_type, Inst.Assembly, Inst.Args(Inst.Assembly){ | ||
| 823 | .asm_source = asm_source, | ||
| 824 | .is_volatile = assembly.kw_args.@"volatile", | ||
| 825 | .output = output, | ||
| 826 | .inputs = inputs, | ||
| 827 | .clobbers = clobbers, | ||
| 828 | .args = args, | ||
| 829 | }); | ||
| 830 | } | ||
| 831 | |||
| 832 | fn analyzeInstCmp(self: *Analyze, block: ?*Block, inst: *text.Inst.Cmp) InnerError!*Inst { | ||
| 833 | const lhs = try self.resolveInst(block, inst.positionals.lhs); | ||
| 834 | const rhs = try self.resolveInst(block, inst.positionals.rhs); | ||
| 835 | const op = inst.positionals.op; | ||
| 836 | |||
| 837 | const is_equality_cmp = switch (op) { | ||
| 838 | .eq, .neq => true, | ||
| 839 | else => false, | ||
| 840 | }; | ||
| 841 | const lhs_ty_tag = lhs.ty.zigTypeTag(); | ||
| 842 | const rhs_ty_tag = rhs.ty.zigTypeTag(); | ||
| 843 | if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) { | ||
| 844 | // null == null, null != null | ||
| 845 | return self.constBool(inst.base.src, op == .eq); | ||
| 846 | } else if (is_equality_cmp and | ||
| 847 | ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or | ||
| 848 | rhs_ty_tag == .Null and lhs_ty_tag == .Optional)) | ||
| 849 | { | ||
| 850 | // comparing null with optionals | ||
| 851 | const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs; | ||
| 852 | if (opt_operand.value()) |opt_val| { | ||
| 853 | const is_null = opt_val.isNull(); | ||
| 854 | return self.constBool(inst.base.src, if (op == .eq) is_null else !is_null); | ||
| 855 | } | ||
| 856 | const b = try self.requireRuntimeBlock(block, inst.base.src); | ||
| 857 | switch (op) { | ||
| 858 | .eq => return self.addNewInstArgs( | ||
| 859 | b, | ||
| 860 | inst.base.src, | ||
| 861 | Type.initTag(.bool), | ||
| 862 | Inst.IsNull, | ||
| 863 | Inst.Args(Inst.IsNull){ .operand = opt_operand }, | ||
| 864 | ), | ||
| 865 | .neq => return self.addNewInstArgs( | ||
| 866 | b, | ||
| 867 | inst.base.src, | ||
| 868 | Type.initTag(.bool), | ||
| 869 | Inst.IsNonNull, | ||
| 870 | Inst.Args(Inst.IsNonNull){ .operand = opt_operand }, | ||
| 871 | ), | ||
| 872 | else => unreachable, | ||
| 873 | } | ||
| 874 | } else if (is_equality_cmp and | ||
| 875 | ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr()))) | ||
| 876 | { | ||
| 877 | return self.fail(inst.base.src, "TODO implement C pointer cmp", .{}); | ||
| 878 | } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) { | ||
| 879 | const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty; | ||
| 880 | return self.fail(inst.base.src, "comparison of '{}' with null", .{non_null_type}); | ||
| 881 | } else if (is_equality_cmp and | ||
| 882 | ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or | ||
| 883 | (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union))) | ||
| 884 | { | ||
| 885 | return self.fail(inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); | ||
| 886 | } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { | ||
| 887 | if (!is_equality_cmp) { | ||
| 888 | return self.fail(inst.base.src, "{} operator not allowed for errors", .{@tagName(op)}); | ||
| 889 | } | ||
| 890 | return self.fail(inst.base.src, "TODO implement equality comparison between errors", .{}); | ||
| 891 | } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) { | ||
| 892 | // This operation allows any combination of integer and float types, regardless of the | ||
| 893 | // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for | ||
| 894 | // numeric types. | ||
| 895 | return self.cmpNumeric(block, inst.base.src, lhs, rhs, op); | ||
| 896 | } | ||
| 897 | return self.fail(inst.base.src, "TODO implement more cmp analysis", .{}); | ||
| 898 | } | ||
| 899 | |||
| 900 | fn analyzeInstIsNull(self: *Analyze, block: ?*Block, inst: *text.Inst.IsNull) InnerError!*Inst { | ||
| 901 | const operand = try self.resolveInst(block, inst.positionals.operand); | ||
| 902 | return self.analyzeIsNull(block, inst.base.src, operand, true); | ||
| 903 | } | ||
| 904 | |||
| 905 | fn analyzeInstIsNonNull(self: *Analyze, block: ?*Block, inst: *text.Inst.IsNonNull) InnerError!*Inst { | ||
| 906 | const operand = try self.resolveInst(block, inst.positionals.operand); | ||
| 907 | return self.analyzeIsNull(block, inst.base.src, operand, false); | ||
| 908 | } | ||
| 909 | |||
| 910 | fn analyzeInstCondBr(self: *Analyze, block: ?*Block, inst: *text.Inst.CondBr) InnerError!*Inst { | ||
| 911 | const uncasted_cond = try self.resolveInst(block, inst.positionals.condition); | ||
| 912 | const cond = try self.coerce(block, Type.initTag(.bool), uncasted_cond); | ||
| 913 | |||
| 914 | if (try self.resolveDefinedValue(cond)) |cond_val| { | ||
| 915 | const body = if (cond_val.toBool()) &inst.positionals.true_body else &inst.positionals.false_body; | ||
| 916 | try self.analyzeBody(block, body.*); | ||
| 917 | return self.constVoid(inst.base.src); | ||
| 918 | } | ||
| 919 | |||
| 920 | const parent_block = try self.requireRuntimeBlock(block, inst.base.src); | ||
| 921 | |||
| 922 | var true_block: Block = .{ | ||
| 923 | .func = parent_block.func, | ||
| 924 | .instructions = std.ArrayList(*Inst).init(self.allocator), | ||
| 925 | }; | ||
| 926 | defer true_block.instructions.deinit(); | ||
| 927 | try self.analyzeBody(&true_block, inst.positionals.true_body); | ||
| 928 | |||
| 929 | var false_block: Block = .{ | ||
| 930 | .func = parent_block.func, | ||
| 931 | .instructions = std.ArrayList(*Inst).init(self.allocator), | ||
| 932 | }; | ||
| 933 | defer false_block.instructions.deinit(); | ||
| 934 | try self.analyzeBody(&false_block, inst.positionals.false_body); | ||
| 935 | |||
| 936 | // Copy the instruction pointers to the arena memory | ||
| 937 | const true_instructions = try self.arena.allocator.alloc(*Inst, true_block.instructions.items.len); | ||
| 938 | const false_instructions = try self.arena.allocator.alloc(*Inst, false_block.instructions.items.len); | ||
| 939 | |||
| 940 | mem.copy(*Inst, true_instructions, true_block.instructions.items); | ||
| 941 | mem.copy(*Inst, false_instructions, false_block.instructions.items); | ||
| 942 | |||
| 943 | return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){ | ||
| 944 | .condition = cond, | ||
| 945 | .true_body = .{ .instructions = true_instructions }, | ||
| 946 | .false_body = .{ .instructions = false_instructions }, | ||
| 947 | }); | ||
| 948 | } | ||
| 949 | |||
| 950 | fn wantSafety(self: *Analyze, block: ?*Block) bool { | ||
| 951 | return switch (self.optimize_mode) { | ||
| 952 | .Debug => true, | ||
| 953 | .ReleaseSafe => true, | ||
| 954 | .ReleaseFast => false, | ||
| 955 | .ReleaseSmall => false, | ||
| 956 | }; | ||
| 957 | } | ||
| 958 | |||
| 959 | fn analyzeInstUnreachable(self: *Analyze, block: ?*Block, unreach: *text.Inst.Unreachable) InnerError!*Inst { | ||
| 960 | const b = try self.requireRuntimeBlock(block, unreach.base.src); | ||
| 961 | if (self.wantSafety(block)) { | ||
| 962 | // TODO Once we have a panic function to call, call it here instead of this. | ||
| 963 | _ = try self.addNewInstArgs(b, unreach.base.src, Type.initTag(.void), Inst.Breakpoint, {}); | ||
| 964 | } | ||
| 965 | return self.addNewInstArgs(b, unreach.base.src, Type.initTag(.noreturn), Inst.Unreach, {}); | ||
| 966 | } | ||
| 967 | |||
| 968 | fn analyzeInstRet(self: *Analyze, block: ?*Block, inst: *text.Inst.Return) InnerError!*Inst { | ||
| 969 | const b = try self.requireRuntimeBlock(block, inst.base.src); | ||
| 970 | return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.Ret, {}); | ||
| 971 | } | ||
| 972 | |||
| 973 | fn analyzeBody(self: *Analyze, block: ?*Block, body: text.Module.Body) !void { | ||
| 974 | for (body.instructions) |src_inst| { | ||
| 975 | const new_inst = self.analyzeInst(block, src_inst) catch |err| { | ||
| 976 | if (block) |b| { | ||
| 977 | self.fns.items[b.func.fn_index].analysis_status = .failure; | ||
| 978 | try b.func.inst_table.putNoClobber(src_inst, .{ .ptr = null }); | ||
| 979 | } | ||
| 980 | return err; | ||
| 981 | }; | ||
| 982 | if (block) |b| try b.func.inst_table.putNoClobber(src_inst, .{ .ptr = new_inst }); | ||
| 983 | } | ||
| 984 | } | ||
| 985 | |||
| 986 | fn analyzeIsNull( | ||
| 987 | self: *Analyze, | ||
| 988 | block: ?*Block, | ||
| 989 | src: usize, | ||
| 990 | operand: *Inst, | ||
| 991 | invert_logic: bool, | ||
| 992 | ) InnerError!*Inst { | ||
| 993 | return self.fail(src, "TODO implement analysis of isnull and isnotnull", .{}); | ||
| 994 | } | ||
| 995 | |||
| 996 | /// Asserts that lhs and rhs types are both numeric. | ||
| 997 | fn cmpNumeric( | ||
| 998 | self: *Analyze, | ||
| 999 | block: ?*Block, | ||
| 1000 | src: usize, | ||
| 1001 | lhs: *Inst, | ||
| 1002 | rhs: *Inst, | ||
| 1003 | op: std.math.CompareOperator, | ||
| 1004 | ) !*Inst { | ||
| 1005 | assert(lhs.ty.isNumeric()); | ||
| 1006 | assert(rhs.ty.isNumeric()); | ||
| 1007 | |||
| 1008 | const lhs_ty_tag = lhs.ty.zigTypeTag(); | ||
| 1009 | const rhs_ty_tag = rhs.ty.zigTypeTag(); | ||
| 1010 | |||
| 1011 | if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) { | ||
| 1012 | if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) { | ||
| 1013 | return self.fail(src, "vector length mismatch: {} and {}", .{ | ||
| 1014 | lhs.ty.arrayLen(), | ||
| 1015 | rhs.ty.arrayLen(), | ||
| 1016 | }); | ||
| 1017 | } | ||
| 1018 | return self.fail(src, "TODO implement support for vectors in cmpNumeric", .{}); | ||
| 1019 | } else if (lhs_ty_tag == .Vector or rhs_ty_tag == .Vector) { | ||
| 1020 | return self.fail(src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{ | ||
| 1021 | lhs.ty, | ||
| 1022 | rhs.ty, | ||
| 1023 | }); | ||
| 1024 | } | ||
| 1025 | |||
| 1026 | if (lhs.value()) |lhs_val| { | ||
| 1027 | if (rhs.value()) |rhs_val| { | ||
| 1028 | return self.constBool(src, Value.compare(lhs_val, op, rhs_val)); | ||
| 1029 | } | ||
| 1030 | } | ||
| 1031 | |||
| 1032 | // TODO handle comparisons against lazy zero values | ||
| 1033 | // Some values can be compared against zero without being runtime known or without forcing | ||
| 1034 | // a full resolution of their value, for example `@sizeOf(@Frame(function))` is known to | ||
| 1035 | // always be nonzero, and we benefit from not forcing the full evaluation and stack frame layout | ||
| 1036 | // of this function if we don't need to. | ||
| 1037 | |||
| 1038 | // It must be a runtime comparison. | ||
| 1039 | const b = try self.requireRuntimeBlock(block, src); | ||
| 1040 | // For floats, emit a float comparison instruction. | ||
| 1041 | const lhs_is_float = switch (lhs_ty_tag) { | ||
| 1042 | .Float, .ComptimeFloat => true, | ||
| 1043 | else => false, | ||
| 1044 | }; | ||
| 1045 | const rhs_is_float = switch (rhs_ty_tag) { | ||
| 1046 | .Float, .ComptimeFloat => true, | ||
| 1047 | else => false, | ||
| 1048 | }; | ||
| 1049 | if (lhs_is_float and rhs_is_float) { | ||
| 1050 | // Implicit cast the smaller one to the larger one. | ||
| 1051 | const dest_type = x: { | ||
| 1052 | if (lhs_ty_tag == .ComptimeFloat) { | ||
| 1053 | break :x rhs.ty; | ||
| 1054 | } else if (rhs_ty_tag == .ComptimeFloat) { | ||
| 1055 | break :x lhs.ty; | ||
| 1056 | } | ||
| 1057 | if (lhs.ty.floatBits(self.target) >= rhs.ty.floatBits(self.target)) { | ||
| 1058 | break :x lhs.ty; | ||
| 1059 | } else { | ||
| 1060 | break :x rhs.ty; | ||
| 1061 | } | ||
| 1062 | }; | ||
| 1063 | const casted_lhs = try self.coerce(block, dest_type, lhs); | ||
| 1064 | const casted_rhs = try self.coerce(block, dest_type, rhs); | ||
| 1065 | return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){ | ||
| 1066 | .lhs = casted_lhs, | ||
| 1067 | .rhs = casted_rhs, | ||
| 1068 | .op = op, | ||
| 1069 | }); | ||
| 1070 | } | ||
| 1071 | // For mixed unsigned integer sizes, implicit cast both operands to the larger integer. | ||
| 1072 | // For mixed signed and unsigned integers, implicit cast both operands to a signed | ||
| 1073 | // integer with + 1 bit. | ||
| 1074 | // For mixed floats and integers, extract the integer part from the float, cast that to | ||
| 1075 | // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float, | ||
| 1076 | // add/subtract 1. | ||
| 1077 | const lhs_is_signed = if (lhs.value()) |lhs_val| | ||
| 1078 | lhs_val.compareWithZero(.lt) | ||
| 1079 | else | ||
| 1080 | (lhs.ty.isFloat() or lhs.ty.isSignedInt()); | ||
| 1081 | const rhs_is_signed = if (rhs.value()) |rhs_val| | ||
| 1082 | rhs_val.compareWithZero(.lt) | ||
| 1083 | else | ||
| 1084 | (rhs.ty.isFloat() or rhs.ty.isSignedInt()); | ||
| 1085 | const dest_int_is_signed = lhs_is_signed or rhs_is_signed; | ||
| 1086 | |||
| 1087 | var dest_float_type: ?Type = null; | ||
| 1088 | |||
| 1089 | var lhs_bits: usize = undefined; | ||
| 1090 | if (lhs.value()) |lhs_val| { | ||
| 1091 | if (lhs_val.isUndef()) | ||
| 1092 | return self.constUndef(src, Type.initTag(.bool)); | ||
| 1093 | const is_unsigned = if (lhs_is_float) x: { | ||
| 1094 | var bigint_space: Value.BigIntSpace = undefined; | ||
| 1095 | var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(self.allocator); | ||
| 1096 | defer bigint.deinit(); | ||
| 1097 | const zcmp = lhs_val.orderAgainstZero(); | ||
| 1098 | if (lhs_val.floatHasFraction()) { | ||
| 1099 | switch (op) { | ||
| 1100 | .eq => return self.constBool(src, false), | ||
| 1101 | .neq => return self.constBool(src, true), | ||
| 1102 | else => {}, | ||
| 1103 | } | ||
| 1104 | if (zcmp == .lt) { | ||
| 1105 | try bigint.addScalar(bigint.toConst(), -1); | ||
| 1106 | } else { | ||
| 1107 | try bigint.addScalar(bigint.toConst(), 1); | ||
| 1108 | } | ||
| 1109 | } | ||
| 1110 | lhs_bits = bigint.toConst().bitCountTwosComp(); | ||
| 1111 | break :x (zcmp != .lt); | ||
| 1112 | } else x: { | ||
| 1113 | lhs_bits = lhs_val.intBitCountTwosComp(); | ||
| 1114 | break :x (lhs_val.orderAgainstZero() != .lt); | ||
| 1115 | }; | ||
| 1116 | lhs_bits += @boolToInt(is_unsigned and dest_int_is_signed); | ||
| 1117 | } else if (lhs_is_float) { | ||
| 1118 | dest_float_type = lhs.ty; | ||
| 1119 | } else { | ||
| 1120 | const int_info = lhs.ty.intInfo(self.target); | ||
| 1121 | lhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed); | ||
| 1122 | } | ||
| 1123 | |||
| 1124 | var rhs_bits: usize = undefined; | ||
| 1125 | if (rhs.value()) |rhs_val| { | ||
| 1126 | if (rhs_val.isUndef()) | ||
| 1127 | return self.constUndef(src, Type.initTag(.bool)); | ||
| 1128 | const is_unsigned = if (rhs_is_float) x: { | ||
| 1129 | var bigint_space: Value.BigIntSpace = undefined; | ||
| 1130 | var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(self.allocator); | ||
| 1131 | defer bigint.deinit(); | ||
| 1132 | const zcmp = rhs_val.orderAgainstZero(); | ||
| 1133 | if (rhs_val.floatHasFraction()) { | ||
| 1134 | switch (op) { | ||
| 1135 | .eq => return self.constBool(src, false), | ||
| 1136 | .neq => return self.constBool(src, true), | ||
| 1137 | else => {}, | ||
| 1138 | } | ||
| 1139 | if (zcmp == .lt) { | ||
| 1140 | try bigint.addScalar(bigint.toConst(), -1); | ||
| 1141 | } else { | ||
| 1142 | try bigint.addScalar(bigint.toConst(), 1); | ||
| 1143 | } | ||
| 1144 | } | ||
| 1145 | rhs_bits = bigint.toConst().bitCountTwosComp(); | ||
| 1146 | break :x (zcmp != .lt); | ||
| 1147 | } else x: { | ||
| 1148 | rhs_bits = rhs_val.intBitCountTwosComp(); | ||
| 1149 | break :x (rhs_val.orderAgainstZero() != .lt); | ||
| 1150 | }; | ||
| 1151 | rhs_bits += @boolToInt(is_unsigned and dest_int_is_signed); | ||
| 1152 | } else if (rhs_is_float) { | ||
| 1153 | dest_float_type = rhs.ty; | ||
| 1154 | } else { | ||
| 1155 | const int_info = rhs.ty.intInfo(self.target); | ||
| 1156 | rhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed); | ||
| 1157 | } | ||
| 1158 | |||
| 1159 | const dest_type = if (dest_float_type) |ft| ft else blk: { | ||
| 1160 | const max_bits = std.math.max(lhs_bits, rhs_bits); | ||
| 1161 | const casted_bits = std.math.cast(u16, max_bits) catch |err| switch (err) { | ||
| 1162 | error.Overflow => return self.fail(src, "{} exceeds maximum integer bit count", .{max_bits}), | ||
| 1163 | }; | ||
| 1164 | break :blk try self.makeIntType(dest_int_is_signed, casted_bits); | ||
| 1165 | }; | ||
| 1166 | const casted_lhs = try self.coerce(block, dest_type, lhs); | ||
| 1167 | const casted_rhs = try self.coerce(block, dest_type, lhs); | ||
| 1168 | |||
| 1169 | return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){ | ||
| 1170 | .lhs = casted_lhs, | ||
| 1171 | .rhs = casted_rhs, | ||
| 1172 | .op = op, | ||
| 1173 | }); | ||
| 1174 | } | ||
| 1175 | |||
| 1176 | fn makeIntType(self: *Analyze, signed: bool, bits: u16) !Type { | ||
| 1177 | if (signed) { | ||
| 1178 | const int_payload = try self.arena.allocator.create(Type.Payload.IntSigned); | ||
| 1179 | int_payload.* = .{ .bits = bits }; | ||
| 1180 | return Type.initPayload(&int_payload.base); | ||
| 1181 | } else { | ||
| 1182 | const int_payload = try self.arena.allocator.create(Type.Payload.IntUnsigned); | ||
| 1183 | int_payload.* = .{ .bits = bits }; | ||
| 1184 | return Type.initPayload(&int_payload.base); | ||
| 1185 | } | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | fn coerce(self: *Analyze, block: ?*Block, dest_type: Type, inst: *Inst) !*Inst { | ||
| 1189 | // If the types are the same, we can return the operand. | ||
| 1190 | if (dest_type.eql(inst.ty)) | ||
| 1191 | return inst; | ||
| 1192 | |||
| 1193 | const in_memory_result = coerceInMemoryAllowed(dest_type, inst.ty); | ||
| 1194 | if (in_memory_result == .ok) { | ||
| 1195 | return self.bitcast(block, dest_type, inst); | ||
| 1196 | } | ||
| 1197 | |||
| 1198 | // *[N]T to []T | ||
| 1199 | if (inst.ty.isSinglePointer() and dest_type.isSlice() and | ||
| 1200 | (!inst.ty.pointerIsConst() or dest_type.pointerIsConst())) | ||
| 1201 | { | ||
| 1202 | const array_type = inst.ty.elemType(); | ||
| 1203 | const dst_elem_type = dest_type.elemType(); | ||
| 1204 | if (array_type.zigTypeTag() == .Array and | ||
| 1205 | coerceInMemoryAllowed(dst_elem_type, array_type.elemType()) == .ok) | ||
| 1206 | { | ||
| 1207 | return self.coerceArrayPtrToSlice(dest_type, inst); | ||
| 1208 | } | ||
| 1209 | } | ||
| 1210 | |||
| 1211 | // comptime_int to fixed-width integer | ||
| 1212 | if (inst.ty.zigTypeTag() == .ComptimeInt and dest_type.zigTypeTag() == .Int) { | ||
| 1213 | // The representation is already correct; we only need to make sure it fits in the destination type. | ||
| 1214 | const val = inst.value().?; // comptime_int always has comptime known value | ||
| 1215 | if (!val.intFitsInType(dest_type, self.target)) { | ||
| 1216 | return self.fail(inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val }); | ||
| 1217 | } | ||
| 1218 | return self.constInst(inst.src, .{ .ty = dest_type, .val = val }); | ||
| 1219 | } | ||
| 1220 | |||
| 1221 | // integer widening | ||
| 1222 | if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) { | ||
| 1223 | const src_info = inst.ty.intInfo(self.target); | ||
| 1224 | const dst_info = dest_type.intInfo(self.target); | ||
| 1225 | if (src_info.signed == dst_info.signed and dst_info.bits >= src_info.bits) { | ||
| 1226 | if (inst.value()) |val| { | ||
| 1227 | return self.constInst(inst.src, .{ .ty = dest_type, .val = val }); | ||
| 1228 | } else { | ||
| 1229 | return self.fail(inst.src, "TODO implement runtime integer widening", .{}); | ||
| 1230 | } | ||
| 1231 | } else { | ||
| 1232 | return self.fail(inst.src, "TODO implement more int widening {} to {}", .{ inst.ty, dest_type }); | ||
| 1233 | } | ||
| 1234 | } | ||
| 1235 | |||
| 1236 | return self.fail(inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type }); | ||
| 1237 | } | ||
| 1238 | |||
| 1239 | fn bitcast(self: *Analyze, block: ?*Block, dest_type: Type, inst: *Inst) !*Inst { | ||
| 1240 | if (inst.value()) |val| { | ||
| 1241 | // Keep the comptime Value representation; take the new type. | ||
| 1242 | return self.constInst(inst.src, .{ .ty = dest_type, .val = val }); | ||
| 1243 | } | ||
| 1244 | // TODO validate the type size and other compile errors | ||
| 1245 | const b = try self.requireRuntimeBlock(block, inst.src); | ||
| 1246 | return self.addNewInstArgs(b, inst.src, dest_type, Inst.BitCast, Inst.Args(Inst.BitCast){ .operand = inst }); | ||
| 1247 | } | ||
| 1248 | |||
| 1249 | fn coerceArrayPtrToSlice(self: *Analyze, dest_type: Type, inst: *Inst) !*Inst { | ||
| 1250 | if (inst.value()) |val| { | ||
| 1251 | // The comptime Value representation is compatible with both types. | ||
| 1252 | return self.constInst(inst.src, .{ .ty = dest_type, .val = val }); | ||
| 1253 | } | ||
| 1254 | return self.fail(inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{}); | ||
| 1255 | } | ||
| 1256 | |||
| 1257 | fn fail(self: *Analyze, src: usize, comptime format: []const u8, args: var) InnerError { | ||
| 1258 | @setCold(true); | ||
| 1259 | const msg = try std.fmt.allocPrint(&self.arena.allocator, format, args); | ||
| 1260 | (try self.errors.addOne()).* = .{ | ||
| 1261 | .byte_offset = src, | ||
| 1262 | .msg = msg, | ||
| 1263 | }; | ||
| 1264 | return error.AnalysisFail; | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | const InMemoryCoercionResult = enum { | ||
| 1268 | ok, | ||
| 1269 | no_match, | ||
| 1270 | }; | ||
| 1271 | |||
| 1272 | fn coerceInMemoryAllowed(dest_type: Type, src_type: Type) InMemoryCoercionResult { | ||
| 1273 | if (dest_type.eql(src_type)) | ||
| 1274 | return .ok; | ||
| 1275 | |||
| 1276 | // TODO: implement more of this function | ||
| 1277 | |||
| 1278 | return .no_match; | ||
| 1279 | } | ||
| 1280 | }; | ||
| 1281 | |||
| 1282 | pub fn main() anyerror!void { | ||
| 1283 | var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | ||
| 1284 | defer arena.deinit(); | ||
| 1285 | const allocator = if (std.builtin.link_libc) std.heap.c_allocator else &arena.allocator; | ||
| 1286 | |||
| 1287 | const args = try std.process.argsAlloc(allocator); | ||
| 1288 | defer std.process.argsFree(allocator, args); | ||
| 1289 | |||
| 1290 | const src_path = args[1]; | ||
| 1291 | const debug_error_trace = true; | ||
| 1292 | |||
| 1293 | const source = try std.fs.cwd().readFileAllocOptions(allocator, src_path, std.math.maxInt(u32), 1, 0); | ||
| 1294 | defer allocator.free(source); | ||
| 1295 | |||
| 1296 | var zir_module = try text.parse(allocator, source); | ||
| 1297 | defer zir_module.deinit(allocator); | ||
| 1298 | |||
| 1299 | if (zir_module.errors.len != 0) { | ||
| 1300 | for (zir_module.errors) |err_msg| { | ||
| 1301 | const loc = std.zig.findLineColumn(source, err_msg.byte_offset); | ||
| 1302 | std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg }); | ||
| 1303 | } | ||
| 1304 | if (debug_error_trace) return error.ParseFailure; | ||
| 1305 | std.process.exit(1); | ||
| 1306 | } | ||
| 1307 | |||
| 1308 | const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{}); | ||
| 1309 | |||
| 1310 | var analyzed_module = try analyze(allocator, zir_module, .{ | ||
| 1311 | .target = native_info.target, | ||
| 1312 | .output_mode = .Obj, | ||
| 1313 | .link_mode = .Static, | ||
| 1314 | .optimize_mode = .Debug, | ||
| 1315 | }); | ||
| 1316 | defer analyzed_module.deinit(allocator); | ||
| 1317 | |||
| 1318 | if (analyzed_module.errors.len != 0) { | ||
| 1319 | for (analyzed_module.errors) |err_msg| { | ||
| 1320 | const loc = std.zig.findLineColumn(source, err_msg.byte_offset); | ||
| 1321 | std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg }); | ||
| 1322 | } | ||
| 1323 | if (debug_error_trace) return error.AnalysisFail; | ||
| 1324 | std.process.exit(1); | ||
| 1325 | } | ||
| 1326 | |||
| 1327 | const output_zir = true; | ||
| 1328 | if (output_zir) { | ||
| 1329 | var new_zir_module = try text.emit_zir(allocator, analyzed_module); | ||
| 1330 | defer new_zir_module.deinit(allocator); | ||
| 1331 | |||
| 1332 | var bos = std.io.bufferedOutStream(std.io.getStdOut().outStream()); | ||
| 1333 | try new_zir_module.writeToStream(allocator, bos.outStream()); | ||
| 1334 | try bos.flush(); | ||
| 1335 | } | ||
| 1336 | |||
| 1337 | const link = @import("link.zig"); | ||
| 1338 | var result = try link.updateFilePath(allocator, analyzed_module, std.fs.cwd(), "zir.o"); | ||
| 1339 | defer result.deinit(allocator); | ||
| 1340 | if (result.errors.len != 0) { | ||
| 1341 | for (result.errors) |err_msg| { | ||
| 1342 | const loc = std.zig.findLineColumn(source, err_msg.byte_offset); | ||
| 1343 | std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg }); | ||
| 1344 | } | ||
| 1345 | if (debug_error_trace) return error.LinkFailure; | ||
| 1346 | std.process.exit(1); | ||
| 1347 | } | ||
| 1348 | } | ||
| 1349 | |||
| 1350 | // Performance optimization ideas: | ||
| 1351 | // * when analyzing use a field in the Inst instead of HashMap to track corresponding instructions |
src-self-hosted/ir/text.zig deleted-1282| ... | @@ -1,1282 +0,0 @@ | ||
| 1 | //! This file has to do with parsing and rendering the ZIR text format. | ||
| 2 | |||
| 3 | const std = @import("std"); | ||
| 4 | const mem = std.mem; | ||
| 5 | const Allocator = std.mem.Allocator; | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | const BigIntConst = std.math.big.int.Const; | ||
| 8 | const BigIntMutable = std.math.big.int.Mutable; | ||
| 9 | const Type = @import("../type.zig").Type; | ||
| 10 | const Value = @import("../value.zig").Value; | ||
| 11 | const ir = @import("../ir.zig"); | ||
| 12 | |||
| 13 | /// These are instructions that correspond to the ZIR text format. See `ir.Inst` for | ||
| 14 | /// in-memory, analyzed instructions with types and values. | ||
| 15 | pub const Inst = struct { | ||
| 16 | tag: Tag, | ||
| 17 | /// Byte offset into the source. | ||
| 18 | src: usize, | ||
| 19 | |||
| 20 | /// These names are used directly as the instruction names in the text format. | ||
| 21 | pub const Tag = enum { | ||
| 22 | breakpoint, | ||
| 23 | str, | ||
| 24 | int, | ||
| 25 | ptrtoint, | ||
| 26 | fieldptr, | ||
| 27 | deref, | ||
| 28 | as, | ||
| 29 | @"asm", | ||
| 30 | @"unreachable", | ||
| 31 | @"return", | ||
| 32 | @"fn", | ||
| 33 | @"export", | ||
| 34 | primitive, | ||
| 35 | fntype, | ||
| 36 | intcast, | ||
| 37 | bitcast, | ||
| 38 | elemptr, | ||
| 39 | add, | ||
| 40 | cmp, | ||
| 41 | condbr, | ||
| 42 | isnull, | ||
| 43 | isnonnull, | ||
| 44 | }; | ||
| 45 | |||
| 46 | pub fn TagToType(tag: Tag) type { | ||
| 47 | return switch (tag) { | ||
| 48 | .breakpoint => Breakpoint, | ||
| 49 | .str => Str, | ||
| 50 | .int => Int, | ||
| 51 | .ptrtoint => PtrToInt, | ||
| 52 | .fieldptr => FieldPtr, | ||
| 53 | .deref => Deref, | ||
| 54 | .as => As, | ||
| 55 | .@"asm" => Asm, | ||
| 56 | .@"unreachable" => Unreachable, | ||
| 57 | .@"return" => Return, | ||
| 58 | .@"fn" => Fn, | ||
| 59 | .@"export" => Export, | ||
| 60 | .primitive => Primitive, | ||
| 61 | .fntype => FnType, | ||
| 62 | .intcast => IntCast, | ||
| 63 | .bitcast => BitCast, | ||
| 64 | .elemptr => ElemPtr, | ||
| 65 | .add => Add, | ||
| 66 | .cmp => Cmp, | ||
| 67 | .condbr => CondBr, | ||
| 68 | .isnull => IsNull, | ||
| 69 | .isnonnull => IsNonNull, | ||
| 70 | }; | ||
| 71 | } | ||
| 72 | |||
| 73 | pub fn cast(base: *Inst, comptime T: type) ?*T { | ||
| 74 | if (base.tag != T.base_tag) | ||
| 75 | return null; | ||
| 76 | |||
| 77 | return @fieldParentPtr(T, "base", base); | ||
| 78 | } | ||
| 79 | |||
| 80 | pub const Breakpoint = struct { | ||
| 81 | pub const base_tag = Tag.breakpoint; | ||
| 82 | base: Inst, | ||
| 83 | |||
| 84 | positionals: struct {}, | ||
| 85 | kw_args: struct {}, | ||
| 86 | }; | ||
| 87 | |||
| 88 | pub const Str = struct { | ||
| 89 | pub const base_tag = Tag.str; | ||
| 90 | base: Inst, | ||
| 91 | |||
| 92 | positionals: struct { | ||
| 93 | bytes: []const u8, | ||
| 94 | }, | ||
| 95 | kw_args: struct {}, | ||
| 96 | }; | ||
| 97 | |||
| 98 | pub const Int = struct { | ||
| 99 | pub const base_tag = Tag.int; | ||
| 100 | base: Inst, | ||
| 101 | |||
| 102 | positionals: struct { | ||
| 103 | int: BigIntConst, | ||
| 104 | }, | ||
| 105 | kw_args: struct {}, | ||
| 106 | }; | ||
| 107 | |||
| 108 | pub const PtrToInt = struct { | ||
| 109 | pub const base_tag = Tag.ptrtoint; | ||
| 110 | base: Inst, | ||
| 111 | |||
| 112 | positionals: struct { | ||
| 113 | ptr: *Inst, | ||
| 114 | }, | ||
| 115 | kw_args: struct {}, | ||
| 116 | }; | ||
| 117 | |||
| 118 | pub const FieldPtr = struct { | ||
| 119 | pub const base_tag = Tag.fieldptr; | ||
| 120 | base: Inst, | ||
| 121 | |||
| 122 | positionals: struct { | ||
| 123 | object_ptr: *Inst, | ||
| 124 | field_name: *Inst, | ||
| 125 | }, | ||
| 126 | kw_args: struct {}, | ||
| 127 | }; | ||
| 128 | |||
| 129 | pub const Deref = struct { | ||
| 130 | pub const base_tag = Tag.deref; | ||
| 131 | base: Inst, | ||
| 132 | |||
| 133 | positionals: struct { | ||
| 134 | ptr: *Inst, | ||
| 135 | }, | ||
| 136 | kw_args: struct {}, | ||
| 137 | }; | ||
| 138 | |||
| 139 | pub const As = struct { | ||
| 140 | pub const base_tag = Tag.as; | ||
| 141 | base: Inst, | ||
| 142 | |||
| 143 | positionals: struct { | ||
| 144 | dest_type: *Inst, | ||
| 145 | value: *Inst, | ||
| 146 | }, | ||
| 147 | kw_args: struct {}, | ||
| 148 | }; | ||
| 149 | |||
| 150 | pub const Asm = struct { | ||
| 151 | pub const base_tag = Tag.@"asm"; | ||
| 152 | base: Inst, | ||
| 153 | |||
| 154 | positionals: struct { | ||
| 155 | asm_source: *Inst, | ||
| 156 | return_type: *Inst, | ||
| 157 | }, | ||
| 158 | kw_args: struct { | ||
| 159 | @"volatile": bool = false, | ||
| 160 | output: ?*Inst = null, | ||
| 161 | inputs: []*Inst = &[0]*Inst{}, | ||
| 162 | clobbers: []*Inst = &[0]*Inst{}, | ||
| 163 | args: []*Inst = &[0]*Inst{}, | ||
| 164 | }, | ||
| 165 | }; | ||
| 166 | |||
| 167 | pub const Unreachable = struct { | ||
| 168 | pub const base_tag = Tag.@"unreachable"; | ||
| 169 | base: Inst, | ||
| 170 | |||
| 171 | positionals: struct {}, | ||
| 172 | kw_args: struct {}, | ||
| 173 | }; | ||
| 174 | |||
| 175 | pub const Return = struct { | ||
| 176 | pub const base_tag = Tag.@"return"; | ||
| 177 | base: Inst, | ||
| 178 | |||
| 179 | positionals: struct {}, | ||
| 180 | kw_args: struct {}, | ||
| 181 | }; | ||
| 182 | |||
| 183 | pub const Fn = struct { | ||
| 184 | pub const base_tag = Tag.@"fn"; | ||
| 185 | base: Inst, | ||
| 186 | |||
| 187 | positionals: struct { | ||
| 188 | fn_type: *Inst, | ||
| 189 | body: Module.Body, | ||
| 190 | }, | ||
| 191 | kw_args: struct {}, | ||
| 192 | }; | ||
| 193 | |||
| 194 | pub const Export = struct { | ||
| 195 | pub const base_tag = Tag.@"export"; | ||
| 196 | base: Inst, | ||
| 197 | |||
| 198 | positionals: struct { | ||
| 199 | symbol_name: *Inst, | ||
| 200 | value: *Inst, | ||
| 201 | }, | ||
| 202 | kw_args: struct {}, | ||
| 203 | }; | ||
| 204 | |||
| 205 | pub const Primitive = struct { | ||
| 206 | pub const base_tag = Tag.primitive; | ||
| 207 | base: Inst, | ||
| 208 | |||
| 209 | positionals: struct { | ||
| 210 | tag: BuiltinType, | ||
| 211 | }, | ||
| 212 | kw_args: struct {}, | ||
| 213 | |||
| 214 | pub const BuiltinType = enum { | ||
| 215 | @"isize", | ||
| 216 | @"usize", | ||
| 217 | @"c_short", | ||
| 218 | @"c_ushort", | ||
| 219 | @"c_int", | ||
| 220 | @"c_uint", | ||
| 221 | @"c_long", | ||
| 222 | @"c_ulong", | ||
| 223 | @"c_longlong", | ||
| 224 | @"c_ulonglong", | ||
| 225 | @"c_longdouble", | ||
| 226 | @"c_void", | ||
| 227 | @"f16", | ||
| 228 | @"f32", | ||
| 229 | @"f64", | ||
| 230 | @"f128", | ||
| 231 | @"bool", | ||
| 232 | @"void", | ||
| 233 | @"noreturn", | ||
| 234 | @"type", | ||
| 235 | @"anyerror", | ||
| 236 | @"comptime_int", | ||
| 237 | @"comptime_float", | ||
| 238 | |||
| 239 | pub fn toType(self: BuiltinType) Type { | ||
| 240 | return switch (self) { | ||
| 241 | .@"isize" => Type.initTag(.@"isize"), | ||
| 242 | .@"usize" => Type.initTag(.@"usize"), | ||
| 243 | .@"c_short" => Type.initTag(.@"c_short"), | ||
| 244 | .@"c_ushort" => Type.initTag(.@"c_ushort"), | ||
| 245 | .@"c_int" => Type.initTag(.@"c_int"), | ||
| 246 | .@"c_uint" => Type.initTag(.@"c_uint"), | ||
| 247 | .@"c_long" => Type.initTag(.@"c_long"), | ||
| 248 | .@"c_ulong" => Type.initTag(.@"c_ulong"), | ||
| 249 | .@"c_longlong" => Type.initTag(.@"c_longlong"), | ||
| 250 | .@"c_ulonglong" => Type.initTag(.@"c_ulonglong"), | ||
| 251 | .@"c_longdouble" => Type.initTag(.@"c_longdouble"), | ||
| 252 | .@"c_void" => Type.initTag(.@"c_void"), | ||
| 253 | .@"f16" => Type.initTag(.@"f16"), | ||
| 254 | .@"f32" => Type.initTag(.@"f32"), | ||
| 255 | .@"f64" => Type.initTag(.@"f64"), | ||
| 256 | .@"f128" => Type.initTag(.@"f128"), | ||
| 257 | .@"bool" => Type.initTag(.@"bool"), | ||
| 258 | .@"void" => Type.initTag(.@"void"), | ||
| 259 | .@"noreturn" => Type.initTag(.@"noreturn"), | ||
| 260 | .@"type" => Type.initTag(.@"type"), | ||
| 261 | .@"anyerror" => Type.initTag(.@"anyerror"), | ||
| 262 | .@"comptime_int" => Type.initTag(.@"comptime_int"), | ||
| 263 | .@"comptime_float" => Type.initTag(.@"comptime_float"), | ||
| 264 | }; | ||
| 265 | } | ||
| 266 | }; | ||
| 267 | }; | ||
| 268 | |||
| 269 | pub const FnType = struct { | ||
| 270 | pub const base_tag = Tag.fntype; | ||
| 271 | base: Inst, | ||
| 272 | |||
| 273 | positionals: struct { | ||
| 274 | param_types: []*Inst, | ||
| 275 | return_type: *Inst, | ||
| 276 | }, | ||
| 277 | kw_args: struct { | ||
| 278 | cc: std.builtin.CallingConvention = .Unspecified, | ||
| 279 | }, | ||
| 280 | }; | ||
| 281 | |||
| 282 | pub const IntCast = struct { | ||
| 283 | pub const base_tag = Tag.intcast; | ||
| 284 | base: Inst, | ||
| 285 | |||
| 286 | positionals: struct { | ||
| 287 | dest_type: *Inst, | ||
| 288 | value: *Inst, | ||
| 289 | }, | ||
| 290 | kw_args: struct {}, | ||
| 291 | }; | ||
| 292 | |||
| 293 | pub const BitCast = struct { | ||
| 294 | pub const base_tag = Tag.bitcast; | ||
| 295 | base: Inst, | ||
| 296 | |||
| 297 | positionals: struct { | ||
| 298 | dest_type: *Inst, | ||
| 299 | operand: *Inst, | ||
| 300 | }, | ||
| 301 | kw_args: struct {}, | ||
| 302 | }; | ||
| 303 | |||
| 304 | pub const ElemPtr = struct { | ||
| 305 | pub const base_tag = Tag.elemptr; | ||
| 306 | base: Inst, | ||
| 307 | |||
| 308 | positionals: struct { | ||
| 309 | array_ptr: *Inst, | ||
| 310 | index: *Inst, | ||
| 311 | }, | ||
| 312 | kw_args: struct {}, | ||
| 313 | }; | ||
| 314 | |||
| 315 | pub const Add = struct { | ||
| 316 | pub const base_tag = Tag.add; | ||
| 317 | base: Inst, | ||
| 318 | |||
| 319 | positionals: struct { | ||
| 320 | lhs: *Inst, | ||
| 321 | rhs: *Inst, | ||
| 322 | }, | ||
| 323 | kw_args: struct {}, | ||
| 324 | }; | ||
| 325 | |||
| 326 | pub const Cmp = struct { | ||
| 327 | pub const base_tag = Tag.cmp; | ||
| 328 | base: Inst, | ||
| 329 | |||
| 330 | positionals: struct { | ||
| 331 | lhs: *Inst, | ||
| 332 | op: std.math.CompareOperator, | ||
| 333 | rhs: *Inst, | ||
| 334 | }, | ||
| 335 | kw_args: struct {}, | ||
| 336 | }; | ||
| 337 | |||
| 338 | pub const CondBr = struct { | ||
| 339 | pub const base_tag = Tag.condbr; | ||
| 340 | base: Inst, | ||
| 341 | |||
| 342 | positionals: struct { | ||
| 343 | condition: *Inst, | ||
| 344 | true_body: Module.Body, | ||
| 345 | false_body: Module.Body, | ||
| 346 | }, | ||
| 347 | kw_args: struct {}, | ||
| 348 | }; | ||
| 349 | |||
| 350 | pub const IsNull = struct { | ||
| 351 | pub const base_tag = Tag.isnull; | ||
| 352 | base: Inst, | ||
| 353 | |||
| 354 | positionals: struct { | ||
| 355 | operand: *Inst, | ||
| 356 | }, | ||
| 357 | kw_args: struct {}, | ||
| 358 | }; | ||
| 359 | |||
| 360 | pub const IsNonNull = struct { | ||
| 361 | pub const base_tag = Tag.isnonnull; | ||
| 362 | base: Inst, | ||
| 363 | |||
| 364 | positionals: struct { | ||
| 365 | operand: *Inst, | ||
| 366 | }, | ||
| 367 | kw_args: struct {}, | ||
| 368 | }; | ||
| 369 | }; | ||
| 370 | |||
| 371 | pub const ErrorMsg = struct { | ||
| 372 | byte_offset: usize, | ||
| 373 | msg: []const u8, | ||
| 374 | }; | ||
| 375 | |||
| 376 | pub const Module = struct { | ||
| 377 | decls: []*Inst, | ||
| 378 | errors: []ErrorMsg, | ||
| 379 | arena: std.heap.ArenaAllocator, | ||
| 380 | |||
| 381 | pub const Body = struct { | ||
| 382 | instructions: []*Inst, | ||
| 383 | }; | ||
| 384 | |||
| 385 | pub fn deinit(self: *Module, allocator: *Allocator) void { | ||
| 386 | allocator.free(self.decls); | ||
| 387 | allocator.free(self.errors); | ||
| 388 | self.arena.deinit(); | ||
| 389 | self.* = undefined; | ||
| 390 | } | ||
| 391 | |||
| 392 | /// This is a debugging utility for rendering the tree to stderr. | ||
| 393 | pub fn dump(self: Module) void { | ||
| 394 | self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {}; | ||
| 395 | } | ||
| 396 | |||
| 397 | const InstPtrTable = std.AutoHashMap(*Inst, struct { index: usize, fn_body: ?*Module.Body }); | ||
| 398 | |||
| 399 | /// The allocator is used for temporary storage, but this function always returns | ||
| 400 | /// with no resources allocated. | ||
| 401 | pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void { | ||
| 402 | // First, build a map of *Inst to @ or % indexes | ||
| 403 | var inst_table = InstPtrTable.init(allocator); | ||
| 404 | defer inst_table.deinit(); | ||
| 405 | |||
| 406 | try inst_table.ensureCapacity(self.decls.len); | ||
| 407 | |||
| 408 | for (self.decls) |decl, decl_i| { | ||
| 409 | try inst_table.putNoClobber(decl, .{ .index = decl_i, .fn_body = null }); | ||
| 410 | |||
| 411 | if (decl.cast(Inst.Fn)) |fn_inst| { | ||
| 412 | for (fn_inst.positionals.body.instructions) |inst, inst_i| { | ||
| 413 | try inst_table.putNoClobber(inst, .{ .index = inst_i, .fn_body = &fn_inst.positionals.body }); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | } | ||
| 417 | |||
| 418 | for (self.decls) |decl, i| { | ||
| 419 | try stream.print("@{} ", .{i}); | ||
| 420 | try self.writeInstToStream(stream, decl, &inst_table); | ||
| 421 | try stream.writeByte('\n'); | ||
| 422 | } | ||
| 423 | } | ||
| 424 | |||
| 425 | fn writeInstToStream( | ||
| 426 | self: Module, | ||
| 427 | stream: var, | ||
| 428 | decl: *Inst, | ||
| 429 | inst_table: *const InstPtrTable, | ||
| 430 | ) @TypeOf(stream).Error!void { | ||
| 431 | // TODO I tried implementing this with an inline for loop and hit a compiler bug | ||
| 432 | switch (decl.tag) { | ||
| 433 | .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, decl, inst_table), | ||
| 434 | .str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table), | ||
| 435 | .int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table), | ||
| 436 | .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table), | ||
| 437 | .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, decl, inst_table), | ||
| 438 | .deref => return self.writeInstToStreamGeneric(stream, .deref, decl, inst_table), | ||
| 439 | .as => return self.writeInstToStreamGeneric(stream, .as, decl, inst_table), | ||
| 440 | .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", decl, inst_table), | ||
| 441 | .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", decl, inst_table), | ||
| 442 | .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", decl, inst_table), | ||
| 443 | .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table), | ||
| 444 | .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", decl, inst_table), | ||
| 445 | .primitive => return self.writeInstToStreamGeneric(stream, .primitive, decl, inst_table), | ||
| 446 | .fntype => return self.writeInstToStreamGeneric(stream, .fntype, decl, inst_table), | ||
| 447 | .intcast => return self.writeInstToStreamGeneric(stream, .intcast, decl, inst_table), | ||
| 448 | .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, decl, inst_table), | ||
| 449 | .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, decl, inst_table), | ||
| 450 | .add => return self.writeInstToStreamGeneric(stream, .add, decl, inst_table), | ||
| 451 | .cmp => return self.writeInstToStreamGeneric(stream, .cmp, decl, inst_table), | ||
| 452 | .condbr => return self.writeInstToStreamGeneric(stream, .condbr, decl, inst_table), | ||
| 453 | .isnull => return self.writeInstToStreamGeneric(stream, .isnull, decl, inst_table), | ||
| 454 | .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, decl, inst_table), | ||
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | fn writeInstToStreamGeneric( | ||
| 459 | self: Module, | ||
| 460 | stream: var, | ||
| 461 | comptime inst_tag: Inst.Tag, | ||
| 462 | base: *Inst, | ||
| 463 | inst_table: *const InstPtrTable, | ||
| 464 | ) !void { | ||
| 465 | const SpecificInst = Inst.TagToType(inst_tag); | ||
| 466 | const inst = @fieldParentPtr(SpecificInst, "base", base); | ||
| 467 | const Positionals = @TypeOf(inst.positionals); | ||
| 468 | try stream.writeAll("= " ++ @tagName(inst_tag) ++ "("); | ||
| 469 | const pos_fields = @typeInfo(Positionals).Struct.fields; | ||
| 470 | inline for (pos_fields) |arg_field, i| { | ||
| 471 | if (i != 0) { | ||
| 472 | try stream.writeAll(", "); | ||
| 473 | } | ||
| 474 | try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table); | ||
| 475 | } | ||
| 476 | |||
| 477 | comptime var need_comma = pos_fields.len != 0; | ||
| 478 | const KW_Args = @TypeOf(inst.kw_args); | ||
| 479 | inline for (@typeInfo(KW_Args).Struct.fields) |arg_field, i| { | ||
| 480 | if (@typeInfo(arg_field.field_type) == .Optional) { | ||
| 481 | if (@field(inst.kw_args, arg_field.name)) |non_optional| { | ||
| 482 | if (need_comma) try stream.writeAll(", "); | ||
| 483 | try stream.print("{}=", .{arg_field.name}); | ||
| 484 | try self.writeParamToStream(stream, non_optional, inst_table); | ||
| 485 | need_comma = true; | ||
| 486 | } | ||
| 487 | } else { | ||
| 488 | if (need_comma) try stream.writeAll(", "); | ||
| 489 | try stream.print("{}=", .{arg_field.name}); | ||
| 490 | try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name), inst_table); | ||
| 491 | need_comma = true; | ||
| 492 | } | ||
| 493 | } | ||
| 494 | |||
| 495 | try stream.writeByte(')'); | ||
| 496 | } | ||
| 497 | |||
| 498 | fn writeParamToStream(self: Module, stream: var, param: var, inst_table: *const InstPtrTable) !void { | ||
| 499 | if (@typeInfo(@TypeOf(param)) == .Enum) { | ||
| 500 | return stream.writeAll(@tagName(param)); | ||
| 501 | } | ||
| 502 | switch (@TypeOf(param)) { | ||
| 503 | *Inst => return self.writeInstParamToStream(stream, param, inst_table), | ||
| 504 | []*Inst => { | ||
| 505 | try stream.writeByte('['); | ||
| 506 | for (param) |inst, i| { | ||
| 507 | if (i != 0) { | ||
| 508 | try stream.writeAll(", "); | ||
| 509 | } | ||
| 510 | try self.writeInstParamToStream(stream, inst, inst_table); | ||
| 511 | } | ||
| 512 | try stream.writeByte(']'); | ||
| 513 | }, | ||
| 514 | Module.Body => { | ||
| 515 | try stream.writeAll("{\n"); | ||
| 516 | for (param.instructions) |inst, i| { | ||
| 517 | try stream.print(" %{} ", .{i}); | ||
| 518 | try self.writeInstToStream(stream, inst, inst_table); | ||
| 519 | try stream.writeByte('\n'); | ||
| 520 | } | ||
| 521 | try stream.writeByte('}'); | ||
| 522 | }, | ||
| 523 | bool => return stream.writeByte("01"[@boolToInt(param)]), | ||
| 524 | []u8, []const u8 => return std.zig.renderStringLiteral(param, stream), | ||
| 525 | BigIntConst => return stream.print("{}", .{param}), | ||
| 526 | else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), | ||
| 527 | } | ||
| 528 | } | ||
| 529 | |||
| 530 | fn writeInstParamToStream(self: Module, stream: var, inst: *Inst, inst_table: *const InstPtrTable) !void { | ||
| 531 | const info = inst_table.getValue(inst).?; | ||
| 532 | const prefix = if (info.fn_body == null) "@" else "%"; | ||
| 533 | try stream.print("{}{}", .{ prefix, info.index }); | ||
| 534 | } | ||
| 535 | }; | ||
| 536 | |||
| 537 | pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module { | ||
| 538 | var global_name_map = std.StringHashMap(usize).init(allocator); | ||
| 539 | defer global_name_map.deinit(); | ||
| 540 | |||
| 541 | var parser: Parser = .{ | ||
| 542 | .allocator = allocator, | ||
| 543 | .arena = std.heap.ArenaAllocator.init(allocator), | ||
| 544 | .i = 0, | ||
| 545 | .source = source, | ||
| 546 | .decls = std.ArrayList(*Inst).init(allocator), | ||
| 547 | .errors = std.ArrayList(ErrorMsg).init(allocator), | ||
| 548 | .global_name_map = &global_name_map, | ||
| 549 | }; | ||
| 550 | errdefer parser.arena.deinit(); | ||
| 551 | |||
| 552 | parser.parseRoot() catch |err| switch (err) { | ||
| 553 | error.ParseFailure => { | ||
| 554 | assert(parser.errors.items.len != 0); | ||
| 555 | }, | ||
| 556 | else => |e| return e, | ||
| 557 | }; | ||
| 558 | return Module{ | ||
| 559 | .decls = parser.decls.toOwnedSlice(), | ||
| 560 | .errors = parser.errors.toOwnedSlice(), | ||
| 561 | .arena = parser.arena, | ||
| 562 | }; | ||
| 563 | } | ||
| 564 | |||
| 565 | const Parser = struct { | ||
| 566 | allocator: *Allocator, | ||
| 567 | arena: std.heap.ArenaAllocator, | ||
| 568 | i: usize, | ||
| 569 | source: [:0]const u8, | ||
| 570 | errors: std.ArrayList(ErrorMsg), | ||
| 571 | decls: std.ArrayList(*Inst), | ||
| 572 | global_name_map: *std.StringHashMap(usize), | ||
| 573 | |||
| 574 | const Body = struct { | ||
| 575 | instructions: std.ArrayList(*Inst), | ||
| 576 | name_map: std.StringHashMap(usize), | ||
| 577 | }; | ||
| 578 | |||
| 579 | fn parseBody(self: *Parser) !Module.Body { | ||
| 580 | var body_context = Body{ | ||
| 581 | .instructions = std.ArrayList(*Inst).init(self.allocator), | ||
| 582 | .name_map = std.StringHashMap(usize).init(self.allocator), | ||
| 583 | }; | ||
| 584 | defer body_context.instructions.deinit(); | ||
| 585 | defer body_context.name_map.deinit(); | ||
| 586 | |||
| 587 | try requireEatBytes(self, "{"); | ||
| 588 | skipSpace(self); | ||
| 589 | |||
| 590 | while (true) : (self.i += 1) switch (self.source[self.i]) { | ||
| 591 | ';' => _ = try skipToAndOver(self, '\n'), | ||
| 592 | '%' => { | ||
| 593 | self.i += 1; | ||
| 594 | const ident = try skipToAndOver(self, ' '); | ||
| 595 | skipSpace(self); | ||
| 596 | try requireEatBytes(self, "="); | ||
| 597 | skipSpace(self); | ||
| 598 | const inst = try parseInstruction(self, &body_context); | ||
| 599 | const ident_index = body_context.instructions.items.len; | ||
| 600 | if (try body_context.name_map.put(ident, ident_index)) |_| { | ||
| 601 | return self.fail("redefinition of identifier '{}'", .{ident}); | ||
| 602 | } | ||
| 603 | try body_context.instructions.append(inst); | ||
| 604 | continue; | ||
| 605 | }, | ||
| 606 | ' ', '\n' => continue, | ||
| 607 | '}' => { | ||
| 608 | self.i += 1; | ||
| 609 | break; | ||
| 610 | }, | ||
| 611 | else => |byte| return self.failByte(byte), | ||
| 612 | }; | ||
| 613 | |||
| 614 | // Move the instructions to the arena | ||
| 615 | const instrs = try self.arena.allocator.alloc(*Inst, body_context.instructions.items.len); | ||
| 616 | mem.copy(*Inst, instrs, body_context.instructions.items); | ||
| 617 | return Module.Body{ .instructions = instrs }; | ||
| 618 | } | ||
| 619 | |||
| 620 | fn parseStringLiteral(self: *Parser) ![]u8 { | ||
| 621 | const start = self.i; | ||
| 622 | try self.requireEatBytes("\""); | ||
| 623 | |||
| 624 | while (true) : (self.i += 1) switch (self.source[self.i]) { | ||
| 625 | '"' => { | ||
| 626 | self.i += 1; | ||
| 627 | const span = self.source[start..self.i]; | ||
| 628 | var bad_index: usize = undefined; | ||
| 629 | const parsed = std.zig.parseStringLiteral(&self.arena.allocator, span, &bad_index) catch |err| switch (err) { | ||
| 630 | error.InvalidCharacter => { | ||
| 631 | self.i = start + bad_index; | ||
| 632 | const bad_byte = self.source[self.i]; | ||
| 633 | return self.fail("invalid string literal character: '{c}'\n", .{bad_byte}); | ||
| 634 | }, | ||
| 635 | else => |e| return e, | ||
| 636 | }; | ||
| 637 | return parsed; | ||
| 638 | }, | ||
| 639 | '\\' => { | ||
| 640 | self.i += 1; | ||
| 641 | continue; | ||
| 642 | }, | ||
| 643 | 0 => return self.failByte(0), | ||
| 644 | else => continue, | ||
| 645 | }; | ||
| 646 | } | ||
| 647 | |||
| 648 | fn parseIntegerLiteral(self: *Parser) !BigIntConst { | ||
| 649 | const start = self.i; | ||
| 650 | if (self.source[self.i] == '-') self.i += 1; | ||
| 651 | while (true) : (self.i += 1) switch (self.source[self.i]) { | ||
| 652 | '0'...'9' => continue, | ||
| 653 | else => break, | ||
| 654 | }; | ||
| 655 | const number_text = self.source[start..self.i]; | ||
| 656 | const base = 10; | ||
| 657 | // TODO reuse the same array list for this | ||
| 658 | const limbs_buffer_len = std.math.big.int.calcSetStringLimbsBufferLen(base, number_text.len); | ||
| 659 | const limbs_buffer = try self.allocator.alloc(std.math.big.Limb, limbs_buffer_len); | ||
| 660 | defer self.allocator.free(limbs_buffer); | ||
| 661 | const limb_len = std.math.big.int.calcSetStringLimbCount(base, number_text.len); | ||
| 662 | const limbs = try self.arena.allocator.alloc(std.math.big.Limb, limb_len); | ||
| 663 | var result = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | ||
| 664 | result.setString(base, number_text, limbs_buffer, self.allocator) catch |err| switch (err) { | ||
| 665 | error.InvalidCharacter => { | ||
| 666 | self.i = start; | ||
| 667 | return self.fail("invalid digit in integer literal", .{}); | ||
| 668 | }, | ||
| 669 | }; | ||
| 670 | return result.toConst(); | ||
| 671 | } | ||
| 672 | |||
| 673 | fn parseRoot(self: *Parser) !void { | ||
| 674 | // The IR format is designed so that it can be tokenized and parsed at the same time. | ||
| 675 | while (true) { | ||
| 676 | switch (self.source[self.i]) { | ||
| 677 | ';' => _ = try skipToAndOver(self, '\n'), | ||
| 678 | '@' => { | ||
| 679 | self.i += 1; | ||
| 680 | const ident = try skipToAndOver(self, ' '); | ||
| 681 | skipSpace(self); | ||
| 682 | try requireEatBytes(self, "="); | ||
| 683 | skipSpace(self); | ||
| 684 | const inst = try parseInstruction(self, null); | ||
| 685 | const ident_index = self.decls.items.len; | ||
| 686 | if (try self.global_name_map.put(ident, ident_index)) |_| { | ||
| 687 | return self.fail("redefinition of identifier '{}'", .{ident}); | ||
| 688 | } | ||
| 689 | try self.decls.append(inst); | ||
| 690 | }, | ||
| 691 | ' ', '\n' => self.i += 1, | ||
| 692 | 0 => break, | ||
| 693 | else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}), | ||
| 694 | } | ||
| 695 | } | ||
| 696 | } | ||
| 697 | |||
| 698 | fn eatByte(self: *Parser, byte: u8) bool { | ||
| 699 | if (self.source[self.i] != byte) return false; | ||
| 700 | self.i += 1; | ||
| 701 | return true; | ||
| 702 | } | ||
| 703 | |||
| 704 | fn skipSpace(self: *Parser) void { | ||
| 705 | while (self.source[self.i] == ' ' or self.source[self.i] == '\n') { | ||
| 706 | self.i += 1; | ||
| 707 | } | ||
| 708 | } | ||
| 709 | |||
| 710 | fn requireEatBytes(self: *Parser, bytes: []const u8) !void { | ||
| 711 | const start = self.i; | ||
| 712 | for (bytes) |byte| { | ||
| 713 | if (self.source[self.i] != byte) { | ||
| 714 | self.i = start; | ||
| 715 | return self.fail("expected '{}'", .{bytes}); | ||
| 716 | } | ||
| 717 | self.i += 1; | ||
| 718 | } | ||
| 719 | } | ||
| 720 | |||
| 721 | fn skipToAndOver(self: *Parser, byte: u8) ![]const u8 { | ||
| 722 | const start_i = self.i; | ||
| 723 | while (self.source[self.i] != 0) : (self.i += 1) { | ||
| 724 | if (self.source[self.i] == byte) { | ||
| 725 | const result = self.source[start_i..self.i]; | ||
| 726 | self.i += 1; | ||
| 727 | return result; | ||
| 728 | } | ||
| 729 | } | ||
| 730 | return self.fail("unexpected EOF", .{}); | ||
| 731 | } | ||
| 732 | |||
| 733 | /// ParseFailure is an internal error code; handled in `parse`. | ||
| 734 | const InnerError = error{ ParseFailure, OutOfMemory }; | ||
| 735 | |||
| 736 | fn failByte(self: *Parser, byte: u8) InnerError { | ||
| 737 | if (byte == 0) { | ||
| 738 | return self.fail("unexpected EOF", .{}); | ||
| 739 | } else { | ||
| 740 | return self.fail("unexpected byte: '{c}'", .{byte}); | ||
| 741 | } | ||
| 742 | } | ||
| 743 | |||
| 744 | fn fail(self: *Parser, comptime format: []const u8, args: var) InnerError { | ||
| 745 | @setCold(true); | ||
| 746 | const msg = try std.fmt.allocPrint(&self.arena.allocator, format, args); | ||
| 747 | (try self.errors.addOne()).* = .{ | ||
| 748 | .byte_offset = self.i, | ||
| 749 | .msg = msg, | ||
| 750 | }; | ||
| 751 | return error.ParseFailure; | ||
| 752 | } | ||
| 753 | |||
| 754 | fn parseInstruction(self: *Parser, body_ctx: ?*Body) InnerError!*Inst { | ||
| 755 | const fn_name = try skipToAndOver(self, '('); | ||
| 756 | inline for (@typeInfo(Inst.Tag).Enum.fields) |field| { | ||
| 757 | if (mem.eql(u8, field.name, fn_name)) { | ||
| 758 | const tag = @field(Inst.Tag, field.name); | ||
| 759 | return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx); | ||
| 760 | } | ||
| 761 | } | ||
| 762 | return self.fail("unknown instruction '{}'", .{fn_name}); | ||
| 763 | } | ||
| 764 | |||
| 765 | fn parseInstructionGeneric( | ||
| 766 | self: *Parser, | ||
| 767 | comptime fn_name: []const u8, | ||
| 768 | comptime InstType: type, | ||
| 769 | body_ctx: ?*Body, | ||
| 770 | ) !*Inst { | ||
| 771 | const inst_specific = try self.arena.allocator.create(InstType); | ||
| 772 | inst_specific.base = .{ | ||
| 773 | .src = self.i, | ||
| 774 | .tag = InstType.base_tag, | ||
| 775 | }; | ||
| 776 | |||
| 777 | if (@hasField(InstType, "ty")) { | ||
| 778 | inst_specific.ty = opt_type orelse { | ||
| 779 | return self.fail("instruction '" ++ fn_name ++ "' requires type", .{}); | ||
| 780 | }; | ||
| 781 | } | ||
| 782 | |||
| 783 | const Positionals = @TypeOf(inst_specific.positionals); | ||
| 784 | inline for (@typeInfo(Positionals).Struct.fields) |arg_field| { | ||
| 785 | if (self.source[self.i] == ',') { | ||
| 786 | self.i += 1; | ||
| 787 | skipSpace(self); | ||
| 788 | } else if (self.source[self.i] == ')') { | ||
| 789 | return self.fail("expected positional parameter '{}'", .{arg_field.name}); | ||
| 790 | } | ||
| 791 | @field(inst_specific.positionals, arg_field.name) = try parseParameterGeneric( | ||
| 792 | self, | ||
| 793 | arg_field.field_type, | ||
| 794 | body_ctx, | ||
| 795 | ); | ||
| 796 | skipSpace(self); | ||
| 797 | } | ||
| 798 | |||
| 799 | const KW_Args = @TypeOf(inst_specific.kw_args); | ||
| 800 | inst_specific.kw_args = .{}; // assign defaults | ||
| 801 | skipSpace(self); | ||
| 802 | while (eatByte(self, ',')) { | ||
| 803 | skipSpace(self); | ||
| 804 | const name = try skipToAndOver(self, '='); | ||
| 805 | inline for (@typeInfo(KW_Args).Struct.fields) |arg_field| { | ||
| 806 | const field_name = arg_field.name; | ||
| 807 | if (mem.eql(u8, name, field_name)) { | ||
| 808 | const NonOptional = switch (@typeInfo(arg_field.field_type)) { | ||
| 809 | .Optional => |info| info.child, | ||
| 810 | else => arg_field.field_type, | ||
| 811 | }; | ||
| 812 | @field(inst_specific.kw_args, field_name) = try parseParameterGeneric(self, NonOptional, body_ctx); | ||
| 813 | break; | ||
| 814 | } | ||
| 815 | } else { | ||
| 816 | return self.fail("unrecognized keyword parameter: '{}'", .{name}); | ||
| 817 | } | ||
| 818 | skipSpace(self); | ||
| 819 | } | ||
| 820 | try requireEatBytes(self, ")"); | ||
| 821 | |||
| 822 | return &inst_specific.base; | ||
| 823 | } | ||
| 824 | |||
| 825 | fn parseParameterGeneric(self: *Parser, comptime T: type, body_ctx: ?*Body) !T { | ||
| 826 | if (@typeInfo(T) == .Enum) { | ||
| 827 | const start = self.i; | ||
| 828 | while (true) : (self.i += 1) switch (self.source[self.i]) { | ||
| 829 | ' ', '\n', ',', ')' => { | ||
| 830 | const enum_name = self.source[start..self.i]; | ||
| 831 | return std.meta.stringToEnum(T, enum_name) orelse { | ||
| 832 | return self.fail("tag '{}' not a member of enum '{}'", .{ enum_name, @typeName(T) }); | ||
| 833 | }; | ||
| 834 | }, | ||
| 835 | 0 => return self.failByte(0), | ||
| 836 | else => continue, | ||
| 837 | }; | ||
| 838 | } | ||
| 839 | switch (T) { | ||
| 840 | Module.Body => return parseBody(self), | ||
| 841 | bool => { | ||
| 842 | const bool_value = switch (self.source[self.i]) { | ||
| 843 | '0' => false, | ||
| 844 | '1' => true, | ||
| 845 | else => |byte| return self.fail("expected '0' or '1' for boolean value, found {c}", .{byte}), | ||
| 846 | }; | ||
| 847 | self.i += 1; | ||
| 848 | return bool_value; | ||
| 849 | }, | ||
| 850 | []*Inst => { | ||
| 851 | try requireEatBytes(self, "["); | ||
| 852 | skipSpace(self); | ||
| 853 | if (eatByte(self, ']')) return &[0]*Inst{}; | ||
| 854 | |||
| 855 | var instructions = std.ArrayList(*Inst).init(&self.arena.allocator); | ||
| 856 | while (true) { | ||
| 857 | skipSpace(self); | ||
| 858 | try instructions.append(try parseParameterInst(self, body_ctx)); | ||
| 859 | skipSpace(self); | ||
| 860 | if (!eatByte(self, ',')) break; | ||
| 861 | } | ||
| 862 | try requireEatBytes(self, "]"); | ||
| 863 | return instructions.toOwnedSlice(); | ||
| 864 | }, | ||
| 865 | *Inst => return parseParameterInst(self, body_ctx), | ||
| 866 | []u8, []const u8 => return self.parseStringLiteral(), | ||
| 867 | BigIntConst => return self.parseIntegerLiteral(), | ||
| 868 | else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), | ||
| 869 | } | ||
| 870 | return self.fail("TODO parse parameter {}", .{@typeName(T)}); | ||
| 871 | } | ||
| 872 | |||
| 873 | fn parseParameterInst(self: *Parser, body_ctx: ?*Body) !*Inst { | ||
| 874 | const local_ref = switch (self.source[self.i]) { | ||
| 875 | '@' => false, | ||
| 876 | '%' => true, | ||
| 877 | else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}), | ||
| 878 | }; | ||
| 879 | const map = if (local_ref) | ||
| 880 | if (body_ctx) |bc| | ||
| 881 | &bc.name_map | ||
| 882 | else | ||
| 883 | return self.fail("referencing a % instruction in global scope", .{}) | ||
| 884 | else | ||
| 885 | self.global_name_map; | ||
| 886 | |||
| 887 | self.i += 1; | ||
| 888 | const name_start = self.i; | ||
| 889 | while (true) : (self.i += 1) switch (self.source[self.i]) { | ||
| 890 | 0, ' ', '\n', ',', ')', ']' => break, | ||
| 891 | else => continue, | ||
| 892 | }; | ||
| 893 | const ident = self.source[name_start..self.i]; | ||
| 894 | const kv = map.get(ident) orelse { | ||
| 895 | const bad_name = self.source[name_start - 1 .. self.i]; | ||
| 896 | self.i = name_start - 1; | ||
| 897 | return self.fail("unrecognized identifier: {}", .{bad_name}); | ||
| 898 | }; | ||
| 899 | if (local_ref) { | ||
| 900 | return body_ctx.?.instructions.items[kv.value]; | ||
| 901 | } else { | ||
| 902 | return self.decls.items[kv.value]; | ||
| 903 | } | ||
| 904 | } | ||
| 905 | }; | ||
| 906 | |||
| 907 | pub fn emit_zir(allocator: *Allocator, old_module: ir.Module) !Module { | ||
| 908 | var ctx: EmitZIR = .{ | ||
| 909 | .allocator = allocator, | ||
| 910 | .decls = std.ArrayList(*Inst).init(allocator), | ||
| 911 | .decl_table = std.AutoHashMap(*ir.Inst, *Inst).init(allocator), | ||
| 912 | .arena = std.heap.ArenaAllocator.init(allocator), | ||
| 913 | .old_module = &old_module, | ||
| 914 | }; | ||
| 915 | defer ctx.decls.deinit(); | ||
| 916 | defer ctx.decl_table.deinit(); | ||
| 917 | errdefer ctx.arena.deinit(); | ||
| 918 | |||
| 919 | try ctx.emit(); | ||
| 920 | |||
| 921 | return Module{ | ||
| 922 | .decls = ctx.decls.toOwnedSlice(), | ||
| 923 | .arena = ctx.arena, | ||
| 924 | .errors = &[0]ErrorMsg{}, | ||
| 925 | }; | ||
| 926 | } | ||
| 927 | |||
| 928 | const EmitZIR = struct { | ||
| 929 | allocator: *Allocator, | ||
| 930 | arena: std.heap.ArenaAllocator, | ||
| 931 | old_module: *const ir.Module, | ||
| 932 | decls: std.ArrayList(*Inst), | ||
| 933 | decl_table: std.AutoHashMap(*ir.Inst, *Inst), | ||
| 934 | |||
| 935 | fn emit(self: *EmitZIR) !void { | ||
| 936 | for (self.old_module.exports) |module_export| { | ||
| 937 | const export_value = try self.emitTypedValue(module_export.src, module_export.typed_value); | ||
| 938 | const symbol_name = try self.emitStringLiteral(module_export.src, module_export.name); | ||
| 939 | const export_inst = try self.arena.allocator.create(Inst.Export); | ||
| 940 | export_inst.* = .{ | ||
| 941 | .base = .{ .src = module_export.src, .tag = Inst.Export.base_tag }, | ||
| 942 | .positionals = .{ | ||
| 943 | .symbol_name = symbol_name, | ||
| 944 | .value = export_value, | ||
| 945 | }, | ||
| 946 | .kw_args = .{}, | ||
| 947 | }; | ||
| 948 | try self.decls.append(&export_inst.base); | ||
| 949 | } | ||
| 950 | } | ||
| 951 | |||
| 952 | fn resolveInst(self: *EmitZIR, inst_table: *const std.AutoHashMap(*ir.Inst, *Inst), inst: *ir.Inst) !*Inst { | ||
| 953 | if (inst.cast(ir.Inst.Constant)) |const_inst| { | ||
| 954 | if (self.decl_table.getValue(inst)) |decl| { | ||
| 955 | return decl; | ||
| 956 | } | ||
| 957 | const new_decl = try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); | ||
| 958 | try self.decl_table.putNoClobber(inst, new_decl); | ||
| 959 | return new_decl; | ||
| 960 | } else { | ||
| 961 | return inst_table.getValue(inst).?; | ||
| 962 | } | ||
| 963 | } | ||
| 964 | |||
| 965 | fn emitComptimeIntVal(self: *EmitZIR, src: usize, val: Value) !*Inst { | ||
| 966 | const big_int_space = try self.arena.allocator.create(Value.BigIntSpace); | ||
| 967 | const int_inst = try self.arena.allocator.create(Inst.Int); | ||
| 968 | int_inst.* = .{ | ||
| 969 | .base = .{ .src = src, .tag = Inst.Int.base_tag }, | ||
| 970 | .positionals = .{ | ||
| 971 | .int = val.toBigInt(big_int_space), | ||
| 972 | }, | ||
| 973 | .kw_args = .{}, | ||
| 974 | }; | ||
| 975 | try self.decls.append(&int_inst.base); | ||
| 976 | return &int_inst.base; | ||
| 977 | } | ||
| 978 | |||
| 979 | fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: ir.TypedValue) Allocator.Error!*Inst { | ||
| 980 | switch (typed_value.ty.zigTypeTag()) { | ||
| 981 | .Pointer => { | ||
| 982 | const ptr_elem_type = typed_value.ty.elemType(); | ||
| 983 | switch (ptr_elem_type.zigTypeTag()) { | ||
| 984 | .Array => { | ||
| 985 | // TODO more checks to make sure this can be emitted as a string literal | ||
| 986 | //const array_elem_type = ptr_elem_type.elemType(); | ||
| 987 | //if (array_elem_type.eql(Type.initTag(.u8)) and | ||
| 988 | // ptr_elem_type.hasSentinel(Value.initTag(.zero))) | ||
| 989 | //{ | ||
| 990 | //} | ||
| 991 | const bytes = try typed_value.val.toAllocatedBytes(&self.arena.allocator); | ||
| 992 | return self.emitStringLiteral(src, bytes); | ||
| 993 | }, | ||
| 994 | else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {}", .{@tagName(t)}), | ||
| 995 | } | ||
| 996 | }, | ||
| 997 | .ComptimeInt => return self.emitComptimeIntVal(src, typed_value.val), | ||
| 998 | .Int => { | ||
| 999 | const as_inst = try self.arena.allocator.create(Inst.As); | ||
| 1000 | as_inst.* = .{ | ||
| 1001 | .base = .{ .src = src, .tag = Inst.As.base_tag }, | ||
| 1002 | .positionals = .{ | ||
| 1003 | .dest_type = try self.emitType(src, typed_value.ty), | ||
| 1004 | .value = try self.emitComptimeIntVal(src, typed_value.val), | ||
| 1005 | }, | ||
| 1006 | .kw_args = .{}, | ||
| 1007 | }; | ||
| 1008 | try self.decls.append(&as_inst.base); | ||
| 1009 | |||
| 1010 | return &as_inst.base; | ||
| 1011 | }, | ||
| 1012 | .Type => { | ||
| 1013 | const ty = typed_value.val.toType(); | ||
| 1014 | return self.emitType(src, ty); | ||
| 1015 | }, | ||
| 1016 | .Fn => { | ||
| 1017 | const index = typed_value.val.cast(Value.Payload.Function).?.index; | ||
| 1018 | const module_fn = self.old_module.fns[index]; | ||
| 1019 | |||
| 1020 | var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); | ||
| 1021 | defer inst_table.deinit(); | ||
| 1022 | |||
| 1023 | var instructions = std.ArrayList(*Inst).init(self.allocator); | ||
| 1024 | defer instructions.deinit(); | ||
| 1025 | |||
| 1026 | try self.emitBody(module_fn.body, &inst_table, &instructions); | ||
| 1027 | |||
| 1028 | const fn_type = try self.emitType(src, module_fn.fn_type); | ||
| 1029 | |||
| 1030 | const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len); | ||
| 1031 | mem.copy(*Inst, arena_instrs, instructions.items); | ||
| 1032 | |||
| 1033 | const fn_inst = try self.arena.allocator.create(Inst.Fn); | ||
| 1034 | fn_inst.* = .{ | ||
| 1035 | .base = .{ .src = src, .tag = Inst.Fn.base_tag }, | ||
| 1036 | .positionals = .{ | ||
| 1037 | .fn_type = fn_type, | ||
| 1038 | .body = .{ .instructions = arena_instrs }, | ||
| 1039 | }, | ||
| 1040 | .kw_args = .{}, | ||
| 1041 | }; | ||
| 1042 | try self.decls.append(&fn_inst.base); | ||
| 1043 | return &fn_inst.base; | ||
| 1044 | }, | ||
| 1045 | else => |t| std.debug.panic("TODO implement emitTypedValue for {}", .{@tagName(t)}), | ||
| 1046 | } | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | fn emitTrivial(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst { | ||
| 1050 | const new_inst = try self.arena.allocator.create(T); | ||
| 1051 | new_inst.* = .{ | ||
| 1052 | .base = .{ .src = src, .tag = T.base_tag }, | ||
| 1053 | .positionals = .{}, | ||
| 1054 | .kw_args = .{}, | ||
| 1055 | }; | ||
| 1056 | return &new_inst.base; | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | fn emitBody( | ||
| 1060 | self: *EmitZIR, | ||
| 1061 | body: ir.Module.Body, | ||
| 1062 | inst_table: *std.AutoHashMap(*ir.Inst, *Inst), | ||
| 1063 | instructions: *std.ArrayList(*Inst), | ||
| 1064 | ) Allocator.Error!void { | ||
| 1065 | for (body.instructions) |inst| { | ||
| 1066 | const new_inst = switch (inst.tag) { | ||
| 1067 | .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint), | ||
| 1068 | .unreach => try self.emitTrivial(inst.src, Inst.Unreachable), | ||
| 1069 | .ret => try self.emitTrivial(inst.src, Inst.Return), | ||
| 1070 | .constant => unreachable, // excluded from function bodies | ||
| 1071 | .assembly => blk: { | ||
| 1072 | const old_inst = inst.cast(ir.Inst.Assembly).?; | ||
| 1073 | const new_inst = try self.arena.allocator.create(Inst.Asm); | ||
| 1074 | |||
| 1075 | const inputs = try self.arena.allocator.alloc(*Inst, old_inst.args.inputs.len); | ||
| 1076 | for (inputs) |*elem, i| { | ||
| 1077 | elem.* = try self.emitStringLiteral(inst.src, old_inst.args.inputs[i]); | ||
| 1078 | } | ||
| 1079 | |||
| 1080 | const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.args.clobbers.len); | ||
| 1081 | for (clobbers) |*elem, i| { | ||
| 1082 | elem.* = try self.emitStringLiteral(inst.src, old_inst.args.clobbers[i]); | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len); | ||
| 1086 | for (args) |*elem, i| { | ||
| 1087 | elem.* = try self.resolveInst(inst_table, old_inst.args.args[i]); | ||
| 1088 | } | ||
| 1089 | |||
| 1090 | new_inst.* = .{ | ||
| 1091 | .base = .{ .src = inst.src, .tag = Inst.Asm.base_tag }, | ||
| 1092 | .positionals = .{ | ||
| 1093 | .asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source), | ||
| 1094 | .return_type = try self.emitType(inst.src, inst.ty), | ||
| 1095 | }, | ||
| 1096 | .kw_args = .{ | ||
| 1097 | .@"volatile" = old_inst.args.is_volatile, | ||
| 1098 | .output = if (old_inst.args.output) |o| | ||
| 1099 | try self.emitStringLiteral(inst.src, o) | ||
| 1100 | else | ||
| 1101 | null, | ||
| 1102 | .inputs = inputs, | ||
| 1103 | .clobbers = clobbers, | ||
| 1104 | .args = args, | ||
| 1105 | }, | ||
| 1106 | }; | ||
| 1107 | break :blk &new_inst.base; | ||
| 1108 | }, | ||
| 1109 | .ptrtoint => blk: { | ||
| 1110 | const old_inst = inst.cast(ir.Inst.PtrToInt).?; | ||
| 1111 | const new_inst = try self.arena.allocator.create(Inst.PtrToInt); | ||
| 1112 | new_inst.* = .{ | ||
| 1113 | .base = .{ .src = inst.src, .tag = Inst.PtrToInt.base_tag }, | ||
| 1114 | .positionals = .{ | ||
| 1115 | .ptr = try self.resolveInst(inst_table, old_inst.args.ptr), | ||
| 1116 | }, | ||
| 1117 | .kw_args = .{}, | ||
| 1118 | }; | ||
| 1119 | break :blk &new_inst.base; | ||
| 1120 | }, | ||
| 1121 | .bitcast => blk: { | ||
| 1122 | const old_inst = inst.cast(ir.Inst.BitCast).?; | ||
| 1123 | const new_inst = try self.arena.allocator.create(Inst.BitCast); | ||
| 1124 | new_inst.* = .{ | ||
| 1125 | .base = .{ .src = inst.src, .tag = Inst.BitCast.base_tag }, | ||
| 1126 | .positionals = .{ | ||
| 1127 | .dest_type = try self.emitType(inst.src, inst.ty), | ||
| 1128 | .operand = try self.resolveInst(inst_table, old_inst.args.operand), | ||
| 1129 | }, | ||
| 1130 | .kw_args = .{}, | ||
| 1131 | }; | ||
| 1132 | break :blk &new_inst.base; | ||
| 1133 | }, | ||
| 1134 | .cmp => blk: { | ||
| 1135 | const old_inst = inst.cast(ir.Inst.Cmp).?; | ||
| 1136 | const new_inst = try self.arena.allocator.create(Inst.Cmp); | ||
| 1137 | new_inst.* = .{ | ||
| 1138 | .base = .{ .src = inst.src, .tag = Inst.Cmp.base_tag }, | ||
| 1139 | .positionals = .{ | ||
| 1140 | .lhs = try self.resolveInst(inst_table, old_inst.args.lhs), | ||
| 1141 | .rhs = try self.resolveInst(inst_table, old_inst.args.rhs), | ||
| 1142 | .op = old_inst.args.op, | ||
| 1143 | }, | ||
| 1144 | .kw_args = .{}, | ||
| 1145 | }; | ||
| 1146 | break :blk &new_inst.base; | ||
| 1147 | }, | ||
| 1148 | .condbr => blk: { | ||
| 1149 | const old_inst = inst.cast(ir.Inst.CondBr).?; | ||
| 1150 | |||
| 1151 | var true_body = std.ArrayList(*Inst).init(self.allocator); | ||
| 1152 | var false_body = std.ArrayList(*Inst).init(self.allocator); | ||
| 1153 | |||
| 1154 | defer true_body.deinit(); | ||
| 1155 | defer false_body.deinit(); | ||
| 1156 | |||
| 1157 | try self.emitBody(old_inst.args.true_body, inst_table, &true_body); | ||
| 1158 | try self.emitBody(old_inst.args.false_body, inst_table, &false_body); | ||
| 1159 | |||
| 1160 | const new_inst = try self.arena.allocator.create(Inst.CondBr); | ||
| 1161 | new_inst.* = .{ | ||
| 1162 | .base = .{ .src = inst.src, .tag = Inst.CondBr.base_tag }, | ||
| 1163 | .positionals = .{ | ||
| 1164 | .condition = try self.resolveInst(inst_table, old_inst.args.condition), | ||
| 1165 | .true_body = .{ .instructions = true_body.toOwnedSlice() }, | ||
| 1166 | .false_body = .{ .instructions = false_body.toOwnedSlice() }, | ||
| 1167 | }, | ||
| 1168 | .kw_args = .{}, | ||
| 1169 | }; | ||
| 1170 | break :blk &new_inst.base; | ||
| 1171 | }, | ||
| 1172 | .isnull => blk: { | ||
| 1173 | const old_inst = inst.cast(ir.Inst.IsNull).?; | ||
| 1174 | const new_inst = try self.arena.allocator.create(Inst.IsNull); | ||
| 1175 | new_inst.* = .{ | ||
| 1176 | .base = .{ .src = inst.src, .tag = Inst.IsNull.base_tag }, | ||
| 1177 | .positionals = .{ | ||
| 1178 | .operand = try self.resolveInst(inst_table, old_inst.args.operand), | ||
| 1179 | }, | ||
| 1180 | .kw_args = .{}, | ||
| 1181 | }; | ||
| 1182 | break :blk &new_inst.base; | ||
| 1183 | }, | ||
| 1184 | .isnonnull => blk: { | ||
| 1185 | const old_inst = inst.cast(ir.Inst.IsNonNull).?; | ||
| 1186 | const new_inst = try self.arena.allocator.create(Inst.IsNonNull); | ||
| 1187 | new_inst.* = .{ | ||
| 1188 | .base = .{ .src = inst.src, .tag = Inst.IsNonNull.base_tag }, | ||
| 1189 | .positionals = .{ | ||
| 1190 | .operand = try self.resolveInst(inst_table, old_inst.args.operand), | ||
| 1191 | }, | ||
| 1192 | .kw_args = .{}, | ||
| 1193 | }; | ||
| 1194 | break :blk &new_inst.base; | ||
| 1195 | }, | ||
| 1196 | }; | ||
| 1197 | try instructions.append(new_inst); | ||
| 1198 | try inst_table.putNoClobber(inst, new_inst); | ||
| 1199 | } | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | fn emitType(self: *EmitZIR, src: usize, ty: Type) Allocator.Error!*Inst { | ||
| 1203 | switch (ty.tag()) { | ||
| 1204 | .isize => return self.emitPrimitiveType(src, .isize), | ||
| 1205 | .usize => return self.emitPrimitiveType(src, .usize), | ||
| 1206 | .c_short => return self.emitPrimitiveType(src, .c_short), | ||
| 1207 | .c_ushort => return self.emitPrimitiveType(src, .c_ushort), | ||
| 1208 | .c_int => return self.emitPrimitiveType(src, .c_int), | ||
| 1209 | .c_uint => return self.emitPrimitiveType(src, .c_uint), | ||
| 1210 | .c_long => return self.emitPrimitiveType(src, .c_long), | ||
| 1211 | .c_ulong => return self.emitPrimitiveType(src, .c_ulong), | ||
| 1212 | .c_longlong => return self.emitPrimitiveType(src, .c_longlong), | ||
| 1213 | .c_ulonglong => return self.emitPrimitiveType(src, .c_ulonglong), | ||
| 1214 | .c_longdouble => return self.emitPrimitiveType(src, .c_longdouble), | ||
| 1215 | .c_void => return self.emitPrimitiveType(src, .c_void), | ||
| 1216 | .f16 => return self.emitPrimitiveType(src, .f16), | ||
| 1217 | .f32 => return self.emitPrimitiveType(src, .f32), | ||
| 1218 | .f64 => return self.emitPrimitiveType(src, .f64), | ||
| 1219 | .f128 => return self.emitPrimitiveType(src, .f128), | ||
| 1220 | .anyerror => return self.emitPrimitiveType(src, .anyerror), | ||
| 1221 | else => switch (ty.zigTypeTag()) { | ||
| 1222 | .Bool => return self.emitPrimitiveType(src, .bool), | ||
| 1223 | .Void => return self.emitPrimitiveType(src, .void), | ||
| 1224 | .NoReturn => return self.emitPrimitiveType(src, .noreturn), | ||
| 1225 | .Type => return self.emitPrimitiveType(src, .type), | ||
| 1226 | .ComptimeInt => return self.emitPrimitiveType(src, .comptime_int), | ||
| 1227 | .ComptimeFloat => return self.emitPrimitiveType(src, .comptime_float), | ||
| 1228 | .Fn => { | ||
| 1229 | const param_types = try self.allocator.alloc(Type, ty.fnParamLen()); | ||
| 1230 | defer self.allocator.free(param_types); | ||
| 1231 | |||
| 1232 | ty.fnParamTypes(param_types); | ||
| 1233 | const emitted_params = try self.arena.allocator.alloc(*Inst, param_types.len); | ||
| 1234 | for (param_types) |param_type, i| { | ||
| 1235 | emitted_params[i] = try self.emitType(src, param_type); | ||
| 1236 | } | ||
| 1237 | |||
| 1238 | const fntype_inst = try self.arena.allocator.create(Inst.FnType); | ||
| 1239 | fntype_inst.* = .{ | ||
| 1240 | .base = .{ .src = src, .tag = Inst.FnType.base_tag }, | ||
| 1241 | .positionals = .{ | ||
| 1242 | .param_types = emitted_params, | ||
| 1243 | .return_type = try self.emitType(src, ty.fnReturnType()), | ||
| 1244 | }, | ||
| 1245 | .kw_args = .{ | ||
| 1246 | .cc = ty.fnCallingConvention(), | ||
| 1247 | }, | ||
| 1248 | }; | ||
| 1249 | try self.decls.append(&fntype_inst.base); | ||
| 1250 | return &fntype_inst.base; | ||
| 1251 | }, | ||
| 1252 | else => std.debug.panic("TODO implement emitType for {}", .{ty}), | ||
| 1253 | }, | ||
| 1254 | } | ||
| 1255 | } | ||
| 1256 | |||
| 1257 | fn emitPrimitiveType(self: *EmitZIR, src: usize, tag: Inst.Primitive.BuiltinType) !*Inst { | ||
| 1258 | const primitive_inst = try self.arena.allocator.create(Inst.Primitive); | ||
| 1259 | primitive_inst.* = .{ | ||
| 1260 | .base = .{ .src = src, .tag = Inst.Primitive.base_tag }, | ||
| 1261 | .positionals = .{ | ||
| 1262 | .tag = tag, | ||
| 1263 | }, | ||
| 1264 | .kw_args = .{}, | ||
| 1265 | }; | ||
| 1266 | try self.decls.append(&primitive_inst.base); | ||
| 1267 | return &primitive_inst.base; | ||
| 1268 | } | ||
| 1269 | |||
| 1270 | fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Inst { | ||
| 1271 | const str_inst = try self.arena.allocator.create(Inst.Str); | ||
| 1272 | str_inst.* = .{ | ||
| 1273 | .base = .{ .src = src, .tag = Inst.Str.base_tag }, | ||
| 1274 | .positionals = .{ | ||
| 1275 | .bytes = str, | ||
| 1276 | }, | ||
| 1277 | .kw_args = .{}, | ||
| 1278 | }; | ||
| 1279 | try self.decls.append(&str_inst.base); | ||
| 1280 | return &str_inst.base; | ||
| 1281 | } | ||
| 1282 | }; | ||
src-self-hosted/libc_installation.zig-1| ... | @@ -1,6 +1,5 @@ | ... | @@ -1,6 +1,5 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); | 2 | const builtin = @import("builtin"); |
| 3 | const util = @import("util.zig"); | ||
| 4 | const Target = std.Target; | 3 | const Target = std.Target; |
| 5 | const fs = std.fs; | 4 | const fs = std.fs; |
| 6 | const Allocator = std.mem.Allocator; | 5 | const Allocator = std.mem.Allocator; |
src-self-hosted/link.zig+749-291| ... | @@ -3,56 +3,74 @@ const mem = std.mem; | ... | @@ -3,56 +3,74 @@ const mem = std.mem; |
| 3 | const assert = std.debug.assert; | 3 | const assert = std.debug.assert; |
| 4 | const Allocator = std.mem.Allocator; | 4 | const Allocator = std.mem.Allocator; |
| 5 | const ir = @import("ir.zig"); | 5 | const ir = @import("ir.zig"); |
| 6 | const Module = @import("Module.zig"); | ||
| 6 | const fs = std.fs; | 7 | const fs = std.fs; |
| 7 | const elf = std.elf; | 8 | const elf = std.elf; |
| 8 | const codegen = @import("codegen.zig"); | 9 | const codegen = @import("codegen.zig"); |
| 9 | 10 | ||
| 10 | const default_entry_addr = 0x8000000; | 11 | const default_entry_addr = 0x8000000; |
| 11 | 12 | ||
| 12 | pub const ErrorMsg = struct { | 13 | pub const Options = struct { |
| 13 | byte_offset: usize, | 14 | target: std.Target, |
| 14 | msg: []const u8, | 15 | output_mode: std.builtin.OutputMode, |
| 15 | }; | 16 | link_mode: std.builtin.LinkMode, |
| 16 | 17 | object_format: std.builtin.ObjectFormat, | |
| 17 | pub const Result = struct { | 18 | /// Used for calculating how much space to reserve for symbols in case the binary file |
| 18 | errors: []ErrorMsg, | 19 | /// does not already have a symbol table. |
| 19 | 20 | symbol_count_hint: u64 = 32, | |
| 20 | pub fn deinit(self: *Result, allocator: *mem.Allocator) void { | 21 | /// Used for calculating how much space to reserve for executable program code in case |
| 21 | for (self.errors) |err| { | 22 | /// the binary file deos not already have such a section. |
| 22 | allocator.free(err.msg); | 23 | program_code_size_hint: u64 = 256 * 1024, |
| 23 | } | ||
| 24 | allocator.free(self.errors); | ||
| 25 | self.* = undefined; | ||
| 26 | } | ||
| 27 | }; | 24 | }; |
| 28 | 25 | ||
| 29 | /// Attempts incremental linking, if the file already exists. | 26 | /// Attempts incremental linking, if the file already exists. |
| 30 | /// If incremental linking fails, falls back to truncating the file and rewriting it. | 27 | /// If incremental linking fails, falls back to truncating the file and rewriting it. |
| 31 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. | 28 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. |
| 32 | /// This operation is not atomic. | 29 | /// This operation is not atomic. |
| 33 | pub fn updateFilePath( | 30 | pub fn openBinFilePath( |
| 34 | allocator: *Allocator, | 31 | allocator: *Allocator, |
| 35 | module: ir.Module, | ||
| 36 | dir: fs.Dir, | 32 | dir: fs.Dir, |
| 37 | sub_path: []const u8, | 33 | sub_path: []const u8, |
| 38 | ) !Result { | 34 | options: Options, |
| 39 | const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(module) }); | 35 | ) !ElfFile { |
| 40 | defer file.close(); | 36 | const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) }); |
| 41 | 37 | errdefer file.close(); | |
| 42 | return updateFile(allocator, module, file); | 38 | |
| 39 | var bin_file = try openBinFile(allocator, file, options); | ||
| 40 | bin_file.owns_file_handle = true; | ||
| 41 | return bin_file; | ||
| 43 | } | 42 | } |
| 44 | 43 | ||
| 45 | /// Atomically overwrites the old file, if present. | 44 | /// Atomically overwrites the old file, if present. |
| 46 | pub fn writeFilePath( | 45 | pub fn writeFilePath( |
| 47 | allocator: *Allocator, | 46 | allocator: *Allocator, |
| 48 | module: ir.Module, | ||
| 49 | dir: fs.Dir, | 47 | dir: fs.Dir, |
| 50 | sub_path: []const u8, | 48 | sub_path: []const u8, |
| 51 | ) !Result { | 49 | module: Module, |
| 52 | const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(module) }); | 50 | errors: *std.ArrayList(Module.ErrorMsg), |
| 51 | ) !void { | ||
| 52 | const options: Options = .{ | ||
| 53 | .target = module.target, | ||
| 54 | .output_mode = module.output_mode, | ||
| 55 | .link_mode = module.link_mode, | ||
| 56 | .object_format = module.object_format, | ||
| 57 | .symbol_count_hint = module.decls.items.len, | ||
| 58 | }; | ||
| 59 | const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(options) }); | ||
| 53 | defer af.deinit(); | 60 | defer af.deinit(); |
| 54 | 61 | ||
| 55 | const result = try writeFile(allocator, module, af.file); | 62 | const elf_file = try createElfFile(allocator, af.file, options); |
| 63 | for (module.decls.items) |decl| { | ||
| 64 | try elf_file.updateDecl(module, decl, errors); | ||
| 65 | } | ||
| 66 | try elf_file.flush(); | ||
| 67 | if (elf_file.error_flags.no_entry_point_found) { | ||
| 68 | try errors.ensureCapacity(errors.items.len + 1); | ||
| 69 | errors.appendAssumeCapacity(.{ | ||
| 70 | .byte_offset = 0, | ||
| 71 | .msg = try std.fmt.allocPrint(errors.allocator, "no entry point found", .{}), | ||
| 72 | }); | ||
| 73 | } | ||
| 56 | try af.finish(); | 74 | try af.finish(); |
| 57 | return result; | 75 | return result; |
| 58 | } | 76 | } |
| ... | @@ -62,58 +80,126 @@ pub fn writeFilePath( | ... | @@ -62,58 +80,126 @@ pub fn writeFilePath( |
| 62 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | 80 | /// Returns an error if `file` is not already open with +read +write +seek abilities. |
| 63 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. | 81 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. |
| 64 | /// This operation is not atomic. | 82 | /// This operation is not atomic. |
| 65 | pub fn updateFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | 83 | pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !ElfFile { |
| 66 | return updateFileInner(allocator, module, file) catch |err| switch (err) { | 84 | return openBinFileInner(allocator, file, options) catch |err| switch (err) { |
| 67 | error.IncrFailed => { | 85 | error.IncrFailed => { |
| 68 | return writeFile(allocator, module, file); | 86 | return createElfFile(allocator, file, options); |
| 69 | }, | 87 | }, |
| 70 | else => |e| return e, | 88 | else => |e| return e, |
| 71 | }; | 89 | }; |
| 72 | } | 90 | } |
| 73 | 91 | ||
| 74 | const Update = struct { | 92 | pub const ElfFile = struct { |
| 75 | file: fs.File, | 93 | allocator: *Allocator, |
| 76 | module: *const ir.Module, | 94 | file: ?fs.File, |
| 95 | owns_file_handle: bool, | ||
| 96 | options: Options, | ||
| 97 | ptr_width: enum { p32, p64 }, | ||
| 77 | 98 | ||
| 78 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. | 99 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. |
| 79 | /// Same order as in the file. | 100 | /// Same order as in the file. |
| 80 | sections: std.ArrayList(elf.Elf64_Shdr), | 101 | sections: std.ArrayListUnmanaged(elf.Elf64_Shdr) = std.ArrayListUnmanaged(elf.Elf64_Shdr){}, |
| 81 | shdr_table_offset: ?u64, | 102 | shdr_table_offset: ?u64 = null, |
| 82 | 103 | ||
| 83 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. | 104 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. |
| 84 | /// Same order as in the file. | 105 | /// Same order as in the file. |
| 85 | program_headers: std.ArrayList(elf.Elf64_Phdr), | 106 | program_headers: std.ArrayListUnmanaged(elf.Elf64_Phdr) = std.ArrayListUnmanaged(elf.Elf64_Phdr){}, |
| 86 | phdr_table_offset: ?u64, | 107 | phdr_table_offset: ?u64 = null, |
| 87 | /// The index into the program headers of a PT_LOAD program header with Read and Execute flags | 108 | /// The index into the program headers of a PT_LOAD program header with Read and Execute flags |
| 88 | phdr_load_re_index: ?u16, | 109 | phdr_load_re_index: ?u16 = null, |
| 89 | entry_addr: ?u64, | 110 | /// The index into the program headers of the global offset table. |
| 111 | /// It needs PT_LOAD and Read flags. | ||
| 112 | phdr_got_index: ?u16 = null, | ||
| 113 | entry_addr: ?u64 = null, | ||
| 114 | |||
| 115 | shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){}, | ||
| 116 | shstrtab_index: ?u16 = null, | ||
| 117 | |||
| 118 | text_section_index: ?u16 = null, | ||
| 119 | symtab_section_index: ?u16 = null, | ||
| 120 | got_section_index: ?u16 = null, | ||
| 121 | |||
| 122 | /// The same order as in the file. ELF requires global symbols to all be after the | ||
| 123 | /// local symbols, they cannot be mixed. So we must buffer all the global symbols and | ||
| 124 | /// write them at the end. These are only the local symbols. The length of this array | ||
| 125 | /// is the value used for sh_info in the .symtab section. | ||
| 126 | local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, | ||
| 127 | global_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, | ||
| 128 | |||
| 129 | /// Same order as in the file. The value is the absolute vaddr value. | ||
| 130 | /// If the vaddr of the executable program header changes, the entire | ||
| 131 | /// offset table needs to be rewritten. | ||
| 132 | offset_table: std.ArrayListUnmanaged(u64) = std.ArrayListUnmanaged(u64){}, | ||
| 133 | |||
| 134 | phdr_table_dirty: bool = false, | ||
| 135 | shdr_table_dirty: bool = false, | ||
| 136 | shstrtab_dirty: bool = false, | ||
| 137 | offset_table_count_dirty: bool = false, | ||
| 138 | |||
| 139 | error_flags: ErrorFlags = ErrorFlags{}, | ||
| 140 | |||
| 141 | pub const ErrorFlags = struct { | ||
| 142 | no_entry_point_found: bool = false, | ||
| 143 | }; | ||
| 90 | 144 | ||
| 91 | shstrtab: std.ArrayList(u8), | 145 | pub const Decl = struct { |
| 92 | shstrtab_index: ?u16, | 146 | /// Each decl always gets a local symbol with the fully qualified name. |
| 147 | /// The vaddr and size are found here directly. | ||
| 148 | /// The file offset is found by computing the vaddr offset from the section vaddr | ||
| 149 | /// the symbol references, and adding that to the file offset of the section. | ||
| 150 | /// If this field is 0, it means the codegen size = 0 and there is no symbol or | ||
| 151 | /// offset table entry. | ||
| 152 | local_sym_index: u32, | ||
| 153 | /// This field is undefined for symbols with size = 0. | ||
| 154 | offset_table_index: u32, | ||
| 155 | |||
| 156 | pub const empty = Decl{ | ||
| 157 | .local_sym_index = 0, | ||
| 158 | .offset_table_index = undefined, | ||
| 159 | }; | ||
| 160 | }; | ||
| 93 | 161 | ||
| 94 | text_section_index: ?u16, | 162 | pub const Export = struct { |
| 95 | symtab_section_index: ?u16, | 163 | sym_index: ?u32 = null, |
| 164 | }; | ||
| 96 | 165 | ||
| 97 | /// The same order as in the file | 166 | pub fn deinit(self: *ElfFile) void { |
| 98 | symbols: std.ArrayList(elf.Elf64_Sym), | 167 | self.sections.deinit(self.allocator); |
| 168 | self.program_headers.deinit(self.allocator); | ||
| 169 | self.shstrtab.deinit(self.allocator); | ||
| 170 | self.local_symbols.deinit(self.allocator); | ||
| 171 | self.global_symbols.deinit(self.allocator); | ||
| 172 | self.offset_table.deinit(self.allocator); | ||
| 173 | if (self.owns_file_handle) { | ||
| 174 | if (self.file) |f| f.close(); | ||
| 175 | } | ||
| 176 | } | ||
| 99 | 177 | ||
| 100 | errors: std.ArrayList(ErrorMsg), | 178 | pub fn makeExecutable(self: *ElfFile) !void { |
| 179 | assert(self.owns_file_handle); | ||
| 180 | if (self.file) |f| { | ||
| 181 | f.close(); | ||
| 182 | self.file = null; | ||
| 183 | } | ||
| 184 | } | ||
| 101 | 185 | ||
| 102 | fn deinit(self: *Update) void { | 186 | pub fn makeWritable(self: *ElfFile, dir: fs.Dir, sub_path: []const u8) !void { |
| 103 | self.sections.deinit(); | 187 | assert(self.owns_file_handle); |
| 104 | self.program_headers.deinit(); | 188 | if (self.file != null) return; |
| 105 | self.shstrtab.deinit(); | 189 | self.file = try dir.createFile(sub_path, .{ |
| 106 | self.symbols.deinit(); | 190 | .truncate = false, |
| 107 | self.errors.deinit(); | 191 | .read = true, |
| 192 | .mode = determineMode(self.options), | ||
| 193 | }); | ||
| 108 | } | 194 | } |
| 109 | 195 | ||
| 110 | // `expand_num / expand_den` is the factor of padding when allocation | 196 | // `alloc_num / alloc_den` is the factor of padding when allocation |
| 111 | const alloc_num = 4; | 197 | const alloc_num = 4; |
| 112 | const alloc_den = 3; | 198 | const alloc_den = 3; |
| 113 | 199 | ||
| 114 | /// Returns end pos of collision, if any. | 200 | /// Returns end pos of collision, if any. |
| 115 | fn detectAllocCollision(self: *Update, start: u64, size: u64) ?u64 { | 201 | fn detectAllocCollision(self: *ElfFile, start: u64, size: u64) ?u64 { |
| 116 | const small_ptr = self.module.target.cpu.arch.ptrBitWidth() == 32; | 202 | const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32; |
| 117 | const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); | 203 | const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); |
| 118 | if (start < ehdr_size) | 204 | if (start < ehdr_size) |
| 119 | return ehdr_size; | 205 | return ehdr_size; |
| ... | @@ -157,7 +243,7 @@ const Update = struct { | ... | @@ -157,7 +243,7 @@ const Update = struct { |
| 157 | return null; | 243 | return null; |
| 158 | } | 244 | } |
| 159 | 245 | ||
| 160 | fn allocatedSize(self: *Update, start: u64) u64 { | 246 | fn allocatedSize(self: *ElfFile, start: u64) u64 { |
| 161 | var min_pos: u64 = std.math.maxInt(u64); | 247 | var min_pos: u64 = std.math.maxInt(u64); |
| 162 | if (self.shdr_table_offset) |off| { | 248 | if (self.shdr_table_offset) |off| { |
| 163 | if (off > start and off < min_pos) min_pos = off; | 249 | if (off > start and off < min_pos) min_pos = off; |
| ... | @@ -176,7 +262,7 @@ const Update = struct { | ... | @@ -176,7 +262,7 @@ const Update = struct { |
| 176 | return min_pos - start; | 262 | return min_pos - start; |
| 177 | } | 263 | } |
| 178 | 264 | ||
| 179 | fn findFreeSpace(self: *Update, object_size: u64, min_alignment: u16) u64 { | 265 | fn findFreeSpace(self: *ElfFile, object_size: u64, min_alignment: u16) u64 { |
| 180 | var start: u64 = 0; | 266 | var start: u64 = 0; |
| 181 | while (self.detectAllocCollision(start, object_size)) |item_end| { | 267 | while (self.detectAllocCollision(start, object_size)) |item_end| { |
| 182 | start = mem.alignForwardGeneric(u64, item_end, min_alignment); | 268 | start = mem.alignForwardGeneric(u64, item_end, min_alignment); |
| ... | @@ -184,73 +270,86 @@ const Update = struct { | ... | @@ -184,73 +270,86 @@ const Update = struct { |
| 184 | return start; | 270 | return start; |
| 185 | } | 271 | } |
| 186 | 272 | ||
| 187 | fn makeString(self: *Update, bytes: []const u8) !u32 { | 273 | fn makeString(self: *ElfFile, bytes: []const u8) !u32 { |
| 274 | try self.shstrtab.ensureCapacity(self.allocator, self.shstrtab.items.len + bytes.len + 1); | ||
| 188 | const result = self.shstrtab.items.len; | 275 | const result = self.shstrtab.items.len; |
| 189 | try self.shstrtab.appendSlice(bytes); | 276 | self.shstrtab.appendSliceAssumeCapacity(bytes); |
| 190 | try self.shstrtab.append(0); | 277 | self.shstrtab.appendAssumeCapacity(0); |
| 191 | return @intCast(u32, result); | 278 | return @intCast(u32, result); |
| 192 | } | 279 | } |
| 193 | 280 | ||
| 194 | fn perform(self: *Update) !void { | 281 | fn getString(self: *ElfFile, str_off: u32) []const u8 { |
| 195 | const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) { | 282 | assert(str_off < self.shstrtab.items.len); |
| 196 | 32 => .p32, | 283 | return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off)); |
| 197 | 64 => .p64, | 284 | } |
| 198 | else => return error.UnsupportedArchitecture, | 285 | |
| 199 | }; | 286 | fn updateString(self: *ElfFile, old_str_off: u32, new_name: []const u8) !u32 { |
| 200 | const small_ptr = switch (ptr_width) { | 287 | const existing_name = self.getString(old_str_off); |
| 288 | if (mem.eql(u8, existing_name, new_name)) { | ||
| 289 | return old_str_off; | ||
| 290 | } | ||
| 291 | return self.makeString(new_name); | ||
| 292 | } | ||
| 293 | |||
| 294 | pub fn populateMissingMetadata(self: *ElfFile) !void { | ||
| 295 | const small_ptr = switch (self.ptr_width) { | ||
| 201 | .p32 => true, | 296 | .p32 => true, |
| 202 | .p64 => false, | 297 | .p64 => false, |
| 203 | }; | 298 | }; |
| 204 | // This means the entire read-only executable program code needs to be rewritten. | 299 | const ptr_size: u8 = switch (self.ptr_width) { |
| 205 | var phdr_load_re_dirty = false; | 300 | .p32 => 4, |
| 206 | var phdr_table_dirty = false; | 301 | .p64 => 8, |
| 207 | var shdr_table_dirty = false; | 302 | }; |
| 208 | var shstrtab_dirty = false; | ||
| 209 | var symtab_dirty = false; | ||
| 210 | |||
| 211 | if (self.phdr_load_re_index == null) { | 303 | if (self.phdr_load_re_index == null) { |
| 212 | self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len); | 304 | self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len); |
| 213 | const file_size = 256 * 1024; | 305 | const file_size = self.options.program_code_size_hint; |
| 214 | const p_align = 0x1000; | 306 | const p_align = 0x1000; |
| 215 | const off = self.findFreeSpace(file_size, p_align); | 307 | const off = self.findFreeSpace(file_size, p_align); |
| 216 | //std.debug.warn("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | 308 | //std.debug.warn("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); |
| 217 | try self.program_headers.append(.{ | 309 | try self.program_headers.append(self.allocator, .{ |
| 218 | .p_type = elf.PT_LOAD, | 310 | .p_type = elf.PT_LOAD, |
| 219 | .p_offset = off, | 311 | .p_offset = off, |
| 220 | .p_filesz = file_size, | 312 | .p_filesz = file_size, |
| 221 | .p_vaddr = default_entry_addr, | 313 | .p_vaddr = default_entry_addr, |
| 222 | .p_paddr = default_entry_addr, | 314 | .p_paddr = default_entry_addr, |
| 223 | .p_memsz = 0, | 315 | .p_memsz = file_size, |
| 224 | .p_align = p_align, | 316 | .p_align = p_align, |
| 225 | .p_flags = elf.PF_X | elf.PF_R, | 317 | .p_flags = elf.PF_X | elf.PF_R, |
| 226 | }); | 318 | }); |
| 227 | self.entry_addr = null; | 319 | self.entry_addr = null; |
| 228 | phdr_load_re_dirty = true; | 320 | self.phdr_table_dirty = true; |
| 229 | phdr_table_dirty = true; | 321 | } |
| 230 | } | 322 | if (self.phdr_got_index == null) { |
| 231 | if (self.sections.items.len == 0) { | 323 | self.phdr_got_index = @intCast(u16, self.program_headers.items.len); |
| 232 | // There must always be a null section in index 0 | 324 | const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint; |
| 233 | try self.sections.append(.{ | 325 | // We really only need ptr alignment but since we are using PROGBITS, linux requires |
| 234 | .sh_name = 0, | 326 | // page align. |
| 235 | .sh_type = elf.SHT_NULL, | 327 | const p_align = 0x1000; |
| 236 | .sh_flags = 0, | 328 | const off = self.findFreeSpace(file_size, p_align); |
| 237 | .sh_addr = 0, | 329 | //std.debug.warn("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); |
| 238 | .sh_offset = 0, | 330 | // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. |
| 239 | .sh_size = 0, | 331 | // we'll need to re-use that function anyway, in case the GOT grows and overlaps something |
| 240 | .sh_link = 0, | 332 | // else in virtual memory. |
| 241 | .sh_info = 0, | 333 | const default_got_addr = 0x4000000; |
| 242 | .sh_addralign = 0, | 334 | try self.program_headers.append(self.allocator, .{ |
| 243 | .sh_entsize = 0, | 335 | .p_type = elf.PT_LOAD, |
| 336 | .p_offset = off, | ||
| 337 | .p_filesz = file_size, | ||
| 338 | .p_vaddr = default_got_addr, | ||
| 339 | .p_paddr = default_got_addr, | ||
| 340 | .p_memsz = file_size, | ||
| 341 | .p_align = p_align, | ||
| 342 | .p_flags = elf.PF_R, | ||
| 244 | }); | 343 | }); |
| 245 | shdr_table_dirty = true; | 344 | self.phdr_table_dirty = true; |
| 246 | } | 345 | } |
| 247 | if (self.shstrtab_index == null) { | 346 | if (self.shstrtab_index == null) { |
| 248 | self.shstrtab_index = @intCast(u16, self.sections.items.len); | 347 | self.shstrtab_index = @intCast(u16, self.sections.items.len); |
| 249 | assert(self.shstrtab.items.len == 0); | 348 | assert(self.shstrtab.items.len == 0); |
| 250 | try self.shstrtab.append(0); // need a 0 at position 0 | 349 | try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0 |
| 251 | const off = self.findFreeSpace(self.shstrtab.items.len, 1); | 350 | const off = self.findFreeSpace(self.shstrtab.items.len, 1); |
| 252 | //std.debug.warn("found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); | 351 | //std.debug.warn("found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); |
| 253 | try self.sections.append(.{ | 352 | try self.sections.append(self.allocator, .{ |
| 254 | .sh_name = try self.makeString(".shstrtab"), | 353 | .sh_name = try self.makeString(".shstrtab"), |
| 255 | .sh_type = elf.SHT_STRTAB, | 354 | .sh_type = elf.SHT_STRTAB, |
| 256 | .sh_flags = 0, | 355 | .sh_flags = 0, |
| ... | @@ -262,14 +361,14 @@ const Update = struct { | ... | @@ -262,14 +361,14 @@ const Update = struct { |
| 262 | .sh_addralign = 1, | 361 | .sh_addralign = 1, |
| 263 | .sh_entsize = 0, | 362 | .sh_entsize = 0, |
| 264 | }); | 363 | }); |
| 265 | shstrtab_dirty = true; | 364 | self.shstrtab_dirty = true; |
| 266 | shdr_table_dirty = true; | 365 | self.shdr_table_dirty = true; |
| 267 | } | 366 | } |
| 268 | if (self.text_section_index == null) { | 367 | if (self.text_section_index == null) { |
| 269 | self.text_section_index = @intCast(u16, self.sections.items.len); | 368 | self.text_section_index = @intCast(u16, self.sections.items.len); |
| 270 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | 369 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; |
| 271 | 370 | ||
| 272 | try self.sections.append(.{ | 371 | try self.sections.append(self.allocator, .{ |
| 273 | .sh_name = try self.makeString(".text"), | 372 | .sh_name = try self.makeString(".text"), |
| 274 | .sh_type = elf.SHT_PROGBITS, | 373 | .sh_type = elf.SHT_PROGBITS, |
| 275 | .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, | 374 | .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, |
| ... | @@ -281,17 +380,35 @@ const Update = struct { | ... | @@ -281,17 +380,35 @@ const Update = struct { |
| 281 | .sh_addralign = phdr.p_align, | 380 | .sh_addralign = phdr.p_align, |
| 282 | .sh_entsize = 0, | 381 | .sh_entsize = 0, |
| 283 | }); | 382 | }); |
| 284 | shdr_table_dirty = true; | 383 | self.shdr_table_dirty = true; |
| 384 | } | ||
| 385 | if (self.got_section_index == null) { | ||
| 386 | self.got_section_index = @intCast(u16, self.sections.items.len); | ||
| 387 | const phdr = &self.program_headers.items[self.phdr_got_index.?]; | ||
| 388 | |||
| 389 | try self.sections.append(self.allocator, .{ | ||
| 390 | .sh_name = try self.makeString(".got"), | ||
| 391 | .sh_type = elf.SHT_PROGBITS, | ||
| 392 | .sh_flags = elf.SHF_ALLOC, | ||
| 393 | .sh_addr = phdr.p_vaddr, | ||
| 394 | .sh_offset = phdr.p_offset, | ||
| 395 | .sh_size = phdr.p_filesz, | ||
| 396 | .sh_link = 0, | ||
| 397 | .sh_info = 0, | ||
| 398 | .sh_addralign = phdr.p_align, | ||
| 399 | .sh_entsize = 0, | ||
| 400 | }); | ||
| 401 | self.shdr_table_dirty = true; | ||
| 285 | } | 402 | } |
| 286 | if (self.symtab_section_index == null) { | 403 | if (self.symtab_section_index == null) { |
| 287 | self.symtab_section_index = @intCast(u16, self.sections.items.len); | 404 | self.symtab_section_index = @intCast(u16, self.sections.items.len); |
| 288 | const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); | 405 | const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); |
| 289 | const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); | 406 | const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); |
| 290 | const file_size = self.module.exports.len * each_size; | 407 | const file_size = self.options.symbol_count_hint * each_size; |
| 291 | const off = self.findFreeSpace(file_size, min_align); | 408 | const off = self.findFreeSpace(file_size, min_align); |
| 292 | //std.debug.warn("found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | 409 | //std.debug.warn("found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); |
| 293 | 410 | ||
| 294 | try self.sections.append(.{ | 411 | try self.sections.append(self.allocator, .{ |
| 295 | .sh_name = try self.makeString(".symtab"), | 412 | .sh_name = try self.makeString(".symtab"), |
| 296 | .sh_type = elf.SHT_SYMTAB, | 413 | .sh_type = elf.SHT_SYMTAB, |
| 297 | .sh_flags = 0, | 414 | .sh_flags = 0, |
| ... | @@ -300,42 +417,56 @@ const Update = struct { | ... | @@ -300,42 +417,56 @@ const Update = struct { |
| 300 | .sh_size = file_size, | 417 | .sh_size = file_size, |
| 301 | // The section header index of the associated string table. | 418 | // The section header index of the associated string table. |
| 302 | .sh_link = self.shstrtab_index.?, | 419 | .sh_link = self.shstrtab_index.?, |
| 303 | .sh_info = @intCast(u32, self.module.exports.len), | 420 | .sh_info = @intCast(u32, self.local_symbols.items.len), |
| 304 | .sh_addralign = min_align, | 421 | .sh_addralign = min_align, |
| 305 | .sh_entsize = each_size, | 422 | .sh_entsize = each_size, |
| 306 | }); | 423 | }); |
| 307 | symtab_dirty = true; | 424 | self.shdr_table_dirty = true; |
| 308 | shdr_table_dirty = true; | 425 | try self.writeSymbol(0); |
| 309 | } | 426 | } |
| 310 | const shsize: u64 = switch (ptr_width) { | 427 | const shsize: u64 = switch (self.ptr_width) { |
| 311 | .p32 => @sizeOf(elf.Elf32_Shdr), | 428 | .p32 => @sizeOf(elf.Elf32_Shdr), |
| 312 | .p64 => @sizeOf(elf.Elf64_Shdr), | 429 | .p64 => @sizeOf(elf.Elf64_Shdr), |
| 313 | }; | 430 | }; |
| 314 | const shalign: u16 = switch (ptr_width) { | 431 | const shalign: u16 = switch (self.ptr_width) { |
| 315 | .p32 => @alignOf(elf.Elf32_Shdr), | 432 | .p32 => @alignOf(elf.Elf32_Shdr), |
| 316 | .p64 => @alignOf(elf.Elf64_Shdr), | 433 | .p64 => @alignOf(elf.Elf64_Shdr), |
| 317 | }; | 434 | }; |
| 318 | if (self.shdr_table_offset == null) { | 435 | if (self.shdr_table_offset == null) { |
| 319 | self.shdr_table_offset = self.findFreeSpace(self.sections.items.len * shsize, shalign); | 436 | self.shdr_table_offset = self.findFreeSpace(self.sections.items.len * shsize, shalign); |
| 320 | shdr_table_dirty = true; | 437 | self.shdr_table_dirty = true; |
| 321 | } | 438 | } |
| 322 | const phsize: u64 = switch (ptr_width) { | 439 | const phsize: u64 = switch (self.ptr_width) { |
| 323 | .p32 => @sizeOf(elf.Elf32_Phdr), | 440 | .p32 => @sizeOf(elf.Elf32_Phdr), |
| 324 | .p64 => @sizeOf(elf.Elf64_Phdr), | 441 | .p64 => @sizeOf(elf.Elf64_Phdr), |
| 325 | }; | 442 | }; |
| 326 | const phalign: u16 = switch (ptr_width) { | 443 | const phalign: u16 = switch (self.ptr_width) { |
| 327 | .p32 => @alignOf(elf.Elf32_Phdr), | 444 | .p32 => @alignOf(elf.Elf32_Phdr), |
| 328 | .p64 => @alignOf(elf.Elf64_Phdr), | 445 | .p64 => @alignOf(elf.Elf64_Phdr), |
| 329 | }; | 446 | }; |
| 330 | if (self.phdr_table_offset == null) { | 447 | if (self.phdr_table_offset == null) { |
| 331 | self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign); | 448 | self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign); |
| 332 | phdr_table_dirty = true; | 449 | self.phdr_table_dirty = true; |
| 333 | } | 450 | } |
| 334 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 451 | } |
| 335 | 452 | ||
| 336 | try self.writeCodeAndSymbols(phdr_table_dirty, shdr_table_dirty); | 453 | /// Commit pending changes and write headers. |
| 454 | pub fn flush(self: *ElfFile) !void { | ||
| 455 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | ||
| 337 | 456 | ||
| 338 | if (phdr_table_dirty) { | 457 | // Unfortunately these have to be buffered and done at the end because ELF does not allow |
| 458 | // mixing local and global symbols within a symbol table. | ||
| 459 | try self.writeAllGlobalSymbols(); | ||
| 460 | |||
| 461 | if (self.phdr_table_dirty) { | ||
| 462 | const phsize: u64 = switch (self.ptr_width) { | ||
| 463 | .p32 => @sizeOf(elf.Elf32_Phdr), | ||
| 464 | .p64 => @sizeOf(elf.Elf64_Phdr), | ||
| 465 | }; | ||
| 466 | const phalign: u16 = switch (self.ptr_width) { | ||
| 467 | .p32 => @alignOf(elf.Elf32_Phdr), | ||
| 468 | .p64 => @alignOf(elf.Elf64_Phdr), | ||
| 469 | }; | ||
| 339 | const allocated_size = self.allocatedSize(self.phdr_table_offset.?); | 470 | const allocated_size = self.allocatedSize(self.phdr_table_offset.?); |
| 340 | const needed_size = self.program_headers.items.len * phsize; | 471 | const needed_size = self.program_headers.items.len * phsize; |
| 341 | 472 | ||
| ... | @@ -344,11 +475,10 @@ const Update = struct { | ... | @@ -344,11 +475,10 @@ const Update = struct { |
| 344 | self.phdr_table_offset = self.findFreeSpace(needed_size, phalign); | 475 | self.phdr_table_offset = self.findFreeSpace(needed_size, phalign); |
| 345 | } | 476 | } |
| 346 | 477 | ||
| 347 | const allocator = self.program_headers.allocator; | 478 | switch (self.ptr_width) { |
| 348 | switch (ptr_width) { | ||
| 349 | .p32 => { | 479 | .p32 => { |
| 350 | const buf = try allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len); | 480 | const buf = try self.allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len); |
| 351 | defer allocator.free(buf); | 481 | defer self.allocator.free(buf); |
| 352 | 482 | ||
| 353 | for (buf) |*phdr, i| { | 483 | for (buf) |*phdr, i| { |
| 354 | phdr.* = progHeaderTo32(self.program_headers.items[i]); | 484 | phdr.* = progHeaderTo32(self.program_headers.items[i]); |
| ... | @@ -356,11 +486,11 @@ const Update = struct { | ... | @@ -356,11 +486,11 @@ const Update = struct { |
| 356 | bswapAllFields(elf.Elf32_Phdr, phdr); | 486 | bswapAllFields(elf.Elf32_Phdr, phdr); |
| 357 | } | 487 | } |
| 358 | } | 488 | } |
| 359 | try self.file.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); | 489 | try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); |
| 360 | }, | 490 | }, |
| 361 | .p64 => { | 491 | .p64 => { |
| 362 | const buf = try allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len); | 492 | const buf = try self.allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len); |
| 363 | defer allocator.free(buf); | 493 | defer self.allocator.free(buf); |
| 364 | 494 | ||
| 365 | for (buf) |*phdr, i| { | 495 | for (buf) |*phdr, i| { |
| 366 | phdr.* = self.program_headers.items[i]; | 496 | phdr.* = self.program_headers.items[i]; |
| ... | @@ -368,14 +498,15 @@ const Update = struct { | ... | @@ -368,14 +498,15 @@ const Update = struct { |
| 368 | bswapAllFields(elf.Elf64_Phdr, phdr); | 498 | bswapAllFields(elf.Elf64_Phdr, phdr); |
| 369 | } | 499 | } |
| 370 | } | 500 | } |
| 371 | try self.file.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); | 501 | try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); |
| 372 | }, | 502 | }, |
| 373 | } | 503 | } |
| 504 | self.phdr_table_dirty = false; | ||
| 374 | } | 505 | } |
| 375 | 506 | ||
| 376 | { | 507 | { |
| 377 | const shstrtab_sect = &self.sections.items[self.shstrtab_index.?]; | 508 | const shstrtab_sect = &self.sections.items[self.shstrtab_index.?]; |
| 378 | if (shstrtab_dirty or self.shstrtab.items.len != shstrtab_sect.sh_size) { | 509 | if (self.shstrtab_dirty or self.shstrtab.items.len != shstrtab_sect.sh_size) { |
| 379 | const allocated_size = self.allocatedSize(shstrtab_sect.sh_offset); | 510 | const allocated_size = self.allocatedSize(shstrtab_sect.sh_offset); |
| 380 | const needed_size = self.shstrtab.items.len; | 511 | const needed_size = self.shstrtab.items.len; |
| 381 | 512 | ||
| ... | @@ -386,27 +517,35 @@ const Update = struct { | ... | @@ -386,27 +517,35 @@ const Update = struct { |
| 386 | shstrtab_sect.sh_size = needed_size; | 517 | shstrtab_sect.sh_size = needed_size; |
| 387 | //std.debug.warn("shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); | 518 | //std.debug.warn("shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); |
| 388 | 519 | ||
| 389 | try self.file.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); | 520 | try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); |
| 390 | if (!shdr_table_dirty) { | 521 | if (!self.shdr_table_dirty) { |
| 391 | // Then it won't get written with the others and we need to do it. | 522 | // Then it won't get written with the others and we need to do it. |
| 392 | try self.writeSectHeader(self.shstrtab_index.?); | 523 | try self.writeSectHeader(self.shstrtab_index.?); |
| 393 | } | 524 | } |
| 525 | self.shstrtab_dirty = false; | ||
| 394 | } | 526 | } |
| 395 | } | 527 | } |
| 396 | if (shdr_table_dirty) { | 528 | if (self.shdr_table_dirty) { |
| 529 | const shsize: u64 = switch (self.ptr_width) { | ||
| 530 | .p32 => @sizeOf(elf.Elf32_Shdr), | ||
| 531 | .p64 => @sizeOf(elf.Elf64_Shdr), | ||
| 532 | }; | ||
| 533 | const shalign: u16 = switch (self.ptr_width) { | ||
| 534 | .p32 => @alignOf(elf.Elf32_Shdr), | ||
| 535 | .p64 => @alignOf(elf.Elf64_Shdr), | ||
| 536 | }; | ||
| 397 | const allocated_size = self.allocatedSize(self.shdr_table_offset.?); | 537 | const allocated_size = self.allocatedSize(self.shdr_table_offset.?); |
| 398 | const needed_size = self.sections.items.len * phsize; | 538 | const needed_size = self.sections.items.len * shsize; |
| 399 | 539 | ||
| 400 | if (needed_size > allocated_size) { | 540 | if (needed_size > allocated_size) { |
| 401 | self.shdr_table_offset = null; // free the space | 541 | self.shdr_table_offset = null; // free the space |
| 402 | self.shdr_table_offset = self.findFreeSpace(needed_size, phalign); | 542 | self.shdr_table_offset = self.findFreeSpace(needed_size, shalign); |
| 403 | } | 543 | } |
| 404 | 544 | ||
| 405 | const allocator = self.sections.allocator; | 545 | switch (self.ptr_width) { |
| 406 | switch (ptr_width) { | ||
| 407 | .p32 => { | 546 | .p32 => { |
| 408 | const buf = try allocator.alloc(elf.Elf32_Shdr, self.sections.items.len); | 547 | const buf = try self.allocator.alloc(elf.Elf32_Shdr, self.sections.items.len); |
| 409 | defer allocator.free(buf); | 548 | defer self.allocator.free(buf); |
| 410 | 549 | ||
| 411 | for (buf) |*shdr, i| { | 550 | for (buf) |*shdr, i| { |
| 412 | shdr.* = sectHeaderTo32(self.sections.items[i]); | 551 | shdr.* = sectHeaderTo32(self.sections.items[i]); |
| ... | @@ -414,11 +553,11 @@ const Update = struct { | ... | @@ -414,11 +553,11 @@ const Update = struct { |
| 414 | bswapAllFields(elf.Elf32_Shdr, shdr); | 553 | bswapAllFields(elf.Elf32_Shdr, shdr); |
| 415 | } | 554 | } |
| 416 | } | 555 | } |
| 417 | try self.file.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); | 556 | try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); |
| 418 | }, | 557 | }, |
| 419 | .p64 => { | 558 | .p64 => { |
| 420 | const buf = try allocator.alloc(elf.Elf64_Shdr, self.sections.items.len); | 559 | const buf = try self.allocator.alloc(elf.Elf64_Shdr, self.sections.items.len); |
| 421 | defer allocator.free(buf); | 560 | defer self.allocator.free(buf); |
| 422 | 561 | ||
| 423 | for (buf) |*shdr, i| { | 562 | for (buf) |*shdr, i| { |
| 424 | shdr.* = self.sections.items[i]; | 563 | shdr.* = self.sections.items[i]; |
| ... | @@ -427,42 +566,42 @@ const Update = struct { | ... | @@ -427,42 +566,42 @@ const Update = struct { |
| 427 | bswapAllFields(elf.Elf64_Shdr, shdr); | 566 | bswapAllFields(elf.Elf64_Shdr, shdr); |
| 428 | } | 567 | } |
| 429 | } | 568 | } |
| 430 | try self.file.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); | 569 | try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); |
| 431 | }, | 570 | }, |
| 432 | } | 571 | } |
| 572 | self.shdr_table_dirty = false; | ||
| 433 | } | 573 | } |
| 434 | if (self.entry_addr == null and self.module.output_mode == .Exe) { | 574 | if (self.entry_addr == null and self.options.output_mode == .Exe) { |
| 435 | const msg = try std.fmt.allocPrint(self.errors.allocator, "no entry point found", .{}); | 575 | self.error_flags.no_entry_point_found = true; |
| 436 | errdefer self.errors.allocator.free(msg); | ||
| 437 | try self.errors.append(.{ | ||
| 438 | .byte_offset = 0, | ||
| 439 | .msg = msg, | ||
| 440 | }); | ||
| 441 | } else { | 576 | } else { |
| 577 | self.error_flags.no_entry_point_found = false; | ||
| 442 | try self.writeElfHeader(); | 578 | try self.writeElfHeader(); |
| 443 | } | 579 | } |
| 444 | // TODO find end pos and truncate | 580 | // TODO find end pos and truncate |
| 581 | |||
| 582 | // The point of flush() is to commit changes, so nothing should be dirty after this. | ||
| 583 | assert(!self.phdr_table_dirty); | ||
| 584 | assert(!self.shdr_table_dirty); | ||
| 585 | assert(!self.shstrtab_dirty); | ||
| 586 | assert(!self.offset_table_count_dirty); | ||
| 587 | const syms_sect = &self.sections.items[self.symtab_section_index.?]; | ||
| 588 | assert(syms_sect.sh_info == self.local_symbols.items.len); | ||
| 445 | } | 589 | } |
| 446 | 590 | ||
| 447 | fn writeElfHeader(self: *Update) !void { | 591 | fn writeElfHeader(self: *ElfFile) !void { |
| 448 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; | 592 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; |
| 449 | 593 | ||
| 450 | var index: usize = 0; | 594 | var index: usize = 0; |
| 451 | hdr_buf[0..4].* = "\x7fELF".*; | 595 | hdr_buf[0..4].* = "\x7fELF".*; |
| 452 | index += 4; | 596 | index += 4; |
| 453 | 597 | ||
| 454 | const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) { | 598 | hdr_buf[index] = switch (self.ptr_width) { |
| 455 | 32 => .p32, | ||
| 456 | 64 => .p64, | ||
| 457 | else => return error.UnsupportedArchitecture, | ||
| 458 | }; | ||
| 459 | hdr_buf[index] = switch (ptr_width) { | ||
| 460 | .p32 => elf.ELFCLASS32, | 599 | .p32 => elf.ELFCLASS32, |
| 461 | .p64 => elf.ELFCLASS64, | 600 | .p64 => elf.ELFCLASS64, |
| 462 | }; | 601 | }; |
| 463 | index += 1; | 602 | index += 1; |
| 464 | 603 | ||
| 465 | const endian = self.module.target.cpu.arch.endian(); | 604 | const endian = self.options.target.cpu.arch.endian(); |
| 466 | hdr_buf[index] = switch (endian) { | 605 | hdr_buf[index] = switch (endian) { |
| 467 | .Little => elf.ELFDATA2LSB, | 606 | .Little => elf.ELFDATA2LSB, |
| 468 | .Big => elf.ELFDATA2MSB, | 607 | .Big => elf.ELFDATA2MSB, |
| ... | @@ -480,10 +619,10 @@ const Update = struct { | ... | @@ -480,10 +619,10 @@ const Update = struct { |
| 480 | 619 | ||
| 481 | assert(index == 16); | 620 | assert(index == 16); |
| 482 | 621 | ||
| 483 | const elf_type = switch (self.module.output_mode) { | 622 | const elf_type = switch (self.options.output_mode) { |
| 484 | .Exe => elf.ET.EXEC, | 623 | .Exe => elf.ET.EXEC, |
| 485 | .Obj => elf.ET.REL, | 624 | .Obj => elf.ET.REL, |
| 486 | .Lib => switch (self.module.link_mode) { | 625 | .Lib => switch (self.options.link_mode) { |
| 487 | .Static => elf.ET.REL, | 626 | .Static => elf.ET.REL, |
| 488 | .Dynamic => elf.ET.DYN, | 627 | .Dynamic => elf.ET.DYN, |
| 489 | }, | 628 | }, |
| ... | @@ -491,7 +630,7 @@ const Update = struct { | ... | @@ -491,7 +630,7 @@ const Update = struct { |
| 491 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian); | 630 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian); |
| 492 | index += 2; | 631 | index += 2; |
| 493 | 632 | ||
| 494 | const machine = self.module.target.cpu.arch.toElfMachine(); | 633 | const machine = self.options.target.cpu.arch.toElfMachine(); |
| 495 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian); | 634 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian); |
| 496 | index += 2; | 635 | index += 2; |
| 497 | 636 | ||
| ... | @@ -501,7 +640,7 @@ const Update = struct { | ... | @@ -501,7 +640,7 @@ const Update = struct { |
| 501 | 640 | ||
| 502 | const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?; | 641 | const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?; |
| 503 | 642 | ||
| 504 | switch (ptr_width) { | 643 | switch (self.ptr_width) { |
| 505 | .p32 => { | 644 | .p32 => { |
| 506 | mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian); | 645 | mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian); |
| 507 | index += 4; | 646 | index += 4; |
| ... | @@ -533,14 +672,14 @@ const Update = struct { | ... | @@ -533,14 +672,14 @@ const Update = struct { |
| 533 | mem.writeInt(u32, hdr_buf[index..][0..4], e_flags, endian); | 672 | mem.writeInt(u32, hdr_buf[index..][0..4], e_flags, endian); |
| 534 | index += 4; | 673 | index += 4; |
| 535 | 674 | ||
| 536 | const e_ehsize: u16 = switch (ptr_width) { | 675 | const e_ehsize: u16 = switch (self.ptr_width) { |
| 537 | .p32 => @sizeOf(elf.Elf32_Ehdr), | 676 | .p32 => @sizeOf(elf.Elf32_Ehdr), |
| 538 | .p64 => @sizeOf(elf.Elf64_Ehdr), | 677 | .p64 => @sizeOf(elf.Elf64_Ehdr), |
| 539 | }; | 678 | }; |
| 540 | mem.writeInt(u16, hdr_buf[index..][0..2], e_ehsize, endian); | 679 | mem.writeInt(u16, hdr_buf[index..][0..2], e_ehsize, endian); |
| 541 | index += 2; | 680 | index += 2; |
| 542 | 681 | ||
| 543 | const e_phentsize: u16 = switch (ptr_width) { | 682 | const e_phentsize: u16 = switch (self.ptr_width) { |
| 544 | .p32 => @sizeOf(elf.Elf32_Phdr), | 683 | .p32 => @sizeOf(elf.Elf32_Phdr), |
| 545 | .p64 => @sizeOf(elf.Elf64_Phdr), | 684 | .p64 => @sizeOf(elf.Elf64_Phdr), |
| 546 | }; | 685 | }; |
| ... | @@ -551,7 +690,7 @@ const Update = struct { | ... | @@ -551,7 +690,7 @@ const Update = struct { |
| 551 | mem.writeInt(u16, hdr_buf[index..][0..2], e_phnum, endian); | 690 | mem.writeInt(u16, hdr_buf[index..][0..2], e_phnum, endian); |
| 552 | index += 2; | 691 | index += 2; |
| 553 | 692 | ||
| 554 | const e_shentsize: u16 = switch (ptr_width) { | 693 | const e_shentsize: u16 = switch (self.ptr_width) { |
| 555 | .p32 => @sizeOf(elf.Elf32_Shdr), | 694 | .p32 => @sizeOf(elf.Elf32_Shdr), |
| 556 | .p64 => @sizeOf(elf.Elf64_Shdr), | 695 | .p64 => @sizeOf(elf.Elf64_Shdr), |
| 557 | }; | 696 | }; |
| ... | @@ -567,186 +706,463 @@ const Update = struct { | ... | @@ -567,186 +706,463 @@ const Update = struct { |
| 567 | 706 | ||
| 568 | assert(index == e_ehsize); | 707 | assert(index == e_ehsize); |
| 569 | 708 | ||
| 570 | try self.file.pwriteAll(hdr_buf[0..index], 0); | 709 | try self.file.?.pwriteAll(hdr_buf[0..index], 0); |
| 571 | } | 710 | } |
| 572 | 711 | ||
| 573 | fn writeCodeAndSymbols(self: *Update, phdr_table_dirty: bool, shdr_table_dirty: bool) !void { | 712 | const AllocatedBlock = struct { |
| 574 | // index 0 is always a null symbol | 713 | vaddr: u64, |
| 575 | try self.symbols.resize(1); | 714 | file_offset: u64, |
| 576 | self.symbols.items[0] = .{ | 715 | size_capacity: u64, |
| 716 | }; | ||
| 717 | |||
| 718 | fn allocateTextBlock(self: *ElfFile, new_block_size: u64, alignment: u64) !AllocatedBlock { | ||
| 719 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | ||
| 720 | const shdr = &self.sections.items[self.text_section_index.?]; | ||
| 721 | |||
| 722 | // TODO Also detect virtual address collisions. | ||
| 723 | const text_capacity = self.allocatedSize(shdr.sh_offset); | ||
| 724 | // TODO instead of looping here, maintain a free list and a pointer to the end. | ||
| 725 | var last_start: u64 = phdr.p_vaddr; | ||
| 726 | var last_size: u64 = 0; | ||
| 727 | for (self.local_symbols.items) |sym| { | ||
| 728 | if (sym.st_value + sym.st_size > last_start + last_size) { | ||
| 729 | last_start = sym.st_value; | ||
| 730 | last_size = sym.st_size; | ||
| 731 | } | ||
| 732 | } | ||
| 733 | const end_vaddr = last_start + (last_size * alloc_num / alloc_den); | ||
| 734 | const aligned_start_vaddr = mem.alignForwardGeneric(u64, end_vaddr, alignment); | ||
| 735 | const needed_size = (aligned_start_vaddr + new_block_size) - phdr.p_vaddr; | ||
| 736 | if (needed_size > text_capacity) { | ||
| 737 | // Must move the entire text section. | ||
| 738 | const new_offset = self.findFreeSpace(needed_size, 0x1000); | ||
| 739 | const text_size = (last_start + last_size) - phdr.p_vaddr; | ||
| 740 | const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, text_size); | ||
| 741 | if (amt != text_size) return error.InputOutput; | ||
| 742 | shdr.sh_offset = new_offset; | ||
| 743 | } | ||
| 744 | // Now that we know the code size, we need to update the program header for executable code | ||
| 745 | shdr.sh_size = needed_size; | ||
| 746 | phdr.p_memsz = needed_size; | ||
| 747 | phdr.p_filesz = needed_size; | ||
| 748 | |||
| 749 | self.phdr_table_dirty = true; // TODO look into making only the one program header dirty | ||
| 750 | self.shdr_table_dirty = true; // TODO look into making only the one section dirty | ||
| 751 | |||
| 752 | return AllocatedBlock{ | ||
| 753 | .vaddr = aligned_start_vaddr, | ||
| 754 | .file_offset = shdr.sh_offset + (aligned_start_vaddr - phdr.p_vaddr), | ||
| 755 | .size_capacity = text_capacity - needed_size, | ||
| 756 | }; | ||
| 757 | } | ||
| 758 | |||
| 759 | fn findAllocatedTextBlock(self: *ElfFile, sym: elf.Elf64_Sym) AllocatedBlock { | ||
| 760 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | ||
| 761 | const shdr = &self.sections.items[self.text_section_index.?]; | ||
| 762 | |||
| 763 | // Find the next sym after this one. | ||
| 764 | // TODO look into using a hash map to speed up perf. | ||
| 765 | const text_capacity = self.allocatedSize(shdr.sh_offset); | ||
| 766 | var next_vaddr_start = phdr.p_vaddr + text_capacity; | ||
| 767 | for (self.local_symbols.items) |elem| { | ||
| 768 | if (elem.st_value < sym.st_value) continue; | ||
| 769 | if (elem.st_value < next_vaddr_start) next_vaddr_start = elem.st_value; | ||
| 770 | } | ||
| 771 | return .{ | ||
| 772 | .vaddr = sym.st_value, | ||
| 773 | .file_offset = shdr.sh_offset + (sym.st_value - phdr.p_vaddr), | ||
| 774 | .size_capacity = next_vaddr_start - sym.st_value, | ||
| 775 | }; | ||
| 776 | } | ||
| 777 | |||
| 778 | pub fn allocateDeclIndexes(self: *ElfFile, decl: *Module.Decl) !void { | ||
| 779 | if (decl.link.local_sym_index != 0) return; | ||
| 780 | |||
| 781 | try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1); | ||
| 782 | try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1); | ||
| 783 | const local_sym_index = self.local_symbols.items.len; | ||
| 784 | const offset_table_index = self.offset_table.items.len; | ||
| 785 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | ||
| 786 | |||
| 787 | self.local_symbols.appendAssumeCapacity(.{ | ||
| 577 | .st_name = 0, | 788 | .st_name = 0, |
| 578 | .st_info = 0, | 789 | .st_info = 0, |
| 579 | .st_other = 0, | 790 | .st_other = 0, |
| 580 | .st_shndx = 0, | 791 | .st_shndx = 0, |
| 581 | .st_value = 0, | 792 | .st_value = phdr.p_vaddr, |
| 582 | .st_size = 0, | 793 | .st_size = 0, |
| 794 | }); | ||
| 795 | errdefer self.local_symbols.shrink(self.allocator, self.local_symbols.items.len - 1); | ||
| 796 | self.offset_table.appendAssumeCapacity(0); | ||
| 797 | errdefer self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1); | ||
| 798 | |||
| 799 | self.offset_table_count_dirty = true; | ||
| 800 | |||
| 801 | decl.link = .{ | ||
| 802 | .local_sym_index = @intCast(u32, local_sym_index), | ||
| 803 | .offset_table_index = @intCast(u32, offset_table_index), | ||
| 583 | }; | 804 | }; |
| 805 | } | ||
| 584 | 806 | ||
| 585 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | 807 | pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void { |
| 586 | var vaddr: u64 = phdr.p_vaddr; | 808 | var code_buffer = std.ArrayList(u8).init(self.allocator); |
| 587 | var file_off: u64 = phdr.p_offset; | 809 | defer code_buffer.deinit(); |
| 588 | 810 | ||
| 589 | var code = std.ArrayList(u8).init(self.sections.allocator); | 811 | const typed_value = decl.typed_value.most_recent.typed_value; |
| 590 | defer code.deinit(); | 812 | const code = switch (try codegen.generateSymbol(self, decl.src, typed_value, &code_buffer)) { |
| 591 | 813 | .externally_managed => |x| x, | |
| 592 | for (self.module.exports) |exp| { | 814 | .appended => code_buffer.items, |
| 593 | code.shrink(0); | 815 | .fail => |em| { |
| 594 | var symbol = try codegen.generateSymbol(exp.typed_value, self.module.*, &code); | 816 | decl.analysis = .codegen_failure; |
| 595 | defer symbol.deinit(code.allocator); | 817 | _ = try module.failed_decls.put(decl, em); |
| 596 | if (symbol.errors.len != 0) { | 818 | return; |
| 597 | for (symbol.errors) |err| { | 819 | }, |
| 598 | const msg = try mem.dupe(self.errors.allocator, u8, err.msg); | 820 | }; |
| 599 | errdefer self.errors.allocator.free(msg); | ||
| 600 | try self.errors.append(.{ | ||
| 601 | .byte_offset = err.byte_offset, | ||
| 602 | .msg = msg, | ||
| 603 | }); | ||
| 604 | } | ||
| 605 | continue; | ||
| 606 | } | ||
| 607 | try self.file.pwriteAll(code.items, file_off); | ||
| 608 | 821 | ||
| 609 | if (mem.eql(u8, exp.name, "_start")) { | 822 | const required_alignment = typed_value.ty.abiAlignment(self.options.target); |
| 610 | self.entry_addr = vaddr; | 823 | |
| 611 | } | 824 | const file_offset = blk: { |
| 612 | (try self.symbols.addOne()).* = .{ | 825 | const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) { |
| 613 | .st_name = try self.makeString(exp.name), | 826 | .Fn => elf.STT_FUNC, |
| 614 | .st_info = (elf.STB_LOCAL << 4) | elf.STT_FUNC, | 827 | else => elf.STT_OBJECT, |
| 615 | .st_other = 0, | ||
| 616 | .st_shndx = self.text_section_index.?, | ||
| 617 | .st_value = vaddr, | ||
| 618 | .st_size = code.items.len, | ||
| 619 | }; | 828 | }; |
| 620 | vaddr += code.items.len; | ||
| 621 | } | ||
| 622 | 829 | ||
| 623 | { | 830 | if (decl.link.local_sym_index != 0) { |
| 624 | // Now that we know the code size, we need to update the program header for executable code | 831 | const local_sym = &self.local_symbols.items[decl.link.local_sym_index]; |
| 625 | phdr.p_memsz = vaddr - phdr.p_vaddr; | 832 | const existing_block = self.findAllocatedTextBlock(local_sym.*); |
| 626 | phdr.p_filesz = phdr.p_memsz; | 833 | const need_realloc = local_sym.st_size == 0 or |
| 834 | code.len > existing_block.size_capacity or | ||
| 835 | !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); | ||
| 836 | // TODO check for collision with another symbol | ||
| 837 | const file_offset = if (need_realloc) fo: { | ||
| 838 | const new_block = try self.allocateTextBlock(code.len, required_alignment); | ||
| 839 | local_sym.st_value = new_block.vaddr; | ||
| 840 | self.offset_table.items[decl.link.offset_table_index] = new_block.vaddr; | ||
| 841 | |||
| 842 | //std.debug.warn("{}: writing got index {}=0x{x}\n", .{ | ||
| 843 | // decl.name, | ||
| 844 | // decl.link.offset_table_index, | ||
| 845 | // self.offset_table.items[decl.link.offset_table_index], | ||
| 846 | //}); | ||
| 847 | try self.writeOffsetTableEntry(decl.link.offset_table_index); | ||
| 848 | |||
| 849 | break :fo new_block.file_offset; | ||
| 850 | } else existing_block.file_offset; | ||
| 851 | local_sym.st_size = code.len; | ||
| 852 | local_sym.st_name = try self.updateString(local_sym.st_name, mem.spanZ(decl.name)); | ||
| 853 | local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits; | ||
| 854 | local_sym.st_other = 0; | ||
| 855 | local_sym.st_shndx = self.text_section_index.?; | ||
| 856 | // TODO this write could be avoided if no fields of the symbol were changed. | ||
| 857 | try self.writeSymbol(decl.link.local_sym_index); | ||
| 858 | |||
| 859 | //std.debug.warn("updating {} at vaddr 0x{x}\n", .{ decl.name, local_sym.st_value }); | ||
| 860 | break :blk file_offset; | ||
| 861 | } else { | ||
| 862 | try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1); | ||
| 863 | try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1); | ||
| 864 | const decl_name = mem.spanZ(decl.name); | ||
| 865 | const name_str_index = try self.makeString(decl_name); | ||
| 866 | const new_block = try self.allocateTextBlock(code.len, required_alignment); | ||
| 867 | const local_sym_index = self.local_symbols.items.len; | ||
| 868 | const offset_table_index = self.offset_table.items.len; | ||
| 869 | |||
| 870 | //std.debug.warn("add symbol for {} at vaddr 0x{x}, size {}\n", .{ decl.name, new_block.vaddr, code.len }); | ||
| 871 | self.local_symbols.appendAssumeCapacity(.{ | ||
| 872 | .st_name = name_str_index, | ||
| 873 | .st_info = (elf.STB_LOCAL << 4) | stt_bits, | ||
| 874 | .st_other = 0, | ||
| 875 | .st_shndx = self.text_section_index.?, | ||
| 876 | .st_value = new_block.vaddr, | ||
| 877 | .st_size = code.len, | ||
| 878 | }); | ||
| 879 | errdefer self.local_symbols.shrink(self.allocator, self.local_symbols.items.len - 1); | ||
| 880 | self.offset_table.appendAssumeCapacity(new_block.vaddr); | ||
| 881 | errdefer self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1); | ||
| 882 | |||
| 883 | self.offset_table_count_dirty = true; | ||
| 884 | |||
| 885 | try self.writeSymbol(local_sym_index); | ||
| 886 | try self.writeOffsetTableEntry(offset_table_index); | ||
| 887 | |||
| 888 | decl.link = .{ | ||
| 889 | .local_sym_index = @intCast(u32, local_sym_index), | ||
| 890 | .offset_table_index = @intCast(u32, offset_table_index), | ||
| 891 | }; | ||
| 892 | |||
| 893 | //std.debug.warn("writing new {} at vaddr 0x{x}\n", .{ decl.name, new_block.vaddr }); | ||
| 894 | break :blk new_block.file_offset; | ||
| 895 | } | ||
| 896 | }; | ||
| 897 | |||
| 898 | try self.file.?.pwriteAll(code, file_offset); | ||
| 627 | 899 | ||
| 628 | const shdr = &self.sections.items[self.text_section_index.?]; | 900 | // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. |
| 629 | shdr.sh_size = phdr.p_filesz; | 901 | const decl_exports = module.decl_exports.getValue(decl) orelse &[0]*Module.Export{}; |
| 902 | return self.updateDeclExports(module, decl, decl_exports); | ||
| 903 | } | ||
| 630 | 904 | ||
| 631 | if (!phdr_table_dirty) { | 905 | /// Must be called only after a successful call to `updateDecl`. |
| 632 | // Then it won't get written with the others and we need to do it. | 906 | pub fn updateDeclExports( |
| 633 | try self.writeProgHeader(self.phdr_load_re_index.?); | 907 | self: *ElfFile, |
| 908 | module: *Module, | ||
| 909 | decl: *const Module.Decl, | ||
| 910 | exports: []const *Module.Export, | ||
| 911 | ) !void { | ||
| 912 | try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len); | ||
| 913 | const typed_value = decl.typed_value.most_recent.typed_value; | ||
| 914 | if (decl.link.local_sym_index == 0) return; | ||
| 915 | const decl_sym = self.local_symbols.items[decl.link.local_sym_index]; | ||
| 916 | |||
| 917 | for (exports) |exp| { | ||
| 918 | if (exp.options.section) |section_name| { | ||
| 919 | if (!mem.eql(u8, section_name, ".text")) { | ||
| 920 | try module.failed_exports.ensureCapacity(module.failed_exports.size + 1); | ||
| 921 | module.failed_exports.putAssumeCapacityNoClobber( | ||
| 922 | exp, | ||
| 923 | try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}), | ||
| 924 | ); | ||
| 925 | continue; | ||
| 926 | } | ||
| 634 | } | 927 | } |
| 635 | if (!shdr_table_dirty) { | 928 | const stb_bits: u8 = switch (exp.options.linkage) { |
| 636 | // Then it won't get written with the others and we need to do it. | 929 | .Internal => elf.STB_LOCAL, |
| 637 | try self.writeSectHeader(self.text_section_index.?); | 930 | .Strong => blk: { |
| 931 | if (mem.eql(u8, exp.options.name, "_start")) { | ||
| 932 | self.entry_addr = decl_sym.st_value; | ||
| 933 | } | ||
| 934 | break :blk elf.STB_GLOBAL; | ||
| 935 | }, | ||
| 936 | .Weak => elf.STB_WEAK, | ||
| 937 | .LinkOnce => { | ||
| 938 | try module.failed_exports.ensureCapacity(module.failed_exports.size + 1); | ||
| 939 | module.failed_exports.putAssumeCapacityNoClobber( | ||
| 940 | exp, | ||
| 941 | try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), | ||
| 942 | ); | ||
| 943 | continue; | ||
| 944 | }, | ||
| 945 | }; | ||
| 946 | const stt_bits: u8 = @truncate(u4, decl_sym.st_info); | ||
| 947 | if (exp.link.sym_index) |i| { | ||
| 948 | const sym = &self.global_symbols.items[i]; | ||
| 949 | sym.* = .{ | ||
| 950 | .st_name = try self.updateString(sym.st_name, exp.options.name), | ||
| 951 | .st_info = (stb_bits << 4) | stt_bits, | ||
| 952 | .st_other = 0, | ||
| 953 | .st_shndx = self.text_section_index.?, | ||
| 954 | .st_value = decl_sym.st_value, | ||
| 955 | .st_size = decl_sym.st_size, | ||
| 956 | }; | ||
| 957 | } else { | ||
| 958 | const name = try self.makeString(exp.options.name); | ||
| 959 | const i = self.global_symbols.items.len; | ||
| 960 | self.global_symbols.appendAssumeCapacity(.{ | ||
| 961 | .st_name = name, | ||
| 962 | .st_info = (stb_bits << 4) | stt_bits, | ||
| 963 | .st_other = 0, | ||
| 964 | .st_shndx = self.text_section_index.?, | ||
| 965 | .st_value = decl_sym.st_value, | ||
| 966 | .st_size = decl_sym.st_size, | ||
| 967 | }); | ||
| 968 | errdefer self.global_symbols.shrink(self.allocator, self.global_symbols.items.len - 1); | ||
| 969 | |||
| 970 | exp.link.sym_index = @intCast(u32, i); | ||
| 638 | } | 971 | } |
| 639 | } | 972 | } |
| 640 | |||
| 641 | return self.writeSymbols(); | ||
| 642 | } | 973 | } |
| 643 | 974 | ||
| 644 | fn writeProgHeader(self: *Update, index: usize) !void { | 975 | fn writeProgHeader(self: *ElfFile, index: usize) !void { |
| 645 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 976 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 646 | const offset = self.program_headers.items[index].p_offset; | 977 | const offset = self.program_headers.items[index].p_offset; |
| 647 | switch (self.module.target.cpu.arch.ptrBitWidth()) { | 978 | switch (self.options.target.cpu.arch.ptrBitWidth()) { |
| 648 | 32 => { | 979 | 32 => { |
| 649 | var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; | 980 | var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; |
| 650 | if (foreign_endian) { | 981 | if (foreign_endian) { |
| 651 | bswapAllFields(elf.Elf32_Phdr, &phdr[0]); | 982 | bswapAllFields(elf.Elf32_Phdr, &phdr[0]); |
| 652 | } | 983 | } |
| 653 | return self.file.pwriteAll(mem.sliceAsBytes(&phdr), offset); | 984 | return self.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); |
| 654 | }, | 985 | }, |
| 655 | 64 => { | 986 | 64 => { |
| 656 | var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; | 987 | var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; |
| 657 | if (foreign_endian) { | 988 | if (foreign_endian) { |
| 658 | bswapAllFields(elf.Elf64_Phdr, &phdr[0]); | 989 | bswapAllFields(elf.Elf64_Phdr, &phdr[0]); |
| 659 | } | 990 | } |
| 660 | return self.file.pwriteAll(mem.sliceAsBytes(&phdr), offset); | 991 | return self.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); |
| 661 | }, | 992 | }, |
| 662 | else => return error.UnsupportedArchitecture, | 993 | else => return error.UnsupportedArchitecture, |
| 663 | } | 994 | } |
| 664 | } | 995 | } |
| 665 | 996 | ||
| 666 | fn writeSectHeader(self: *Update, index: usize) !void { | 997 | fn writeSectHeader(self: *ElfFile, index: usize) !void { |
| 667 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 998 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 668 | const offset = self.sections.items[index].sh_offset; | 999 | const offset = self.sections.items[index].sh_offset; |
| 669 | switch (self.module.target.cpu.arch.ptrBitWidth()) { | 1000 | switch (self.options.target.cpu.arch.ptrBitWidth()) { |
| 670 | 32 => { | 1001 | 32 => { |
| 671 | var shdr: [1]elf.Elf32_Shdr = undefined; | 1002 | var shdr: [1]elf.Elf32_Shdr = undefined; |
| 672 | shdr[0] = sectHeaderTo32(self.sections.items[index]); | 1003 | shdr[0] = sectHeaderTo32(self.sections.items[index]); |
| 673 | if (foreign_endian) { | 1004 | if (foreign_endian) { |
| 674 | bswapAllFields(elf.Elf32_Shdr, &shdr[0]); | 1005 | bswapAllFields(elf.Elf32_Shdr, &shdr[0]); |
| 675 | } | 1006 | } |
| 676 | return self.file.pwriteAll(mem.sliceAsBytes(&shdr), offset); | 1007 | return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); |
| 677 | }, | 1008 | }, |
| 678 | 64 => { | 1009 | 64 => { |
| 679 | var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; | 1010 | var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; |
| 680 | if (foreign_endian) { | 1011 | if (foreign_endian) { |
| 681 | bswapAllFields(elf.Elf64_Shdr, &shdr[0]); | 1012 | bswapAllFields(elf.Elf64_Shdr, &shdr[0]); |
| 682 | } | 1013 | } |
| 683 | return self.file.pwriteAll(mem.sliceAsBytes(&shdr), offset); | 1014 | return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); |
| 684 | }, | 1015 | }, |
| 685 | else => return error.UnsupportedArchitecture, | 1016 | else => return error.UnsupportedArchitecture, |
| 686 | } | 1017 | } |
| 687 | } | 1018 | } |
| 688 | 1019 | ||
| 689 | fn writeSymbols(self: *Update) !void { | 1020 | fn writeOffsetTableEntry(self: *ElfFile, index: usize) !void { |
| 690 | const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) { | 1021 | const shdr = &self.sections.items[self.got_section_index.?]; |
| 691 | 32 => .p32, | 1022 | const phdr = &self.program_headers.items[self.phdr_got_index.?]; |
| 692 | 64 => .p64, | 1023 | const entry_size: u16 = switch (self.ptr_width) { |
| 693 | else => return error.UnsupportedArchitecture, | 1024 | .p32 => 4, |
| 1025 | .p64 => 8, | ||
| 694 | }; | 1026 | }; |
| 695 | const small_ptr = ptr_width == .p32; | 1027 | if (self.offset_table_count_dirty) { |
| 696 | const syms_sect = &self.sections.items[self.symtab_section_index.?]; | 1028 | // TODO Also detect virtual address collisions. |
| 697 | const sym_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); | 1029 | const allocated_size = self.allocatedSize(shdr.sh_offset); |
| 698 | const sym_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); | 1030 | const needed_size = self.local_symbols.items.len * entry_size; |
| 1031 | if (needed_size > allocated_size) { | ||
| 1032 | // Must move the entire got section. | ||
| 1033 | const new_offset = self.findFreeSpace(needed_size, entry_size); | ||
| 1034 | const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, shdr.sh_size); | ||
| 1035 | if (amt != shdr.sh_size) return error.InputOutput; | ||
| 1036 | shdr.sh_offset = new_offset; | ||
| 1037 | } | ||
| 1038 | shdr.sh_size = needed_size; | ||
| 1039 | phdr.p_memsz = needed_size; | ||
| 1040 | phdr.p_filesz = needed_size; | ||
| 1041 | |||
| 1042 | self.shdr_table_dirty = true; // TODO look into making only the one section dirty | ||
| 1043 | self.phdr_table_dirty = true; // TODO look into making only the one program header dirty | ||
| 1044 | |||
| 1045 | self.offset_table_count_dirty = false; | ||
| 1046 | } | ||
| 1047 | const endian = self.options.target.cpu.arch.endian(); | ||
| 1048 | const off = shdr.sh_offset + @as(u64, entry_size) * index; | ||
| 1049 | switch (self.ptr_width) { | ||
| 1050 | .p32 => { | ||
| 1051 | var buf: [4]u8 = undefined; | ||
| 1052 | mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); | ||
| 1053 | try self.file.?.pwriteAll(&buf, off); | ||
| 1054 | }, | ||
| 1055 | .p64 => { | ||
| 1056 | var buf: [8]u8 = undefined; | ||
| 1057 | mem.writeInt(u64, &buf, self.offset_table.items[index], endian); | ||
| 1058 | try self.file.?.pwriteAll(&buf, off); | ||
| 1059 | }, | ||
| 1060 | } | ||
| 1061 | } | ||
| 699 | 1062 | ||
| 700 | const allocated_size = self.allocatedSize(syms_sect.sh_offset); | 1063 | fn writeSymbol(self: *ElfFile, index: usize) !void { |
| 701 | const needed_size = self.symbols.items.len * sym_size; | 1064 | const syms_sect = &self.sections.items[self.symtab_section_index.?]; |
| 702 | if (needed_size > allocated_size) { | 1065 | // Make sure we are not pointlessly writing symbol data that will have to get relocated |
| 703 | syms_sect.sh_size = 0; // free the space | 1066 | // due to running out of space. |
| 704 | syms_sect.sh_offset = self.findFreeSpace(needed_size, sym_align); | 1067 | if (self.local_symbols.items.len != syms_sect.sh_info) { |
| 705 | //std.debug.warn("moved symtab to 0x{x} to 0x{x}\n", .{ syms_sect.sh_offset, syms_sect.sh_offset + needed_size }); | 1068 | const sym_size: u64 = switch (self.ptr_width) { |
| 1069 | .p32 => @sizeOf(elf.Elf32_Sym), | ||
| 1070 | .p64 => @sizeOf(elf.Elf64_Sym), | ||
| 1071 | }; | ||
| 1072 | const sym_align: u16 = switch (self.ptr_width) { | ||
| 1073 | .p32 => @alignOf(elf.Elf32_Sym), | ||
| 1074 | .p64 => @alignOf(elf.Elf64_Sym), | ||
| 1075 | }; | ||
| 1076 | const needed_size = (self.local_symbols.items.len + self.global_symbols.items.len) * sym_size; | ||
| 1077 | if (needed_size > self.allocatedSize(syms_sect.sh_offset)) { | ||
| 1078 | // Move all the symbols to a new file location. | ||
| 1079 | const new_offset = self.findFreeSpace(needed_size, sym_align); | ||
| 1080 | const existing_size = @as(u64, syms_sect.sh_info) * sym_size; | ||
| 1081 | const amt = try self.file.?.copyRangeAll(syms_sect.sh_offset, self.file.?, new_offset, existing_size); | ||
| 1082 | if (amt != existing_size) return error.InputOutput; | ||
| 1083 | syms_sect.sh_offset = new_offset; | ||
| 1084 | } | ||
| 1085 | syms_sect.sh_info = @intCast(u32, self.local_symbols.items.len); | ||
| 1086 | syms_sect.sh_size = needed_size; // anticipating adding the global symbols later | ||
| 1087 | self.shdr_table_dirty = true; // TODO look into only writing one section | ||
| 706 | } | 1088 | } |
| 1089 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | ||
| 1090 | switch (self.ptr_width) { | ||
| 1091 | .p32 => { | ||
| 1092 | var sym = [1]elf.Elf32_Sym{ | ||
| 1093 | .{ | ||
| 1094 | .st_name = self.local_symbols.items[index].st_name, | ||
| 1095 | .st_value = @intCast(u32, self.local_symbols.items[index].st_value), | ||
| 1096 | .st_size = @intCast(u32, self.local_symbols.items[index].st_size), | ||
| 1097 | .st_info = self.local_symbols.items[index].st_info, | ||
| 1098 | .st_other = self.local_symbols.items[index].st_other, | ||
| 1099 | .st_shndx = self.local_symbols.items[index].st_shndx, | ||
| 1100 | }, | ||
| 1101 | }; | ||
| 1102 | if (foreign_endian) { | ||
| 1103 | bswapAllFields(elf.Elf32_Sym, &sym[0]); | ||
| 1104 | } | ||
| 1105 | const off = syms_sect.sh_offset + @sizeOf(elf.Elf32_Sym) * index; | ||
| 1106 | try self.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); | ||
| 1107 | }, | ||
| 1108 | .p64 => { | ||
| 1109 | var sym = [1]elf.Elf64_Sym{self.local_symbols.items[index]}; | ||
| 1110 | if (foreign_endian) { | ||
| 1111 | bswapAllFields(elf.Elf64_Sym, &sym[0]); | ||
| 1112 | } | ||
| 1113 | const off = syms_sect.sh_offset + @sizeOf(elf.Elf64_Sym) * index; | ||
| 1114 | try self.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); | ||
| 1115 | }, | ||
| 1116 | } | ||
| 1117 | } | ||
| 1118 | |||
| 1119 | fn writeAllGlobalSymbols(self: *ElfFile) !void { | ||
| 1120 | const syms_sect = &self.sections.items[self.symtab_section_index.?]; | ||
| 1121 | const sym_size: u64 = switch (self.ptr_width) { | ||
| 1122 | .p32 => @sizeOf(elf.Elf32_Sym), | ||
| 1123 | .p64 => @sizeOf(elf.Elf64_Sym), | ||
| 1124 | }; | ||
| 707 | //std.debug.warn("symtab start=0x{x} end=0x{x}\n", .{ syms_sect.sh_offset, syms_sect.sh_offset + needed_size }); | 1125 | //std.debug.warn("symtab start=0x{x} end=0x{x}\n", .{ syms_sect.sh_offset, syms_sect.sh_offset + needed_size }); |
| 708 | syms_sect.sh_size = needed_size; | 1126 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 709 | syms_sect.sh_info = @intCast(u32, self.symbols.items.len); | 1127 | const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size; |
| 710 | const allocator = self.symbols.allocator; | 1128 | switch (self.ptr_width) { |
| 711 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | ||
| 712 | switch (ptr_width) { | ||
| 713 | .p32 => { | 1129 | .p32 => { |
| 714 | const buf = try allocator.alloc(elf.Elf32_Sym, self.symbols.items.len); | 1130 | const buf = try self.allocator.alloc(elf.Elf32_Sym, self.global_symbols.items.len); |
| 715 | defer allocator.free(buf); | 1131 | defer self.allocator.free(buf); |
| 716 | 1132 | ||
| 717 | for (buf) |*sym, i| { | 1133 | for (buf) |*sym, i| { |
| 718 | sym.* = .{ | 1134 | sym.* = .{ |
| 719 | .st_name = self.symbols.items[i].st_name, | 1135 | .st_name = self.global_symbols.items[i].st_name, |
| 720 | .st_value = @intCast(u32, self.symbols.items[i].st_value), | 1136 | .st_value = @intCast(u32, self.global_symbols.items[i].st_value), |
| 721 | .st_size = @intCast(u32, self.symbols.items[i].st_size), | 1137 | .st_size = @intCast(u32, self.global_symbols.items[i].st_size), |
| 722 | .st_info = self.symbols.items[i].st_info, | 1138 | .st_info = self.global_symbols.items[i].st_info, |
| 723 | .st_other = self.symbols.items[i].st_other, | 1139 | .st_other = self.global_symbols.items[i].st_other, |
| 724 | .st_shndx = self.symbols.items[i].st_shndx, | 1140 | .st_shndx = self.global_symbols.items[i].st_shndx, |
| 725 | }; | 1141 | }; |
| 726 | if (foreign_endian) { | 1142 | if (foreign_endian) { |
| 727 | bswapAllFields(elf.Elf32_Sym, sym); | 1143 | bswapAllFields(elf.Elf32_Sym, sym); |
| 728 | } | 1144 | } |
| 729 | } | 1145 | } |
| 730 | try self.file.pwriteAll(mem.sliceAsBytes(buf), syms_sect.sh_offset); | 1146 | try self.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off); |
| 731 | }, | 1147 | }, |
| 732 | .p64 => { | 1148 | .p64 => { |
| 733 | const buf = try allocator.alloc(elf.Elf64_Sym, self.symbols.items.len); | 1149 | const buf = try self.allocator.alloc(elf.Elf64_Sym, self.global_symbols.items.len); |
| 734 | defer allocator.free(buf); | 1150 | defer self.allocator.free(buf); |
| 735 | 1151 | ||
| 736 | for (buf) |*sym, i| { | 1152 | for (buf) |*sym, i| { |
| 737 | sym.* = .{ | 1153 | sym.* = .{ |
| 738 | .st_name = self.symbols.items[i].st_name, | 1154 | .st_name = self.global_symbols.items[i].st_name, |
| 739 | .st_value = self.symbols.items[i].st_value, | 1155 | .st_value = self.global_symbols.items[i].st_value, |
| 740 | .st_size = self.symbols.items[i].st_size, | 1156 | .st_size = self.global_symbols.items[i].st_size, |
| 741 | .st_info = self.symbols.items[i].st_info, | 1157 | .st_info = self.global_symbols.items[i].st_info, |
| 742 | .st_other = self.symbols.items[i].st_other, | 1158 | .st_other = self.global_symbols.items[i].st_other, |
| 743 | .st_shndx = self.symbols.items[i].st_shndx, | 1159 | .st_shndx = self.global_symbols.items[i].st_shndx, |
| 744 | }; | 1160 | }; |
| 745 | if (foreign_endian) { | 1161 | if (foreign_endian) { |
| 746 | bswapAllFields(elf.Elf64_Sym, sym); | 1162 | bswapAllFields(elf.Elf64_Sym, sym); |
| 747 | } | 1163 | } |
| 748 | } | 1164 | } |
| 749 | try self.file.pwriteAll(mem.sliceAsBytes(buf), syms_sect.sh_offset); | 1165 | try self.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off); |
| 750 | }, | 1166 | }, |
| 751 | } | 1167 | } |
| 752 | } | 1168 | } |
| ... | @@ -754,13 +1170,13 @@ const Update = struct { | ... | @@ -754,13 +1170,13 @@ const Update = struct { |
| 754 | 1170 | ||
| 755 | /// Truncates the existing file contents and overwrites the contents. | 1171 | /// Truncates the existing file contents and overwrites the contents. |
| 756 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | 1172 | /// Returns an error if `file` is not already open with +read +write +seek abilities. |
| 757 | pub fn writeFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | 1173 | pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !ElfFile { |
| 758 | switch (module.output_mode) { | 1174 | switch (options.output_mode) { |
| 759 | .Exe => {}, | 1175 | .Exe => {}, |
| 760 | .Obj => {}, | 1176 | .Obj => {}, |
| 761 | .Lib => return error.TODOImplementWritingLibFiles, | 1177 | .Lib => return error.TODOImplementWritingLibFiles, |
| 762 | } | 1178 | } |
| 763 | switch (module.object_format) { | 1179 | switch (options.object_format) { |
| 764 | .unknown => unreachable, // TODO remove this tag from the enum | 1180 | .unknown => unreachable, // TODO remove this tag from the enum |
| 765 | .coff => return error.TODOImplementWritingCOFF, | 1181 | .coff => return error.TODOImplementWritingCOFF, |
| 766 | .elf => {}, | 1182 | .elf => {}, |
| ... | @@ -768,38 +1184,80 @@ pub fn writeFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Resul | ... | @@ -768,38 +1184,80 @@ pub fn writeFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Resul |
| 768 | .wasm => return error.TODOImplementWritingWasmObjects, | 1184 | .wasm => return error.TODOImplementWritingWasmObjects, |
| 769 | } | 1185 | } |
| 770 | 1186 | ||
| 771 | var update = Update{ | 1187 | var self: ElfFile = .{ |
| 1188 | .allocator = allocator, | ||
| 772 | .file = file, | 1189 | .file = file, |
| 773 | .module = &module, | 1190 | .options = options, |
| 774 | .sections = std.ArrayList(elf.Elf64_Shdr).init(allocator), | 1191 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { |
| 775 | .shdr_table_offset = null, | 1192 | 32 => .p32, |
| 776 | .program_headers = std.ArrayList(elf.Elf64_Phdr).init(allocator), | 1193 | 64 => .p64, |
| 777 | .phdr_table_offset = null, | 1194 | else => return error.UnsupportedELFArchitecture, |
| 778 | .phdr_load_re_index = null, | 1195 | }, |
| 779 | .entry_addr = null, | 1196 | .shdr_table_dirty = true, |
| 780 | .shstrtab = std.ArrayList(u8).init(allocator), | 1197 | .owns_file_handle = false, |
| 781 | .shstrtab_index = null, | ||
| 782 | .text_section_index = null, | ||
| 783 | .symtab_section_index = null, | ||
| 784 | |||
| 785 | .symbols = std.ArrayList(elf.Elf64_Sym).init(allocator), | ||
| 786 | |||
| 787 | .errors = std.ArrayList(ErrorMsg).init(allocator), | ||
| 788 | }; | ||
| 789 | defer update.deinit(); | ||
| 790 | |||
| 791 | try update.perform(); | ||
| 792 | return Result{ | ||
| 793 | .errors = update.errors.toOwnedSlice(), | ||
| 794 | }; | 1198 | }; |
| 1199 | errdefer self.deinit(); | ||
| 1200 | |||
| 1201 | // Index 0 is always a null symbol. | ||
| 1202 | try self.local_symbols.append(allocator, .{ | ||
| 1203 | .st_name = 0, | ||
| 1204 | .st_info = 0, | ||
| 1205 | .st_other = 0, | ||
| 1206 | .st_shndx = 0, | ||
| 1207 | .st_value = 0, | ||
| 1208 | .st_size = 0, | ||
| 1209 | }); | ||
| 1210 | |||
| 1211 | // There must always be a null section in index 0 | ||
| 1212 | try self.sections.append(allocator, .{ | ||
| 1213 | .sh_name = 0, | ||
| 1214 | .sh_type = elf.SHT_NULL, | ||
| 1215 | .sh_flags = 0, | ||
| 1216 | .sh_addr = 0, | ||
| 1217 | .sh_offset = 0, | ||
| 1218 | .sh_size = 0, | ||
| 1219 | .sh_link = 0, | ||
| 1220 | .sh_info = 0, | ||
| 1221 | .sh_addralign = 0, | ||
| 1222 | .sh_entsize = 0, | ||
| 1223 | }); | ||
| 1224 | |||
| 1225 | try self.populateMissingMetadata(); | ||
| 1226 | |||
| 1227 | return self; | ||
| 795 | } | 1228 | } |
| 796 | 1229 | ||
| 797 | /// Returns error.IncrFailed if incremental update could not be performed. | 1230 | /// Returns error.IncrFailed if incremental update could not be performed. |
| 798 | fn updateFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | 1231 | fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !ElfFile { |
| 799 | //var ehdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; | 1232 | switch (options.output_mode) { |
| 1233 | .Exe => {}, | ||
| 1234 | .Obj => {}, | ||
| 1235 | .Lib => return error.IncrFailed, | ||
| 1236 | } | ||
| 1237 | switch (options.object_format) { | ||
| 1238 | .unknown => unreachable, // TODO remove this tag from the enum | ||
| 1239 | .coff => return error.IncrFailed, | ||
| 1240 | .elf => {}, | ||
| 1241 | .macho => return error.IncrFailed, | ||
| 1242 | .wasm => return error.IncrFailed, | ||
| 1243 | } | ||
| 1244 | var self: ElfFile = .{ | ||
| 1245 | .allocator = allocator, | ||
| 1246 | .file = file, | ||
| 1247 | .owns_file_handle = false, | ||
| 1248 | .options = options, | ||
| 1249 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { | ||
| 1250 | 32 => .p32, | ||
| 1251 | 64 => .p64, | ||
| 1252 | else => return error.UnsupportedELFArchitecture, | ||
| 1253 | }, | ||
| 1254 | }; | ||
| 1255 | errdefer self.deinit(); | ||
| 800 | 1256 | ||
| 801 | // TODO implement incremental linking | 1257 | // TODO implement reading the elf file |
| 802 | return error.IncrFailed; | 1258 | return error.IncrFailed; |
| 1259 | //try self.populateMissingMetadata(); | ||
| 1260 | //return self; | ||
| 803 | } | 1261 | } |
| 804 | 1262 | ||
| 805 | /// Saturating multiplication | 1263 | /// Saturating multiplication |
| ... | @@ -840,14 +1298,14 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr { | ... | @@ -840,14 +1298,14 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr { |
| 840 | }; | 1298 | }; |
| 841 | } | 1299 | } |
| 842 | 1300 | ||
| 843 | fn determineMode(module: ir.Module) fs.File.Mode { | 1301 | fn determineMode(options: Options) fs.File.Mode { |
| 844 | // On common systems with a 0o022 umask, 0o777 will still result in a file created | 1302 | // On common systems with a 0o022 umask, 0o777 will still result in a file created |
| 845 | // with 0o755 permissions, but it works appropriately if the system is configured | 1303 | // with 0o755 permissions, but it works appropriately if the system is configured |
| 846 | // more leniently. As another data point, C's fopen seems to open files with the | 1304 | // more leniently. As another data point, C's fopen seems to open files with the |
| 847 | // 666 mode. | 1305 | // 666 mode. |
| 848 | const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777; | 1306 | const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777; |
| 849 | switch (module.output_mode) { | 1307 | switch (options.output_mode) { |
| 850 | .Lib => return switch (module.link_mode) { | 1308 | .Lib => return switch (options.link_mode) { |
| 851 | .Dynamic => executable_mode, | 1309 | .Dynamic => executable_mode, |
| 852 | .Static => fs.File.default_mode, | 1310 | .Static => fs.File.default_mode, |
| 853 | }, | 1311 | }, |
src-self-hosted/main.zig+413-525| ... | @@ -1,29 +1,29 @@ | ... | @@ -1,29 +1,29 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | const event = std.event; | ||
| 5 | const os = std.os; | ||
| 6 | const io = std.io; | 2 | const io = std.io; |
| 7 | const fs = std.fs; | 3 | const fs = std.fs; |
| 8 | const mem = std.mem; | 4 | const mem = std.mem; |
| 9 | const process = std.process; | 5 | const process = std.process; |
| 10 | const Allocator = mem.Allocator; | 6 | const Allocator = mem.Allocator; |
| 11 | const ArrayList = std.ArrayList; | 7 | const ArrayList = std.ArrayList; |
| 8 | const ast = std.zig.ast; | ||
| 9 | const Module = @import("Module.zig"); | ||
| 10 | const link = @import("link.zig"); | ||
| 11 | const Package = @import("Package.zig"); | ||
| 12 | const zir = @import("zir.zig"); | ||
| 12 | 13 | ||
| 13 | const c = @import("c.zig"); | 14 | // TODO Improve async I/O enough that we feel comfortable doing this. |
| 14 | const introspect = @import("introspect.zig"); | 15 | //pub const io_mode = .evented; |
| 15 | const ZigCompiler = @import("compilation.zig").ZigCompiler; | ||
| 16 | const Compilation = @import("compilation.zig").Compilation; | ||
| 17 | const Target = std.Target; | ||
| 18 | const errmsg = @import("errmsg.zig"); | ||
| 19 | const LibCInstallation = @import("libc_installation.zig").LibCInstallation; | ||
| 20 | |||
| 21 | pub const io_mode = .evented; | ||
| 22 | 16 | ||
| 23 | pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB | 17 | pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB |
| 24 | 18 | ||
| 19 | pub const Color = enum { | ||
| 20 | Auto, | ||
| 21 | Off, | ||
| 22 | On, | ||
| 23 | }; | ||
| 24 | |||
| 25 | const usage = | 25 | const usage = |
| 26 | \\usage: zig [command] [options] | 26 | \\Usage: zig [command] [options] |
| 27 | \\ | 27 | \\ |
| 28 | \\Commands: | 28 | \\Commands: |
| 29 | \\ | 29 | \\ |
| ... | @@ -31,7 +31,6 @@ const usage = | ... | @@ -31,7 +31,6 @@ const usage = |
| 31 | \\ build-lib [source] Create library from source or object files | 31 | \\ build-lib [source] Create library from source or object files |
| 32 | \\ build-obj [source] Create object from source or assembly | 32 | \\ build-obj [source] Create object from source or assembly |
| 33 | \\ fmt [source] Parse file and render in canonical zig format | 33 | \\ fmt [source] Parse file and render in canonical zig format |
| 34 | \\ libc [paths_file] Display native libc paths file or validate one | ||
| 35 | \\ targets List available compilation targets | 34 | \\ targets List available compilation targets |
| 36 | \\ version Print version number and exit | 35 | \\ version Print version number and exit |
| 37 | \\ zen Print zen of zig and exit | 36 | \\ zen Print zen of zig and exit |
| ... | @@ -39,175 +38,152 @@ const usage = | ... | @@ -39,175 +38,152 @@ const usage = |
| 39 | \\ | 38 | \\ |
| 40 | ; | 39 | ; |
| 41 | 40 | ||
| 42 | const Command = struct { | ||
| 43 | name: []const u8, | ||
| 44 | exec: fn (*Allocator, []const []const u8) callconv(.Async) anyerror!void, | ||
| 45 | }; | ||
| 46 | |||
| 47 | pub fn main() !void { | 41 | pub fn main() !void { |
| 48 | const allocator = std.heap.c_allocator; | 42 | // TODO general purpose allocator in the zig std lib |
| 49 | 43 | const gpa = if (std.builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator; | |
| 50 | const stderr = io.getStdErr().outStream(); | 44 | var arena_instance = std.heap.ArenaAllocator.init(gpa); |
| 45 | defer arena_instance.deinit(); | ||
| 46 | const arena = &arena_instance.allocator; | ||
| 51 | 47 | ||
| 52 | const args = try process.argsAlloc(allocator); | 48 | const args = try process.argsAlloc(arena); |
| 53 | defer process.argsFree(allocator, args); | ||
| 54 | 49 | ||
| 55 | if (args.len <= 1) { | 50 | if (args.len <= 1) { |
| 56 | try stderr.writeAll("expected command argument\n\n"); | 51 | std.debug.warn("expected command argument\n\n{}", .{usage}); |
| 57 | try stderr.writeAll(usage); | ||
| 58 | process.exit(1); | 52 | process.exit(1); |
| 59 | } | 53 | } |
| 60 | 54 | ||
| 61 | const cmd = args[1]; | 55 | const cmd = args[1]; |
| 62 | const cmd_args = args[2..]; | 56 | const cmd_args = args[2..]; |
| 63 | if (mem.eql(u8, cmd, "build-exe")) { | 57 | if (mem.eql(u8, cmd, "build-exe")) { |
| 64 | return buildOutputType(allocator, cmd_args, .Exe); | 58 | return buildOutputType(gpa, arena, cmd_args, .Exe); |
| 65 | } else if (mem.eql(u8, cmd, "build-lib")) { | 59 | } else if (mem.eql(u8, cmd, "build-lib")) { |
| 66 | return buildOutputType(allocator, cmd_args, .Lib); | 60 | return buildOutputType(gpa, arena, cmd_args, .Lib); |
| 67 | } else if (mem.eql(u8, cmd, "build-obj")) { | 61 | } else if (mem.eql(u8, cmd, "build-obj")) { |
| 68 | return buildOutputType(allocator, cmd_args, .Obj); | 62 | return buildOutputType(gpa, arena, cmd_args, .Obj); |
| 69 | } else if (mem.eql(u8, cmd, "fmt")) { | 63 | } else if (mem.eql(u8, cmd, "fmt")) { |
| 70 | return cmdFmt(allocator, cmd_args); | 64 | return cmdFmt(gpa, cmd_args); |
| 71 | } else if (mem.eql(u8, cmd, "libc")) { | ||
| 72 | return cmdLibC(allocator, cmd_args); | ||
| 73 | } else if (mem.eql(u8, cmd, "targets")) { | 65 | } else if (mem.eql(u8, cmd, "targets")) { |
| 74 | const info = try std.zig.system.NativeTargetInfo.detect(allocator, .{}); | 66 | const info = try std.zig.system.NativeTargetInfo.detect(arena, .{}); |
| 75 | const stdout = io.getStdOut().outStream(); | 67 | const stdout = io.getStdOut().outStream(); |
| 76 | return @import("print_targets.zig").cmdTargets(allocator, cmd_args, stdout, info.target); | 68 | return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target); |
| 77 | } else if (mem.eql(u8, cmd, "version")) { | 69 | } else if (mem.eql(u8, cmd, "version")) { |
| 78 | return cmdVersion(allocator, cmd_args); | 70 | // Need to set up the build script to give the version as a comptime value. |
| 71 | std.debug.warn("TODO version command not implemented yet\n", .{}); | ||
| 72 | return error.Unimplemented; | ||
| 79 | } else if (mem.eql(u8, cmd, "zen")) { | 73 | } else if (mem.eql(u8, cmd, "zen")) { |
| 80 | return cmdZen(allocator, cmd_args); | 74 | try io.getStdOut().writeAll(info_zen); |
| 81 | } else if (mem.eql(u8, cmd, "help")) { | 75 | } else if (mem.eql(u8, cmd, "help")) { |
| 82 | return cmdHelp(allocator, cmd_args); | 76 | try io.getStdOut().writeAll(usage); |
| 83 | } else if (mem.eql(u8, cmd, "internal")) { | ||
| 84 | return cmdInternal(allocator, cmd_args); | ||
| 85 | } else { | 77 | } else { |
| 86 | try stderr.print("unknown command: {}\n\n", .{args[1]}); | 78 | std.debug.warn("unknown command: {}\n\n{}", .{ args[1], usage }); |
| 87 | try stderr.writeAll(usage); | ||
| 88 | process.exit(1); | 79 | process.exit(1); |
| 89 | } | 80 | } |
| 90 | } | 81 | } |
| 91 | 82 | ||
| 92 | const usage_build_generic = | 83 | const usage_build_generic = |
| 93 | \\usage: zig build-exe <options> [file] | 84 | \\Usage: zig build-exe <options> [files] |
| 94 | \\ zig build-lib <options> [file] | 85 | \\ zig build-lib <options> [files] |
| 95 | \\ zig build-obj <options> [file] | 86 | \\ zig build-obj <options> [files] |
| 87 | \\ | ||
| 88 | \\Supported file types: | ||
| 89 | \\ (planned) .zig Zig source code | ||
| 90 | \\ .zir Zig Intermediate Representation code | ||
| 91 | \\ (planned) .o ELF object file | ||
| 92 | \\ (planned) .o MACH-O (macOS) object file | ||
| 93 | \\ (planned) .obj COFF (Windows) object file | ||
| 94 | \\ (planned) .lib COFF (Windows) static library | ||
| 95 | \\ (planned) .a ELF static library | ||
| 96 | \\ (planned) .so ELF shared object (dynamic link) | ||
| 97 | \\ (planned) .dll Windows Dynamic Link Library | ||
| 98 | \\ (planned) .dylib MACH-O (macOS) dynamic library | ||
| 99 | \\ (planned) .s Target-specific assembly source code | ||
| 100 | \\ (planned) .S Assembly with C preprocessor (requires LLVM extensions) | ||
| 101 | \\ (planned) .c C source code (requires LLVM extensions) | ||
| 102 | \\ (planned) .cpp C++ source code (requires LLVM extensions) | ||
| 103 | \\ Other C++ extensions: .C .cc .cxx | ||
| 96 | \\ | 104 | \\ |
| 97 | \\General Options: | 105 | \\General Options: |
| 98 | \\ --help Print this help and exit | 106 | \\ -h, --help Print this help and exit |
| 99 | \\ --color [auto|off|on] Enable or disable colored error messages | 107 | \\ --watch Enable compiler REPL |
| 108 | \\ --color [auto|off|on] Enable or disable colored error messages | ||
| 109 | \\ -femit-bin[=path] (default) output machine code | ||
| 110 | \\ -fno-emit-bin Do not output machine code | ||
| 100 | \\ | 111 | \\ |
| 101 | \\Compile Options: | 112 | \\Compile Options: |
| 102 | \\ --libc [file] Provide a file which specifies libc paths | 113 | \\ -target [name] <arch><sub>-<os>-<abi> see the targets command |
| 103 | \\ --assembly [source] Add assembly file to build | 114 | \\ -mcpu [cpu] Specify target CPU and feature set |
| 104 | \\ --emit [filetype] Emit a specific file format as compilation output | 115 | \\ --name [name] Override output name |
| 105 | \\ --enable-timing-info Print timing diagnostics | 116 | \\ --mode [mode] Set the build mode |
| 106 | \\ --name [name] Override output name | 117 | \\ Debug (default) optimizations off, safety on |
| 107 | \\ --output [file] Override destination path | 118 | \\ ReleaseFast optimizations on, safety off |
| 108 | \\ --output-h [file] Override generated header file path | 119 | \\ ReleaseSafe optimizations on, safety on |
| 109 | \\ --pkg-begin [name] [path] Make package available to import and push current pkg | 120 | \\ ReleaseSmall optimize for small binary, safety off |
| 110 | \\ --pkg-end Pop current pkg | 121 | \\ --dynamic Force output to be dynamically linked |
| 111 | \\ --mode [mode] Set the build mode | 122 | \\ --strip Exclude debug symbols |
| 112 | \\ debug (default) optimizations off, safety on | ||
| 113 | \\ release-fast optimizations on, safety off | ||
| 114 | \\ release-safe optimizations on, safety on | ||
| 115 | \\ release-small optimize for small binary, safety off | ||
| 116 | \\ --static Output will be statically linked | ||
| 117 | \\ --strip Exclude debug symbols | ||
| 118 | \\ -target [name] <arch><sub>-<os>-<abi> see the targets command | ||
| 119 | \\ --eh-frame-hdr enable C++ exception handling by passing --eh-frame-hdr to linker | ||
| 120 | \\ --verbose-tokenize Turn on compiler debug output for tokenization | ||
| 121 | \\ --verbose-ast-tree Turn on compiler debug output for parsing into an AST (tree view) | ||
| 122 | \\ --verbose-ast-fmt Turn on compiler debug output for parsing into an AST (render source) | ||
| 123 | \\ --verbose-link Turn on compiler debug output for linking | ||
| 124 | \\ --verbose-ir Turn on compiler debug output for Zig IR | ||
| 125 | \\ --verbose-llvm-ir Turn on compiler debug output for LLVM IR | ||
| 126 | \\ --verbose-cimport Turn on compiler debug output for C imports | ||
| 127 | \\ -dirafter [dir] Same as -isystem but do it last | ||
| 128 | \\ -isystem [dir] Add additional search path for other .h files | ||
| 129 | \\ -mllvm [arg] Additional arguments to forward to LLVM's option processing | ||
| 130 | \\ | 123 | \\ |
| 131 | \\Link Options: | 124 | \\Link Options: |
| 132 | \\ --ar-path [path] Set the path to ar | 125 | \\ -l[lib], --library [lib] Link against system library |
| 133 | \\ --each-lib-rpath Add rpath for each used dynamic library | 126 | \\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so) |
| 134 | \\ --library [lib] Link against lib | 127 | \\ --version [ver] Dynamic library semver |
| 135 | \\ --forbid-library [lib] Make it an error to link against lib | ||
| 136 | \\ --library-path [dir] Add a directory to the library search path | ||
| 137 | \\ --linker-script [path] Use a custom linker script | ||
| 138 | \\ --object [obj] Add object file to build | ||
| 139 | \\ -rdynamic Add all symbols to the dynamic symbol table | ||
| 140 | \\ -rpath [path] Add directory to the runtime library search path | ||
| 141 | \\ -framework [name] (darwin) link against framework | ||
| 142 | \\ -mios-version-min [ver] (darwin) set iOS deployment target | ||
| 143 | \\ -mmacosx-version-min [ver] (darwin) set Mac OS X deployment target | ||
| 144 | \\ --ver-major [ver] Dynamic library semver major version | ||
| 145 | \\ --ver-minor [ver] Dynamic library semver minor version | ||
| 146 | \\ --ver-patch [ver] Dynamic library semver patch version | ||
| 147 | \\ | 128 | \\ |
| 129 | \\Debug Options (Zig Compiler Development): | ||
| 130 | \\ -ftime-report Print timing diagnostics | ||
| 131 | \\ --debug-tokenize verbose tokenization | ||
| 132 | \\ --debug-ast-tree verbose parsing into an AST (tree view) | ||
| 133 | \\ --debug-ast-fmt verbose parsing into an AST (render source) | ||
| 134 | \\ --debug-ir verbose Zig IR | ||
| 135 | \\ --debug-link verbose linking | ||
| 136 | \\ --debug-codegen verbose machine code generation | ||
| 148 | \\ | 137 | \\ |
| 149 | ; | 138 | ; |
| 150 | 139 | ||
| 151 | fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Compilation.Kind) !void { | 140 | const Emit = union(enum) { |
| 152 | const stderr = io.getStdErr().outStream(); | 141 | no, |
| 142 | yes_default_path, | ||
| 143 | yes: []const u8, | ||
| 144 | }; | ||
| 153 | 145 | ||
| 154 | var color: errmsg.Color = .Auto; | 146 | fn buildOutputType( |
| 147 | gpa: *Allocator, | ||
| 148 | arena: *Allocator, | ||
| 149 | args: []const []const u8, | ||
| 150 | output_mode: std.builtin.OutputMode, | ||
| 151 | ) !void { | ||
| 152 | var color: Color = .Auto; | ||
| 155 | var build_mode: std.builtin.Mode = .Debug; | 153 | var build_mode: std.builtin.Mode = .Debug; |
| 156 | var emit_bin = true; | ||
| 157 | var emit_asm = false; | ||
| 158 | var emit_llvm_ir = false; | ||
| 159 | var emit_h = false; | ||
| 160 | var provided_name: ?[]const u8 = null; | 154 | var provided_name: ?[]const u8 = null; |
| 161 | var is_dynamic = false; | 155 | var link_mode: ?std.builtin.LinkMode = null; |
| 162 | var root_src_file: ?[]const u8 = null; | 156 | var root_src_file: ?[]const u8 = null; |
| 163 | var libc_arg: ?[]const u8 = null; | ||
| 164 | var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 }; | 157 | var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 }; |
| 165 | var linker_script: ?[]const u8 = null; | ||
| 166 | var strip = false; | 158 | var strip = false; |
| 167 | var verbose_tokenize = false; | 159 | var watch = false; |
| 168 | var verbose_ast_tree = false; | 160 | var debug_tokenize = false; |
| 169 | var verbose_ast_fmt = false; | 161 | var debug_ast_tree = false; |
| 170 | var verbose_link = false; | 162 | var debug_ast_fmt = false; |
| 171 | var verbose_ir = false; | 163 | var debug_link = false; |
| 172 | var verbose_llvm_ir = false; | 164 | var debug_ir = false; |
| 173 | var verbose_cimport = false; | 165 | var debug_codegen = false; |
| 174 | var linker_rdynamic = false; | 166 | var time_report = false; |
| 175 | var link_eh_frame_hdr = false; | 167 | var emit_bin: Emit = .yes_default_path; |
| 176 | var macosx_version_min: ?[]const u8 = null; | 168 | var emit_zir: Emit = .no; |
| 177 | var ios_version_min: ?[]const u8 = null; | 169 | var target_arch_os_abi: []const u8 = "native"; |
| 178 | 170 | var target_mcpu: ?[]const u8 = null; | |
| 179 | var assembly_files = ArrayList([]const u8).init(allocator); | 171 | var target_dynamic_linker: ?[]const u8 = null; |
| 180 | defer assembly_files.deinit(); | 172 | |
| 181 | 173 | var system_libs = std.ArrayList([]const u8).init(gpa); | |
| 182 | var link_objects = ArrayList([]const u8).init(allocator); | ||
| 183 | defer link_objects.deinit(); | ||
| 184 | |||
| 185 | var clang_argv_buf = ArrayList([]const u8).init(allocator); | ||
| 186 | defer clang_argv_buf.deinit(); | ||
| 187 | |||
| 188 | var mllvm_flags = ArrayList([]const u8).init(allocator); | ||
| 189 | defer mllvm_flags.deinit(); | ||
| 190 | |||
| 191 | var cur_pkg = try CliPkg.init(allocator, "", "", null); | ||
| 192 | defer cur_pkg.deinit(); | ||
| 193 | |||
| 194 | var system_libs = ArrayList([]const u8).init(allocator); | ||
| 195 | defer system_libs.deinit(); | 174 | defer system_libs.deinit(); |
| 196 | 175 | ||
| 197 | var c_src_files = ArrayList([]const u8).init(allocator); | ||
| 198 | defer c_src_files.deinit(); | ||
| 199 | |||
| 200 | { | 176 | { |
| 201 | var i: usize = 0; | 177 | var i: usize = 0; |
| 202 | while (i < args.len) : (i += 1) { | 178 | while (i < args.len) : (i += 1) { |
| 203 | const arg = args[i]; | 179 | const arg = args[i]; |
| 204 | if (mem.startsWith(u8, arg, "-")) { | 180 | if (mem.startsWith(u8, arg, "-")) { |
| 205 | if (mem.eql(u8, arg, "--help")) { | 181 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 206 | try io.getStdOut().writeAll(usage_build_generic); | 182 | try io.getStdOut().writeAll(usage_build_generic); |
| 207 | process.exit(0); | 183 | process.exit(0); |
| 208 | } else if (mem.eql(u8, arg, "--color")) { | 184 | } else if (mem.eql(u8, arg, "--color")) { |
| 209 | if (i + 1 >= args.len) { | 185 | if (i + 1 >= args.len) { |
| 210 | try stderr.writeAll("expected [auto|on|off] after --color\n"); | 186 | std.debug.warn("expected [auto|on|off] after --color\n", .{}); |
| 211 | process.exit(1); | 187 | process.exit(1); |
| 212 | } | 188 | } |
| 213 | i += 1; | 189 | i += 1; |
| ... | @@ -219,12 +195,12 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co | ... | @@ -219,12 +195,12 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co |
| 219 | } else if (mem.eql(u8, next_arg, "off")) { | 195 | } else if (mem.eql(u8, next_arg, "off")) { |
| 220 | color = .Off; | 196 | color = .Off; |
| 221 | } else { | 197 | } else { |
| 222 | try stderr.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); | 198 | std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); |
| 223 | process.exit(1); | 199 | process.exit(1); |
| 224 | } | 200 | } |
| 225 | } else if (mem.eql(u8, arg, "--mode")) { | 201 | } else if (mem.eql(u8, arg, "--mode")) { |
| 226 | if (i + 1 >= args.len) { | 202 | if (i + 1 >= args.len) { |
| 227 | try stderr.writeAll("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n"); | 203 | std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{}); |
| 228 | process.exit(1); | 204 | process.exit(1); |
| 229 | } | 205 | } |
| 230 | i += 1; | 206 | i += 1; |
| ... | @@ -238,289 +214,302 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co | ... | @@ -238,289 +214,302 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co |
| 238 | } else if (mem.eql(u8, next_arg, "ReleaseSmall")) { | 214 | } else if (mem.eql(u8, next_arg, "ReleaseSmall")) { |
| 239 | build_mode = .ReleaseSmall; | 215 | build_mode = .ReleaseSmall; |
| 240 | } else { | 216 | } else { |
| 241 | try stderr.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg}); | 217 | std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg}); |
| 242 | process.exit(1); | 218 | process.exit(1); |
| 243 | } | 219 | } |
| 244 | } else if (mem.eql(u8, arg, "--name")) { | 220 | } else if (mem.eql(u8, arg, "--name")) { |
| 245 | if (i + 1 >= args.len) { | 221 | if (i + 1 >= args.len) { |
| 246 | try stderr.writeAll("expected parameter after --name\n"); | 222 | std.debug.warn("expected parameter after --name\n", .{}); |
| 247 | process.exit(1); | 223 | process.exit(1); |
| 248 | } | 224 | } |
| 249 | i += 1; | 225 | i += 1; |
| 250 | provided_name = args[i]; | 226 | provided_name = args[i]; |
| 251 | } else if (mem.eql(u8, arg, "--ver-major")) { | 227 | } else if (mem.eql(u8, arg, "--library")) { |
| 252 | if (i + 1 >= args.len) { | ||
| 253 | try stderr.writeAll("expected parameter after --ver-major\n"); | ||
| 254 | process.exit(1); | ||
| 255 | } | ||
| 256 | i += 1; | ||
| 257 | version.major = try std.fmt.parseInt(u32, args[i], 10); | ||
| 258 | } else if (mem.eql(u8, arg, "--ver-minor")) { | ||
| 259 | if (i + 1 >= args.len) { | ||
| 260 | try stderr.writeAll("expected parameter after --ver-minor\n"); | ||
| 261 | process.exit(1); | ||
| 262 | } | ||
| 263 | i += 1; | ||
| 264 | version.minor = try std.fmt.parseInt(u32, args[i], 10); | ||
| 265 | } else if (mem.eql(u8, arg, "--ver-patch")) { | ||
| 266 | if (i + 1 >= args.len) { | 228 | if (i + 1 >= args.len) { |
| 267 | try stderr.writeAll("expected parameter after --ver-patch\n"); | 229 | std.debug.warn("expected parameter after --library\n", .{}); |
| 268 | process.exit(1); | 230 | process.exit(1); |
| 269 | } | 231 | } |
| 270 | i += 1; | 232 | i += 1; |
| 271 | version.patch = try std.fmt.parseInt(u32, args[i], 10); | 233 | try system_libs.append(args[i]); |
| 272 | } else if (mem.eql(u8, arg, "--linker-script")) { | 234 | } else if (mem.eql(u8, arg, "--version")) { |
| 273 | if (i + 1 >= args.len) { | 235 | if (i + 1 >= args.len) { |
| 274 | try stderr.writeAll("expected parameter after --linker-script\n"); | 236 | std.debug.warn("expected parameter after --version\n", .{}); |
| 275 | process.exit(1); | 237 | process.exit(1); |
| 276 | } | 238 | } |
| 277 | i += 1; | 239 | i += 1; |
| 278 | linker_script = args[i]; | 240 | version = std.builtin.Version.parse(args[i]) catch |err| { |
| 279 | } else if (mem.eql(u8, arg, "--libc")) { | 241 | std.debug.warn("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) }); |
| 280 | if (i + 1 >= args.len) { | ||
| 281 | try stderr.writeAll("expected parameter after --libc\n"); | ||
| 282 | process.exit(1); | 242 | process.exit(1); |
| 283 | } | 243 | }; |
| 284 | i += 1; | 244 | } else if (mem.eql(u8, arg, "-target")) { |
| 285 | libc_arg = args[i]; | ||
| 286 | } else if (mem.eql(u8, arg, "-mllvm")) { | ||
| 287 | if (i + 1 >= args.len) { | 245 | if (i + 1 >= args.len) { |
| 288 | try stderr.writeAll("expected parameter after -mllvm\n"); | 246 | std.debug.warn("expected parameter after -target\n", .{}); |
| 289 | process.exit(1); | 247 | process.exit(1); |
| 290 | } | 248 | } |
| 291 | i += 1; | 249 | i += 1; |
| 292 | try clang_argv_buf.append("-mllvm"); | 250 | target_arch_os_abi = args[i]; |
| 293 | try clang_argv_buf.append(args[i]); | 251 | } else if (mem.eql(u8, arg, "-mcpu")) { |
| 294 | |||
| 295 | try mllvm_flags.append(args[i]); | ||
| 296 | } else if (mem.eql(u8, arg, "-mmacosx-version-min")) { | ||
| 297 | if (i + 1 >= args.len) { | 252 | if (i + 1 >= args.len) { |
| 298 | try stderr.writeAll("expected parameter after -mmacosx-version-min\n"); | 253 | std.debug.warn("expected parameter after -mcpu\n", .{}); |
| 299 | process.exit(1); | 254 | process.exit(1); |
| 300 | } | 255 | } |
| 301 | i += 1; | 256 | i += 1; |
| 302 | macosx_version_min = args[i]; | 257 | target_mcpu = args[i]; |
| 303 | } else if (mem.eql(u8, arg, "-mios-version-min")) { | 258 | } else if (mem.startsWith(u8, arg, "-mcpu=")) { |
| 259 | target_mcpu = arg["-mcpu=".len..]; | ||
| 260 | } else if (mem.eql(u8, arg, "--dynamic-linker")) { | ||
| 304 | if (i + 1 >= args.len) { | 261 | if (i + 1 >= args.len) { |
| 305 | try stderr.writeAll("expected parameter after -mios-version-min\n"); | 262 | std.debug.warn("expected parameter after --dynamic-linker\n", .{}); |
| 306 | process.exit(1); | 263 | process.exit(1); |
| 307 | } | 264 | } |
| 308 | i += 1; | 265 | i += 1; |
| 309 | ios_version_min = args[i]; | 266 | target_dynamic_linker = args[i]; |
| 267 | } else if (mem.eql(u8, arg, "--watch")) { | ||
| 268 | watch = true; | ||
| 269 | } else if (mem.eql(u8, arg, "-ftime-report")) { | ||
| 270 | time_report = true; | ||
| 310 | } else if (mem.eql(u8, arg, "-femit-bin")) { | 271 | } else if (mem.eql(u8, arg, "-femit-bin")) { |
| 311 | emit_bin = true; | 272 | emit_bin = .yes_default_path; |
| 273 | } else if (mem.startsWith(u8, arg, "-femit-bin=")) { | ||
| 274 | emit_bin = .{ .yes = arg["-femit-bin=".len..] }; | ||
| 312 | } else if (mem.eql(u8, arg, "-fno-emit-bin")) { | 275 | } else if (mem.eql(u8, arg, "-fno-emit-bin")) { |
| 313 | emit_bin = false; | 276 | emit_bin = .no; |
| 314 | } else if (mem.eql(u8, arg, "-femit-asm")) { | 277 | } else if (mem.eql(u8, arg, "-femit-zir")) { |
| 315 | emit_asm = true; | 278 | emit_zir = .yes_default_path; |
| 316 | } else if (mem.eql(u8, arg, "-fno-emit-asm")) { | 279 | } else if (mem.startsWith(u8, arg, "-femit-zir=")) { |
| 317 | emit_asm = false; | 280 | emit_zir = .{ .yes = arg["-femit-zir=".len..] }; |
| 318 | } else if (mem.eql(u8, arg, "-femit-llvm-ir")) { | 281 | } else if (mem.eql(u8, arg, "-fno-emit-zir")) { |
| 319 | emit_llvm_ir = true; | 282 | emit_zir = .no; |
| 320 | } else if (mem.eql(u8, arg, "-fno-emit-llvm-ir")) { | ||
| 321 | emit_llvm_ir = false; | ||
| 322 | } else if (mem.eql(u8, arg, "-dynamic")) { | 283 | } else if (mem.eql(u8, arg, "-dynamic")) { |
| 323 | is_dynamic = true; | 284 | link_mode = .Dynamic; |
| 285 | } else if (mem.eql(u8, arg, "-static")) { | ||
| 286 | link_mode = .Static; | ||
| 324 | } else if (mem.eql(u8, arg, "--strip")) { | 287 | } else if (mem.eql(u8, arg, "--strip")) { |
| 325 | strip = true; | 288 | strip = true; |
| 326 | } else if (mem.eql(u8, arg, "--verbose-tokenize")) { | 289 | } else if (mem.eql(u8, arg, "--debug-tokenize")) { |
| 327 | verbose_tokenize = true; | 290 | debug_tokenize = true; |
| 328 | } else if (mem.eql(u8, arg, "--verbose-ast-tree")) { | 291 | } else if (mem.eql(u8, arg, "--debug-ast-tree")) { |
| 329 | verbose_ast_tree = true; | 292 | debug_ast_tree = true; |
| 330 | } else if (mem.eql(u8, arg, "--verbose-ast-fmt")) { | 293 | } else if (mem.eql(u8, arg, "--debug-ast-fmt")) { |
| 331 | verbose_ast_fmt = true; | 294 | debug_ast_fmt = true; |
| 332 | } else if (mem.eql(u8, arg, "--verbose-link")) { | 295 | } else if (mem.eql(u8, arg, "--debug-link")) { |
| 333 | verbose_link = true; | 296 | debug_link = true; |
| 334 | } else if (mem.eql(u8, arg, "--verbose-ir")) { | 297 | } else if (mem.eql(u8, arg, "--debug-ir")) { |
| 335 | verbose_ir = true; | 298 | debug_ir = true; |
| 336 | } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) { | 299 | } else if (mem.eql(u8, arg, "--debug-codegen")) { |
| 337 | verbose_llvm_ir = true; | 300 | debug_codegen = true; |
| 338 | } else if (mem.eql(u8, arg, "--eh-frame-hdr")) { | ||
| 339 | link_eh_frame_hdr = true; | ||
| 340 | } else if (mem.eql(u8, arg, "--verbose-cimport")) { | ||
| 341 | verbose_cimport = true; | ||
| 342 | } else if (mem.eql(u8, arg, "-rdynamic")) { | ||
| 343 | linker_rdynamic = true; | ||
| 344 | } else if (mem.eql(u8, arg, "--pkg-begin")) { | ||
| 345 | if (i + 2 >= args.len) { | ||
| 346 | try stderr.writeAll("expected [name] [path] after --pkg-begin\n"); | ||
| 347 | process.exit(1); | ||
| 348 | } | ||
| 349 | i += 1; | ||
| 350 | const new_pkg_name = args[i]; | ||
| 351 | i += 1; | ||
| 352 | const new_pkg_path = args[i]; | ||
| 353 | |||
| 354 | var new_cur_pkg = try CliPkg.init(allocator, new_pkg_name, new_pkg_path, cur_pkg); | ||
| 355 | try cur_pkg.children.append(new_cur_pkg); | ||
| 356 | cur_pkg = new_cur_pkg; | ||
| 357 | } else if (mem.eql(u8, arg, "--pkg-end")) { | ||
| 358 | if (cur_pkg.parent) |parent| { | ||
| 359 | cur_pkg = parent; | ||
| 360 | } else { | ||
| 361 | try stderr.writeAll("encountered --pkg-end with no matching --pkg-begin\n"); | ||
| 362 | process.exit(1); | ||
| 363 | } | ||
| 364 | } else if (mem.startsWith(u8, arg, "-l")) { | 301 | } else if (mem.startsWith(u8, arg, "-l")) { |
| 365 | try system_libs.append(arg[2..]); | 302 | try system_libs.append(arg[2..]); |
| 366 | } else { | 303 | } else { |
| 367 | try stderr.print("unrecognized parameter: '{}'", .{arg}); | 304 | std.debug.warn("unrecognized parameter: '{}'", .{arg}); |
| 368 | process.exit(1); | 305 | process.exit(1); |
| 369 | } | 306 | } |
| 370 | } else if (mem.endsWith(u8, arg, ".s")) { | 307 | } else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) { |
| 371 | try assembly_files.append(arg); | 308 | std.debug.warn("assembly files not supported yet", .{}); |
| 309 | process.exit(1); | ||
| 372 | } else if (mem.endsWith(u8, arg, ".o") or | 310 | } else if (mem.endsWith(u8, arg, ".o") or |
| 373 | mem.endsWith(u8, arg, ".obj") or | 311 | mem.endsWith(u8, arg, ".obj") or |
| 374 | mem.endsWith(u8, arg, ".a") or | 312 | mem.endsWith(u8, arg, ".a") or |
| 375 | mem.endsWith(u8, arg, ".lib")) | 313 | mem.endsWith(u8, arg, ".lib")) |
| 376 | { | 314 | { |
| 377 | try link_objects.append(arg); | 315 | std.debug.warn("object files and static libraries not supported yet", .{}); |
| 316 | process.exit(1); | ||
| 378 | } else if (mem.endsWith(u8, arg, ".c") or | 317 | } else if (mem.endsWith(u8, arg, ".c") or |
| 379 | mem.endsWith(u8, arg, ".cpp")) | 318 | mem.endsWith(u8, arg, ".cpp")) |
| 380 | { | 319 | { |
| 381 | try c_src_files.append(arg); | 320 | std.debug.warn("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{}); |
| 382 | } else if (mem.endsWith(u8, arg, ".zig")) { | 321 | process.exit(1); |
| 322 | } else if (mem.endsWith(u8, arg, ".so") or | ||
| 323 | mem.endsWith(u8, arg, ".dylib") or | ||
| 324 | mem.endsWith(u8, arg, ".dll")) | ||
| 325 | { | ||
| 326 | std.debug.warn("linking against dynamic libraries not yet supported", .{}); | ||
| 327 | process.exit(1); | ||
| 328 | } else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) { | ||
| 383 | if (root_src_file) |other| { | 329 | if (root_src_file) |other| { |
| 384 | try stderr.print("found another zig file '{}' after root source file '{}'", .{ | 330 | std.debug.warn("found another zig file '{}' after root source file '{}'", .{ arg, other }); |
| 385 | arg, | ||
| 386 | other, | ||
| 387 | }); | ||
| 388 | process.exit(1); | 331 | process.exit(1); |
| 389 | } else { | 332 | } else { |
| 390 | root_src_file = arg; | 333 | root_src_file = arg; |
| 391 | } | 334 | } |
| 392 | } else { | 335 | } else { |
| 393 | try stderr.print("unrecognized file extension of parameter '{}'", .{arg}); | 336 | std.debug.warn("unrecognized file extension of parameter '{}'", .{arg}); |
| 394 | } | 337 | } |
| 395 | } | 338 | } |
| 396 | } | 339 | } |
| 397 | 340 | ||
| 398 | if (cur_pkg.parent != null) { | ||
| 399 | try stderr.print("unmatched --pkg-begin\n", .{}); | ||
| 400 | process.exit(1); | ||
| 401 | } | ||
| 402 | |||
| 403 | const root_name = if (provided_name) |n| n else blk: { | 341 | const root_name = if (provided_name) |n| n else blk: { |
| 404 | if (root_src_file) |file| { | 342 | if (root_src_file) |file| { |
| 405 | const basename = fs.path.basename(file); | 343 | const basename = fs.path.basename(file); |
| 406 | var it = mem.split(basename, "."); | 344 | var it = mem.split(basename, "."); |
| 407 | break :blk it.next() orelse basename; | 345 | break :blk it.next() orelse basename; |
| 408 | } else { | 346 | } else { |
| 409 | try stderr.writeAll("--name [name] not provided and unable to infer\n"); | 347 | std.debug.warn("--name [name] not provided and unable to infer\n", .{}); |
| 410 | process.exit(1); | 348 | process.exit(1); |
| 411 | } | 349 | } |
| 412 | }; | 350 | }; |
| 413 | 351 | ||
| 414 | if (root_src_file == null and link_objects.len == 0 and assembly_files.len == 0) { | 352 | if (system_libs.items.len != 0) { |
| 415 | try stderr.writeAll("Expected source file argument or at least one --object or --assembly argument\n"); | 353 | std.debug.warn("linking against system libraries not yet supported", .{}); |
| 416 | process.exit(1); | 354 | process.exit(1); |
| 417 | } | 355 | } |
| 418 | 356 | ||
| 419 | if (out_type == Compilation.Kind.Obj and link_objects.len != 0) { | 357 | var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{}; |
| 420 | try stderr.writeAll("When building an object file, --object arguments are invalid\n"); | 358 | const cross_target = std.zig.CrossTarget.parse(.{ |
| 359 | .arch_os_abi = target_arch_os_abi, | ||
| 360 | .cpu_features = target_mcpu, | ||
| 361 | .dynamic_linker = target_dynamic_linker, | ||
| 362 | .diagnostics = &diags, | ||
| 363 | }) catch |err| switch (err) { | ||
| 364 | error.UnknownCpuModel => { | ||
| 365 | std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{ | ||
| 366 | diags.cpu_name.?, | ||
| 367 | @tagName(diags.arch.?), | ||
| 368 | }); | ||
| 369 | for (diags.arch.?.allCpuModels()) |cpu| { | ||
| 370 | std.debug.warn(" {}\n", .{cpu.name}); | ||
| 371 | } | ||
| 372 | process.exit(1); | ||
| 373 | }, | ||
| 374 | error.UnknownCpuFeature => { | ||
| 375 | std.debug.warn( | ||
| 376 | \\Unknown CPU feature: '{}' | ||
| 377 | \\Available CPU features for architecture '{}': | ||
| 378 | \\ | ||
| 379 | , .{ | ||
| 380 | diags.unknown_feature_name, | ||
| 381 | @tagName(diags.arch.?), | ||
| 382 | }); | ||
| 383 | for (diags.arch.?.allFeaturesList()) |feature| { | ||
| 384 | std.debug.warn(" {}: {}\n", .{ feature.name, feature.description }); | ||
| 385 | } | ||
| 386 | process.exit(1); | ||
| 387 | }, | ||
| 388 | else => |e| return e, | ||
| 389 | }; | ||
| 390 | |||
| 391 | const object_format: ?std.builtin.ObjectFormat = null; | ||
| 392 | var target_info = try std.zig.system.NativeTargetInfo.detect(gpa, cross_target); | ||
| 393 | if (target_info.cpu_detection_unimplemented) { | ||
| 394 | // TODO We want to just use detected_info.target but implementing | ||
| 395 | // CPU model & feature detection is todo so here we rely on LLVM. | ||
| 396 | std.debug.warn("CPU features detection is not yet available for this system without LLVM extensions\n", .{}); | ||
| 421 | process.exit(1); | 397 | process.exit(1); |
| 422 | } | 398 | } |
| 423 | 399 | ||
| 424 | try ZigCompiler.setLlvmArgv(allocator, mllvm_flags.span()); | 400 | const src_path = root_src_file orelse { |
| 401 | std.debug.warn("expected at least one file argument", .{}); | ||
| 402 | process.exit(1); | ||
| 403 | }; | ||
| 425 | 404 | ||
| 426 | const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch process.exit(1); | 405 | const bin_path = switch (emit_bin) { |
| 427 | defer allocator.free(zig_lib_dir); | 406 | .no => { |
| 407 | std.debug.warn("-fno-emit-bin not supported yet", .{}); | ||
| 408 | process.exit(1); | ||
| 409 | }, | ||
| 410 | .yes_default_path => try std.fmt.allocPrint(arena, "{}{}", .{ root_name, target_info.target.exeFileExt() }), | ||
| 411 | .yes => |p| p, | ||
| 412 | }; | ||
| 428 | 413 | ||
| 429 | var override_libc: LibCInstallation = undefined; | 414 | const zir_out_path: ?[]const u8 = switch (emit_zir) { |
| 415 | .no => null, | ||
| 416 | .yes_default_path => blk: { | ||
| 417 | if (root_src_file) |rsf| { | ||
| 418 | if (mem.endsWith(u8, rsf, ".zir")) { | ||
| 419 | break :blk try std.fmt.allocPrint(arena, "{}.out.zir", .{root_name}); | ||
| 420 | } | ||
| 421 | } | ||
| 422 | break :blk try std.fmt.allocPrint(arena, "{}.zir", .{root_name}); | ||
| 423 | }, | ||
| 424 | .yes => |p| p, | ||
| 425 | }; | ||
| 430 | 426 | ||
| 431 | var zig_compiler = try ZigCompiler.init(allocator); | 427 | const root_pkg = try Package.create(gpa, fs.cwd(), ".", src_path); |
| 432 | defer zig_compiler.deinit(); | 428 | defer root_pkg.destroy(); |
| 429 | |||
| 430 | var module = try Module.init(gpa, .{ | ||
| 431 | .target = target_info.target, | ||
| 432 | .output_mode = output_mode, | ||
| 433 | .root_pkg = root_pkg, | ||
| 434 | .bin_file_dir = fs.cwd(), | ||
| 435 | .bin_file_path = bin_path, | ||
| 436 | .link_mode = link_mode, | ||
| 437 | .object_format = object_format, | ||
| 438 | .optimize_mode = build_mode, | ||
| 439 | }); | ||
| 440 | defer module.deinit(); | ||
| 433 | 441 | ||
| 434 | var comp = try Compilation.create( | 442 | const stdin = std.io.getStdIn().inStream(); |
| 435 | &zig_compiler, | 443 | const stderr = std.io.getStdErr().outStream(); |
| 436 | root_name, | 444 | var repl_buf: [1024]u8 = undefined; |
| 437 | root_src_file, | ||
| 438 | .{}, | ||
| 439 | out_type, | ||
| 440 | build_mode, | ||
| 441 | !is_dynamic, | ||
| 442 | zig_lib_dir, | ||
| 443 | ); | ||
| 444 | defer comp.destroy(); | ||
| 445 | 445 | ||
| 446 | if (libc_arg) |libc_path| { | 446 | try updateModule(gpa, &module, zir_out_path); |
| 447 | parseLibcPaths(allocator, &override_libc, libc_path); | ||
| 448 | comp.override_libc = &override_libc; | ||
| 449 | } | ||
| 450 | 447 | ||
| 451 | for (system_libs.span()) |lib| { | 448 | while (watch) { |
| 452 | _ = try comp.addLinkLib(lib, true); | 449 | try stderr.print("🦎 ", .{}); |
| 450 | if (output_mode == .Exe) { | ||
| 451 | try module.makeBinFileExecutable(); | ||
| 452 | } | ||
| 453 | if (stdin.readUntilDelimiterOrEof(&repl_buf, '\n') catch |err| { | ||
| 454 | try stderr.print("\nUnable to parse command: {}\n", .{@errorName(err)}); | ||
| 455 | continue; | ||
| 456 | }) |line| { | ||
| 457 | if (mem.eql(u8, line, "update")) { | ||
| 458 | if (output_mode == .Exe) { | ||
| 459 | try module.makeBinFileWritable(); | ||
| 460 | } | ||
| 461 | try updateModule(gpa, &module, zir_out_path); | ||
| 462 | } else if (mem.eql(u8, line, "exit")) { | ||
| 463 | break; | ||
| 464 | } else if (mem.eql(u8, line, "help")) { | ||
| 465 | try stderr.writeAll(repl_help); | ||
| 466 | } else { | ||
| 467 | try stderr.print("unknown command: {}\n", .{line}); | ||
| 468 | } | ||
| 469 | } else { | ||
| 470 | break; | ||
| 471 | } | ||
| 453 | } | 472 | } |
| 473 | } | ||
| 454 | 474 | ||
| 455 | comp.version = version; | 475 | fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !void { |
| 456 | comp.is_test = false; | 476 | try module.update(); |
| 457 | comp.linker_script = linker_script; | ||
| 458 | comp.clang_argv = clang_argv_buf.span(); | ||
| 459 | comp.strip = strip; | ||
| 460 | |||
| 461 | comp.verbose_tokenize = verbose_tokenize; | ||
| 462 | comp.verbose_ast_tree = verbose_ast_tree; | ||
| 463 | comp.verbose_ast_fmt = verbose_ast_fmt; | ||
| 464 | comp.verbose_link = verbose_link; | ||
| 465 | comp.verbose_ir = verbose_ir; | ||
| 466 | comp.verbose_llvm_ir = verbose_llvm_ir; | ||
| 467 | comp.verbose_cimport = verbose_cimport; | ||
| 468 | |||
| 469 | comp.link_eh_frame_hdr = link_eh_frame_hdr; | ||
| 470 | |||
| 471 | comp.err_color = color; | ||
| 472 | 477 | ||
| 473 | comp.linker_rdynamic = linker_rdynamic; | 478 | var errors = try module.getAllErrorsAlloc(); |
| 479 | defer errors.deinit(module.allocator); | ||
| 474 | 480 | ||
| 475 | if (macosx_version_min != null and ios_version_min != null) { | 481 | if (errors.list.len != 0) { |
| 476 | try stderr.writeAll("-mmacosx-version-min and -mios-version-min options not allowed together\n"); | 482 | for (errors.list) |full_err_msg| { |
| 477 | process.exit(1); | 483 | std.debug.warn("{}:{}:{}: error: {}\n", .{ |
| 484 | full_err_msg.src_path, | ||
| 485 | full_err_msg.line + 1, | ||
| 486 | full_err_msg.column + 1, | ||
| 487 | full_err_msg.msg, | ||
| 488 | }); | ||
| 489 | } | ||
| 478 | } | 490 | } |
| 479 | 491 | ||
| 480 | if (macosx_version_min) |ver| { | 492 | if (zir_out_path) |zop| { |
| 481 | comp.darwin_version_min = Compilation.DarwinVersionMin{ .MacOS = ver }; | 493 | var new_zir_module = try zir.emit(gpa, module.*); |
| 482 | } | 494 | defer new_zir_module.deinit(gpa); |
| 483 | if (ios_version_min) |ver| { | ||
| 484 | comp.darwin_version_min = Compilation.DarwinVersionMin{ .Ios = ver }; | ||
| 485 | } | ||
| 486 | 495 | ||
| 487 | comp.emit_bin = emit_bin; | 496 | const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{}); |
| 488 | comp.emit_asm = emit_asm; | 497 | defer baf.destroy(); |
| 489 | comp.emit_llvm_ir = emit_llvm_ir; | ||
| 490 | comp.emit_h = emit_h; | ||
| 491 | comp.assembly_files = assembly_files.span(); | ||
| 492 | comp.link_objects = link_objects.span(); | ||
| 493 | 498 | ||
| 494 | comp.start(); | 499 | try new_zir_module.writeToStream(gpa, baf.stream()); |
| 495 | processBuildEvents(comp, color); | ||
| 496 | } | ||
| 497 | 500 | ||
| 498 | fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { | 501 | try baf.finish(); |
| 499 | const stderr_file = io.getStdErr(); | ||
| 500 | const stderr = stderr_file.outStream(); | ||
| 501 | var count: usize = 0; | ||
| 502 | while (!comp.cancelled) { | ||
| 503 | const build_event = comp.events.get(); | ||
| 504 | count += 1; | ||
| 505 | |||
| 506 | switch (build_event) { | ||
| 507 | .Ok => { | ||
| 508 | stderr.print("Build {} succeeded\n", .{count}) catch process.exit(1); | ||
| 509 | }, | ||
| 510 | .Error => |err| { | ||
| 511 | stderr.print("Build {} failed: {}\n", .{ count, @errorName(err) }) catch process.exit(1); | ||
| 512 | }, | ||
| 513 | .Fail => |msgs| { | ||
| 514 | stderr.print("Build {} compile errors:\n", .{count}) catch process.exit(1); | ||
| 515 | for (msgs) |msg| { | ||
| 516 | defer msg.destroy(); | ||
| 517 | msg.printToFile(stderr_file, color) catch process.exit(1); | ||
| 518 | } | ||
| 519 | }, | ||
| 520 | } | ||
| 521 | } | 502 | } |
| 522 | } | 503 | } |
| 523 | 504 | ||
| 505 | const repl_help = | ||
| 506 | \\Commands: | ||
| 507 | \\ update Detect changes to source files and update output files. | ||
| 508 | \\ help Print this text | ||
| 509 | \\ exit Quit this repl | ||
| 510 | \\ | ||
| 511 | ; | ||
| 512 | |||
| 524 | pub const usage_fmt = | 513 | pub const usage_fmt = |
| 525 | \\usage: zig fmt [file]... | 514 | \\usage: zig fmt [file]... |
| 526 | \\ | 515 | \\ |
| ... | @@ -539,58 +528,20 @@ pub const usage_fmt = | ... | @@ -539,58 +528,20 @@ pub const usage_fmt = |
| 539 | ; | 528 | ; |
| 540 | 529 | ||
| 541 | const Fmt = struct { | 530 | const Fmt = struct { |
| 542 | seen: event.Locked(SeenMap), | 531 | seen: SeenMap, |
| 543 | any_error: bool, | 532 | any_error: bool, |
| 544 | color: errmsg.Color, | 533 | color: Color, |
| 545 | allocator: *Allocator, | 534 | gpa: *Allocator, |
| 546 | 535 | ||
| 547 | const SeenMap = std.StringHashMap(void); | 536 | const SeenMap = std.BufSet; |
| 548 | }; | 537 | }; |
| 549 | 538 | ||
| 550 | fn parseLibcPaths(allocator: *Allocator, libc: *LibCInstallation, libc_paths_file: []const u8) void { | 539 | pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { |
| 551 | const stderr = io.getStdErr().outStream(); | ||
| 552 | libc.* = LibCInstallation.parse(allocator, libc_paths_file, stderr) catch |err| { | ||
| 553 | stderr.print("Unable to parse libc path file '{}': {}.\n" ++ | ||
| 554 | "Try running `zig libc` to see an example for the native target.\n", .{ | ||
| 555 | libc_paths_file, | ||
| 556 | @errorName(err), | ||
| 557 | }) catch {}; | ||
| 558 | process.exit(1); | ||
| 559 | }; | ||
| 560 | } | ||
| 561 | |||
| 562 | fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void { | ||
| 563 | const stderr = io.getStdErr().outStream(); | ||
| 564 | switch (args.len) { | ||
| 565 | 0 => {}, | ||
| 566 | 1 => { | ||
| 567 | var libc_installation: LibCInstallation = undefined; | ||
| 568 | parseLibcPaths(allocator, &libc_installation, args[0]); | ||
| 569 | return; | ||
| 570 | }, | ||
| 571 | else => { | ||
| 572 | try stderr.print("unexpected extra parameter: {}\n", .{args[1]}); | ||
| 573 | process.exit(1); | ||
| 574 | }, | ||
| 575 | } | ||
| 576 | |||
| 577 | var zig_compiler = try ZigCompiler.init(allocator); | ||
| 578 | defer zig_compiler.deinit(); | ||
| 579 | |||
| 580 | const libc = zig_compiler.getNativeLibC() catch |err| { | ||
| 581 | stderr.print("unable to find libc: {}\n", .{@errorName(err)}) catch {}; | ||
| 582 | process.exit(1); | ||
| 583 | }; | ||
| 584 | libc.render(io.getStdOut().outStream()) catch process.exit(1); | ||
| 585 | } | ||
| 586 | |||
| 587 | fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { | ||
| 588 | const stderr_file = io.getStdErr(); | 540 | const stderr_file = io.getStdErr(); |
| 589 | const stderr = stderr_file.outStream(); | 541 | var color: Color = .Auto; |
| 590 | var color: errmsg.Color = .Auto; | ||
| 591 | var stdin_flag: bool = false; | 542 | var stdin_flag: bool = false; |
| 592 | var check_flag: bool = false; | 543 | var check_flag: bool = false; |
| 593 | var input_files = ArrayList([]const u8).init(allocator); | 544 | var input_files = ArrayList([]const u8).init(gpa); |
| 594 | 545 | ||
| 595 | { | 546 | { |
| 596 | var i: usize = 0; | 547 | var i: usize = 0; |
| ... | @@ -603,7 +554,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { | ... | @@ -603,7 +554,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { |
| 603 | process.exit(0); | 554 | process.exit(0); |
| 604 | } else if (mem.eql(u8, arg, "--color")) { | 555 | } else if (mem.eql(u8, arg, "--color")) { |
| 605 | if (i + 1 >= args.len) { | 556 | if (i + 1 >= args.len) { |
| 606 | try stderr.writeAll("expected [auto|on|off] after --color\n"); | 557 | std.debug.warn("expected [auto|on|off] after --color\n", .{}); |
| 607 | process.exit(1); | 558 | process.exit(1); |
| 608 | } | 559 | } |
| 609 | i += 1; | 560 | i += 1; |
| ... | @@ -615,7 +566,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { | ... | @@ -615,7 +566,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { |
| 615 | } else if (mem.eql(u8, next_arg, "off")) { | 566 | } else if (mem.eql(u8, next_arg, "off")) { |
| 616 | color = .Off; | 567 | color = .Off; |
| 617 | } else { | 568 | } else { |
| 618 | try stderr.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); | 569 | std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); |
| 619 | process.exit(1); | 570 | process.exit(1); |
| 620 | } | 571 | } |
| 621 | } else if (mem.eql(u8, arg, "--stdin")) { | 572 | } else if (mem.eql(u8, arg, "--stdin")) { |
| ... | @@ -623,7 +574,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { | ... | @@ -623,7 +574,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { |
| 623 | } else if (mem.eql(u8, arg, "--check")) { | 574 | } else if (mem.eql(u8, arg, "--check")) { |
| 624 | check_flag = true; | 575 | check_flag = true; |
| 625 | } else { | 576 | } else { |
| 626 | try stderr.print("unrecognized parameter: '{}'", .{arg}); | 577 | std.debug.warn("unrecognized parameter: '{}'", .{arg}); |
| 627 | process.exit(1); | 578 | process.exit(1); |
| 628 | } | 579 | } |
| 629 | } else { | 580 | } else { |
| ... | @@ -633,60 +584,55 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { | ... | @@ -633,60 +584,55 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { |
| 633 | } | 584 | } |
| 634 | 585 | ||
| 635 | if (stdin_flag) { | 586 | if (stdin_flag) { |
| 636 | if (input_files.len != 0) { | 587 | if (input_files.items.len != 0) { |
| 637 | try stderr.writeAll("cannot use --stdin with positional arguments\n"); | 588 | std.debug.warn("cannot use --stdin with positional arguments\n", .{}); |
| 638 | process.exit(1); | 589 | process.exit(1); |
| 639 | } | 590 | } |
| 640 | 591 | ||
| 641 | const stdin = io.getStdIn().inStream(); | 592 | const stdin = io.getStdIn().inStream(); |
| 642 | 593 | ||
| 643 | const source_code = try stdin.readAllAlloc(allocator, max_src_size); | 594 | const source_code = try stdin.readAllAlloc(gpa, max_src_size); |
| 644 | defer allocator.free(source_code); | 595 | defer gpa.free(source_code); |
| 645 | 596 | ||
| 646 | const tree = std.zig.parse(allocator, source_code) catch |err| { | 597 | const tree = std.zig.parse(gpa, source_code) catch |err| { |
| 647 | try stderr.print("error parsing stdin: {}\n", .{err}); | 598 | std.debug.warn("error parsing stdin: {}\n", .{err}); |
| 648 | process.exit(1); | 599 | process.exit(1); |
| 649 | }; | 600 | }; |
| 650 | defer tree.deinit(); | 601 | defer tree.deinit(); |
| 651 | 602 | ||
| 652 | var error_it = tree.errors.iterator(0); | 603 | var error_it = tree.errors.iterator(0); |
| 653 | while (error_it.next()) |parse_error| { | 604 | while (error_it.next()) |parse_error| { |
| 654 | const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, tree, "<stdin>"); | 605 | try printErrMsgToFile(gpa, parse_error, tree, "<stdin>", stderr_file, color); |
| 655 | defer msg.destroy(); | ||
| 656 | |||
| 657 | try msg.printToFile(io.getStdErr(), color); | ||
| 658 | } | 606 | } |
| 659 | if (tree.errors.len != 0) { | 607 | if (tree.errors.len != 0) { |
| 660 | process.exit(1); | 608 | process.exit(1); |
| 661 | } | 609 | } |
| 662 | if (check_flag) { | 610 | if (check_flag) { |
| 663 | const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree); | 611 | const anything_changed = try std.zig.render(gpa, io.null_out_stream, tree); |
| 664 | const code: u8 = if (anything_changed) 1 else 0; | 612 | const code = if (anything_changed) @as(u8, 1) else @as(u8, 0); |
| 665 | process.exit(code); | 613 | process.exit(code); |
| 666 | } | 614 | } |
| 667 | 615 | ||
| 668 | const stdout = io.getStdOut().outStream(); | 616 | const stdout = io.getStdOut().outStream(); |
| 669 | _ = try std.zig.render(allocator, stdout, tree); | 617 | _ = try std.zig.render(gpa, stdout, tree); |
| 670 | return; | 618 | return; |
| 671 | } | 619 | } |
| 672 | 620 | ||
| 673 | if (input_files.len == 0) { | 621 | if (input_files.items.len == 0) { |
| 674 | try stderr.writeAll("expected at least one source file argument\n"); | 622 | std.debug.warn("expected at least one source file argument\n", .{}); |
| 675 | process.exit(1); | 623 | process.exit(1); |
| 676 | } | 624 | } |
| 677 | 625 | ||
| 678 | var fmt = Fmt{ | 626 | var fmt = Fmt{ |
| 679 | .allocator = allocator, | 627 | .gpa = gpa, |
| 680 | .seen = event.Locked(Fmt.SeenMap).init(Fmt.SeenMap.init(allocator)), | 628 | .seen = Fmt.SeenMap.init(gpa), |
| 681 | .any_error = false, | 629 | .any_error = false, |
| 682 | .color = color, | 630 | .color = color, |
| 683 | }; | 631 | }; |
| 684 | 632 | ||
| 685 | var group = event.Group(FmtError!void).init(allocator); | ||
| 686 | for (input_files.span()) |file_path| { | 633 | for (input_files.span()) |file_path| { |
| 687 | try group.call(fmtPath, .{ &fmt, file_path, check_flag }); | 634 | try fmtPath(&fmt, file_path, check_flag); |
| 688 | } | 635 | } |
| 689 | try group.wait(); | ||
| 690 | if (fmt.any_error) { | 636 | if (fmt.any_error) { |
| 691 | process.exit(1); | 637 | process.exit(1); |
| 692 | } | 638 | } |
| ... | @@ -711,53 +657,45 @@ const FmtError = error{ | ... | @@ -711,53 +657,45 @@ const FmtError = error{ |
| 711 | ReadOnlyFileSystem, | 657 | ReadOnlyFileSystem, |
| 712 | LinkQuotaExceeded, | 658 | LinkQuotaExceeded, |
| 713 | FileBusy, | 659 | FileBusy, |
| 714 | CurrentWorkingDirectoryUnlinked, | ||
| 715 | } || fs.File.OpenError; | 660 | } || fs.File.OpenError; |
| 716 | fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) callconv(.Async) FmtError!void { | ||
| 717 | const stderr_file = io.getStdErr(); | ||
| 718 | const stderr = stderr_file.outStream(); | ||
| 719 | |||
| 720 | const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref); | ||
| 721 | defer fmt.allocator.free(file_path); | ||
| 722 | 661 | ||
| 723 | { | 662 | fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { |
| 724 | const held = fmt.seen.acquire(); | 663 | // get the real path here to avoid Windows failing on relative file paths with . or .. in them |
| 725 | defer held.release(); | 664 | var real_path = fs.realpathAlloc(fmt.gpa, file_path) catch |err| { |
| 665 | std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); | ||
| 666 | fmt.any_error = true; | ||
| 667 | return; | ||
| 668 | }; | ||
| 669 | defer fmt.gpa.free(real_path); | ||
| 726 | 670 | ||
| 727 | if (try held.value.put(file_path, {})) |_| return; | 671 | if (fmt.seen.exists(real_path)) return; |
| 728 | } | 672 | try fmt.seen.put(real_path); |
| 729 | 673 | ||
| 730 | const source_code = fs.cwd().readFileAlloc( | 674 | const source_code = fs.cwd().readFileAlloc(fmt.gpa, real_path, max_src_size) catch |err| switch (err) { |
| 731 | fmt.allocator, | ||
| 732 | file_path, | ||
| 733 | max_src_size, | ||
| 734 | ) catch |err| switch (err) { | ||
| 735 | error.IsDir, error.AccessDenied => { | 675 | error.IsDir, error.AccessDenied => { |
| 736 | var dir = try fs.cwd().openDir(file_path, .{ .iterate = true }); | 676 | var dir = try fs.cwd().openDir(file_path, .{ .iterate = true }); |
| 737 | defer dir.close(); | 677 | defer dir.close(); |
| 738 | 678 | ||
| 739 | var group = event.Group(FmtError!void).init(fmt.allocator); | 679 | var dir_it = dir.iterate(); |
| 740 | var it = dir.iterate(); | 680 | |
| 741 | while (try it.next()) |entry| { | 681 | while (try dir_it.next()) |entry| { |
| 742 | if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) { | 682 | if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) { |
| 743 | const full_path = try fs.path.join(fmt.allocator, &[_][]const u8{ file_path, entry.name }); | 683 | const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name }); |
| 744 | @panic("TODO https://github.com/ziglang/zig/issues/3777"); | 684 | try fmtPath(fmt, full_path, check_mode); |
| 745 | // try group.call(fmtPath, .{fmt, full_path, check_mode}); | ||
| 746 | } | 685 | } |
| 747 | } | 686 | } |
| 748 | return group.wait(); | 687 | return; |
| 749 | }, | 688 | }, |
| 750 | else => { | 689 | else => { |
| 751 | // TODO lock stderr printing | 690 | std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); |
| 752 | try stderr.print("unable to open '{}': {}\n", .{ file_path, err }); | ||
| 753 | fmt.any_error = true; | 691 | fmt.any_error = true; |
| 754 | return; | 692 | return; |
| 755 | }, | 693 | }, |
| 756 | }; | 694 | }; |
| 757 | defer fmt.allocator.free(source_code); | 695 | defer fmt.gpa.free(source_code); |
| 758 | 696 | ||
| 759 | const tree = std.zig.parse(fmt.allocator, source_code) catch |err| { | 697 | const tree = std.zig.parse(fmt.gpa, source_code) catch |err| { |
| 760 | try stderr.print("error parsing file '{}': {}\n", .{ file_path, err }); | 698 | std.debug.warn("error parsing file '{}': {}\n", .{ file_path, err }); |
| 761 | fmt.any_error = true; | 699 | fmt.any_error = true; |
| 762 | return; | 700 | return; |
| 763 | }; | 701 | }; |
| ... | @@ -765,10 +703,7 @@ fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) callconv(.Asy | ... | @@ -765,10 +703,7 @@ fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) callconv(.Asy |
| 765 | 703 | ||
| 766 | var error_it = tree.errors.iterator(0); | 704 | var error_it = tree.errors.iterator(0); |
| 767 | while (error_it.next()) |parse_error| { | 705 | while (error_it.next()) |parse_error| { |
| 768 | const msg = try errmsg.Msg.createFromParseError(fmt.allocator, parse_error, tree, file_path); | 706 | try printErrMsgToFile(fmt.gpa, parse_error, tree, file_path, std.io.getStdErr(), fmt.color); |
| 769 | defer fmt.allocator.destroy(msg); | ||
| 770 | |||
| 771 | try msg.printToFile(stderr_file, fmt.color); | ||
| 772 | } | 707 | } |
| 773 | if (tree.errors.len != 0) { | 708 | if (tree.errors.len != 0) { |
| 774 | fmt.any_error = true; | 709 | fmt.any_error = true; |
| ... | @@ -776,32 +711,67 @@ fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) callconv(.Asy | ... | @@ -776,32 +711,67 @@ fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) callconv(.Asy |
| 776 | } | 711 | } |
| 777 | 712 | ||
| 778 | if (check_mode) { | 713 | if (check_mode) { |
| 779 | const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree); | 714 | const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree); |
| 780 | if (anything_changed) { | 715 | if (anything_changed) { |
| 781 | try stderr.print("{}\n", .{file_path}); | 716 | std.debug.warn("{}\n", .{file_path}); |
| 782 | fmt.any_error = true; | 717 | fmt.any_error = true; |
| 783 | } | 718 | } |
| 784 | } else { | 719 | } else { |
| 785 | // TODO make this evented | 720 | const baf = try io.BufferedAtomicFile.create(fmt.gpa, fs.cwd(), real_path, .{}); |
| 786 | const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path); | ||
| 787 | defer baf.destroy(); | 721 | defer baf.destroy(); |
| 788 | 722 | ||
| 789 | const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree); | 723 | const anything_changed = try std.zig.render(fmt.gpa, baf.stream(), tree); |
| 790 | if (anything_changed) { | 724 | if (anything_changed) { |
| 791 | try stderr.print("{}\n", .{file_path}); | 725 | std.debug.warn("{}\n", .{file_path}); |
| 792 | try baf.finish(); | 726 | try baf.finish(); |
| 793 | } | 727 | } |
| 794 | } | 728 | } |
| 795 | } | 729 | } |
| 796 | 730 | ||
| 797 | fn cmdVersion(allocator: *Allocator, args: []const []const u8) !void { | 731 | fn printErrMsgToFile( |
| 798 | const stdout = io.getStdOut().outStream(); | 732 | gpa: *mem.Allocator, |
| 799 | try stdout.print("{}\n", .{c.ZIG_VERSION_STRING}); | 733 | parse_error: *const ast.Error, |
| 800 | } | 734 | tree: *ast.Tree, |
| 801 | 735 | path: []const u8, | |
| 802 | fn cmdHelp(allocator: *Allocator, args: []const []const u8) !void { | 736 | file: fs.File, |
| 803 | const stdout = io.getStdOut(); | 737 | color: Color, |
| 804 | try stdout.writeAll(usage); | 738 | ) !void { |
| 739 | const color_on = switch (color) { | ||
| 740 | .Auto => file.isTty(), | ||
| 741 | .On => true, | ||
| 742 | .Off => false, | ||
| 743 | }; | ||
| 744 | const lok_token = parse_error.loc(); | ||
| 745 | const span_first = lok_token; | ||
| 746 | const span_last = lok_token; | ||
| 747 | |||
| 748 | const first_token = tree.tokens.at(span_first); | ||
| 749 | const last_token = tree.tokens.at(span_last); | ||
| 750 | const start_loc = tree.tokenLocationPtr(0, first_token); | ||
| 751 | const end_loc = tree.tokenLocationPtr(first_token.end, last_token); | ||
| 752 | |||
| 753 | var text_buf = std.ArrayList(u8).init(gpa); | ||
| 754 | defer text_buf.deinit(); | ||
| 755 | const out_stream = text_buf.outStream(); | ||
| 756 | try parse_error.render(&tree.tokens, out_stream); | ||
| 757 | const text = text_buf.span(); | ||
| 758 | |||
| 759 | const stream = file.outStream(); | ||
| 760 | try stream.print("{}:{}:{}: error: {}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text }); | ||
| 761 | |||
| 762 | if (!color_on) return; | ||
| 763 | |||
| 764 | // Print \r and \t as one space each so that column counts line up | ||
| 765 | for (tree.source[start_loc.line_start..start_loc.line_end]) |byte| { | ||
| 766 | try stream.writeByte(switch (byte) { | ||
| 767 | '\r', '\t' => ' ', | ||
| 768 | else => byte, | ||
| 769 | }); | ||
| 770 | } | ||
| 771 | try stream.writeByte('\n'); | ||
| 772 | try stream.writeByteNTimes(' ', start_loc.column); | ||
| 773 | try stream.writeByteNTimes('~', last_token.end - first_token.start); | ||
| 774 | try stream.writeByte('\n'); | ||
| 805 | } | 775 | } |
| 806 | 776 | ||
| 807 | pub const info_zen = | 777 | pub const info_zen = |
| ... | @@ -816,90 +786,8 @@ pub const info_zen = | ... | @@ -816,90 +786,8 @@ pub const info_zen = |
| 816 | \\ * Avoid local maximums. | 786 | \\ * Avoid local maximums. |
| 817 | \\ * Reduce the amount one must remember. | 787 | \\ * Reduce the amount one must remember. |
| 818 | \\ * Minimize energy spent on coding style. | 788 | \\ * Minimize energy spent on coding style. |
| 789 | \\ * Resource deallocation must succeed. | ||
| 819 | \\ * Together we serve end users. | 790 | \\ * Together we serve end users. |
| 820 | \\ | 791 | \\ |
| 821 | \\ | 792 | \\ |
| 822 | ; | 793 | ; |
| 823 | |||
| 824 | fn cmdZen(allocator: *Allocator, args: []const []const u8) !void { | ||
| 825 | try io.getStdOut().writeAll(info_zen); | ||
| 826 | } | ||
| 827 | |||
| 828 | const usage_internal = | ||
| 829 | \\usage: zig internal [subcommand] | ||
| 830 | \\ | ||
| 831 | \\Sub-Commands: | ||
| 832 | \\ build-info Print static compiler build-info | ||
| 833 | \\ | ||
| 834 | \\ | ||
| 835 | ; | ||
| 836 | |||
| 837 | fn cmdInternal(allocator: *Allocator, args: []const []const u8) !void { | ||
| 838 | const stderr = io.getStdErr().outStream(); | ||
| 839 | if (args.len == 0) { | ||
| 840 | try stderr.writeAll(usage_internal); | ||
| 841 | process.exit(1); | ||
| 842 | } | ||
| 843 | |||
| 844 | const sub_commands = [_]Command{Command{ | ||
| 845 | .name = "build-info", | ||
| 846 | .exec = cmdInternalBuildInfo, | ||
| 847 | }}; | ||
| 848 | |||
| 849 | inline for (sub_commands) |sub_command| { | ||
| 850 | if (mem.eql(u8, sub_command.name, args[0])) { | ||
| 851 | var frame = try allocator.create(@Frame(sub_command.exec)); | ||
| 852 | defer allocator.destroy(frame); | ||
| 853 | frame.* = async sub_command.exec(allocator, args[1..]); | ||
| 854 | return await frame; | ||
| 855 | } | ||
| 856 | } | ||
| 857 | |||
| 858 | try stderr.print("unknown sub command: {}\n\n", .{args[0]}); | ||
| 859 | try stderr.writeAll(usage_internal); | ||
| 860 | } | ||
| 861 | |||
| 862 | fn cmdInternalBuildInfo(allocator: *Allocator, args: []const []const u8) !void { | ||
| 863 | const stdout = io.getStdOut().outStream(); | ||
| 864 | try stdout.print( | ||
| 865 | \\ZIG_CMAKE_BINARY_DIR {} | ||
| 866 | \\ZIG_CXX_COMPILER {} | ||
| 867 | \\ZIG_LLD_INCLUDE_PATH {} | ||
| 868 | \\ZIG_LLD_LIBRARIES {} | ||
| 869 | \\ZIG_LLVM_CONFIG_EXE {} | ||
| 870 | \\ZIG_DIA_GUIDS_LIB {} | ||
| 871 | \\ | ||
| 872 | , .{ | ||
| 873 | c.ZIG_CMAKE_BINARY_DIR, | ||
| 874 | c.ZIG_CXX_COMPILER, | ||
| 875 | c.ZIG_LLD_INCLUDE_PATH, | ||
| 876 | c.ZIG_LLD_LIBRARIES, | ||
| 877 | c.ZIG_LLVM_CONFIG_EXE, | ||
| 878 | c.ZIG_DIA_GUIDS_LIB, | ||
| 879 | }); | ||
| 880 | } | ||
| 881 | |||
| 882 | const CliPkg = struct { | ||
| 883 | name: []const u8, | ||
| 884 | path: []const u8, | ||
| 885 | children: ArrayList(*CliPkg), | ||
| 886 | parent: ?*CliPkg, | ||
| 887 | |||
| 888 | pub fn init(allocator: *mem.Allocator, name: []const u8, path: []const u8, parent: ?*CliPkg) !*CliPkg { | ||
| 889 | var pkg = try allocator.create(CliPkg); | ||
| 890 | pkg.* = CliPkg{ | ||
| 891 | .name = name, | ||
| 892 | .path = path, | ||
| 893 | .children = ArrayList(*CliPkg).init(allocator), | ||
| 894 | .parent = parent, | ||
| 895 | }; | ||
| 896 | return pkg; | ||
| 897 | } | ||
| 898 | |||
| 899 | pub fn deinit(self: *CliPkg) void { | ||
| 900 | for (self.children.span()) |child| { | ||
| 901 | child.deinit(); | ||
| 902 | } | ||
| 903 | self.children.deinit(); | ||
| 904 | } | ||
| 905 | }; |
src-self-hosted/package.zig deleted-31| ... | @@ -1,31 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const mem = std.mem; | ||
| 3 | const assert = std.debug.assert; | ||
| 4 | const ArrayListSentineled = std.ArrayListSentineled; | ||
| 5 | |||
| 6 | pub const Package = struct { | ||
| 7 | root_src_dir: ArrayListSentineled(u8, 0), | ||
| 8 | root_src_path: ArrayListSentineled(u8, 0), | ||
| 9 | |||
| 10 | /// relative to root_src_dir | ||
| 11 | table: Table, | ||
| 12 | |||
| 13 | pub const Table = std.StringHashMap(*Package); | ||
| 14 | |||
| 15 | /// makes internal copies of root_src_dir and root_src_path | ||
| 16 | /// allocator should be an arena allocator because Package never frees anything | ||
| 17 | pub fn create(allocator: *mem.Allocator, root_src_dir: []const u8, root_src_path: []const u8) !*Package { | ||
| 18 | const ptr = try allocator.create(Package); | ||
| 19 | ptr.* = Package{ | ||
| 20 | .root_src_dir = try ArrayListSentineled(u8, 0).init(allocator, root_src_dir), | ||
| 21 | .root_src_path = try ArrayListSentineled(u8, 0).init(allocator, root_src_path), | ||
| 22 | .table = Table.init(allocator), | ||
| 23 | }; | ||
| 24 | return ptr; | ||
| 25 | } | ||
| 26 | |||
| 27 | pub fn add(self: *Package, name: []const u8, package: *Package) !void { | ||
| 28 | const entry = try self.table.put(try mem.dupe(self.table.allocator, u8, name), package); | ||
| 29 | assert(entry == null); | ||
| 30 | } | ||
| 31 | }; | ||
src-self-hosted/scope.zig deleted-418| ... | @@ -1,418 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Allocator = mem.Allocator; | ||
| 3 | const Decl = @import("decl.zig").Decl; | ||
| 4 | const Compilation = @import("compilation.zig").Compilation; | ||
| 5 | const mem = std.mem; | ||
| 6 | const ast = std.zig.ast; | ||
| 7 | const Value = @import("value.zig").Value; | ||
| 8 | const Type = @import("type.zig").Type; | ||
| 9 | const ir = @import("ir.zig"); | ||
| 10 | const Span = @import("errmsg.zig").Span; | ||
| 11 | const assert = std.debug.assert; | ||
| 12 | const event = std.event; | ||
| 13 | const llvm = @import("llvm.zig"); | ||
| 14 | |||
| 15 | pub const Scope = struct { | ||
| 16 | id: Id, | ||
| 17 | parent: ?*Scope, | ||
| 18 | ref_count: std.atomic.Int(usize), | ||
| 19 | |||
| 20 | /// Thread-safe | ||
| 21 | pub fn ref(base: *Scope) void { | ||
| 22 | _ = base.ref_count.incr(); | ||
| 23 | } | ||
| 24 | |||
| 25 | /// Thread-safe | ||
| 26 | pub fn deref(base: *Scope, comp: *Compilation) void { | ||
| 27 | if (base.ref_count.decr() == 1) { | ||
| 28 | if (base.parent) |parent| parent.deref(comp); | ||
| 29 | switch (base.id) { | ||
| 30 | .Root => @fieldParentPtr(Root, "base", base).destroy(comp), | ||
| 31 | .Decls => @fieldParentPtr(Decls, "base", base).destroy(comp), | ||
| 32 | .Block => @fieldParentPtr(Block, "base", base).destroy(comp), | ||
| 33 | .FnDef => @fieldParentPtr(FnDef, "base", base).destroy(comp), | ||
| 34 | .CompTime => @fieldParentPtr(CompTime, "base", base).destroy(comp), | ||
| 35 | .Defer => @fieldParentPtr(Defer, "base", base).destroy(comp), | ||
| 36 | .DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(comp), | ||
| 37 | .Var => @fieldParentPtr(Var, "base", base).destroy(comp), | ||
| 38 | .AstTree => @fieldParentPtr(AstTree, "base", base).destroy(comp), | ||
| 39 | } | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | pub fn findRoot(base: *Scope) *Root { | ||
| 44 | var scope = base; | ||
| 45 | while (scope.parent) |parent| { | ||
| 46 | scope = parent; | ||
| 47 | } | ||
| 48 | assert(scope.id == .Root); | ||
| 49 | return @fieldParentPtr(Root, "base", scope); | ||
| 50 | } | ||
| 51 | |||
| 52 | pub fn findFnDef(base: *Scope) ?*FnDef { | ||
| 53 | var scope = base; | ||
| 54 | while (true) { | ||
| 55 | switch (scope.id) { | ||
| 56 | .FnDef => return @fieldParentPtr(FnDef, "base", scope), | ||
| 57 | .Root, .Decls => return null, | ||
| 58 | |||
| 59 | .Block, | ||
| 60 | .Defer, | ||
| 61 | .DeferExpr, | ||
| 62 | .CompTime, | ||
| 63 | .Var, | ||
| 64 | => scope = scope.parent.?, | ||
| 65 | |||
| 66 | .AstTree => unreachable, | ||
| 67 | } | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | pub fn findDeferExpr(base: *Scope) ?*DeferExpr { | ||
| 72 | var scope = base; | ||
| 73 | while (true) { | ||
| 74 | switch (scope.id) { | ||
| 75 | .DeferExpr => return @fieldParentPtr(DeferExpr, "base", scope), | ||
| 76 | |||
| 77 | .FnDef, | ||
| 78 | .Decls, | ||
| 79 | => return null, | ||
| 80 | |||
| 81 | .Block, | ||
| 82 | .Defer, | ||
| 83 | .CompTime, | ||
| 84 | .Root, | ||
| 85 | .Var, | ||
| 86 | => scope = scope.parent orelse return null, | ||
| 87 | |||
| 88 | .AstTree => unreachable, | ||
| 89 | } | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | fn init(base: *Scope, id: Id, parent: *Scope) void { | ||
| 94 | base.* = Scope{ | ||
| 95 | .id = id, | ||
| 96 | .parent = parent, | ||
| 97 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 98 | }; | ||
| 99 | parent.ref(); | ||
| 100 | } | ||
| 101 | |||
| 102 | pub const Id = enum { | ||
| 103 | Root, | ||
| 104 | AstTree, | ||
| 105 | Decls, | ||
| 106 | Block, | ||
| 107 | FnDef, | ||
| 108 | CompTime, | ||
| 109 | Defer, | ||
| 110 | DeferExpr, | ||
| 111 | Var, | ||
| 112 | }; | ||
| 113 | |||
| 114 | pub const Root = struct { | ||
| 115 | base: Scope, | ||
| 116 | realpath: []const u8, | ||
| 117 | decls: *Decls, | ||
| 118 | |||
| 119 | /// Creates a Root scope with 1 reference | ||
| 120 | /// Takes ownership of realpath | ||
| 121 | pub fn create(comp: *Compilation, realpath: []u8) !*Root { | ||
| 122 | const self = try comp.gpa().create(Root); | ||
| 123 | self.* = Root{ | ||
| 124 | .base = Scope{ | ||
| 125 | .id = .Root, | ||
| 126 | .parent = null, | ||
| 127 | .ref_count = std.atomic.Int(usize).init(1), | ||
| 128 | }, | ||
| 129 | .realpath = realpath, | ||
| 130 | .decls = undefined, | ||
| 131 | }; | ||
| 132 | errdefer comp.gpa().destroy(self); | ||
| 133 | self.decls = try Decls.create(comp, &self.base); | ||
| 134 | return self; | ||
| 135 | } | ||
| 136 | |||
| 137 | pub fn destroy(self: *Root, comp: *Compilation) void { | ||
| 138 | // TODO comp.fs_watch.removeFile(self.realpath); | ||
| 139 | self.decls.base.deref(comp); | ||
| 140 | comp.gpa().free(self.realpath); | ||
| 141 | comp.gpa().destroy(self); | ||
| 142 | } | ||
| 143 | }; | ||
| 144 | |||
| 145 | pub const AstTree = struct { | ||
| 146 | base: Scope, | ||
| 147 | tree: *ast.Tree, | ||
| 148 | |||
| 149 | /// Creates a scope with 1 reference | ||
| 150 | /// Takes ownership of tree, will deinit and destroy when done. | ||
| 151 | pub fn create(comp: *Compilation, tree: *ast.Tree, root_scope: *Root) !*AstTree { | ||
| 152 | const self = try comp.gpa().create(AstTree); | ||
| 153 | self.* = AstTree{ | ||
| 154 | .base = undefined, | ||
| 155 | .tree = tree, | ||
| 156 | }; | ||
| 157 | self.base.init(.AstTree, &root_scope.base); | ||
| 158 | |||
| 159 | return self; | ||
| 160 | } | ||
| 161 | |||
| 162 | pub fn destroy(self: *AstTree, comp: *Compilation) void { | ||
| 163 | comp.gpa().free(self.tree.source); | ||
| 164 | self.tree.deinit(); | ||
| 165 | comp.gpa().destroy(self); | ||
| 166 | } | ||
| 167 | |||
| 168 | pub fn root(self: *AstTree) *Root { | ||
| 169 | return self.base.findRoot(); | ||
| 170 | } | ||
| 171 | }; | ||
| 172 | |||
| 173 | pub const Decls = struct { | ||
| 174 | base: Scope, | ||
| 175 | |||
| 176 | /// This table remains Write Locked when the names are incomplete or possibly outdated. | ||
| 177 | /// So if a reader manages to grab a lock, it can be sure that the set of names is complete | ||
| 178 | /// and correct. | ||
| 179 | table: event.RwLocked(Decl.Table), | ||
| 180 | |||
| 181 | /// Creates a Decls scope with 1 reference | ||
| 182 | pub fn create(comp: *Compilation, parent: *Scope) !*Decls { | ||
| 183 | const self = try comp.gpa().create(Decls); | ||
| 184 | self.* = Decls{ | ||
| 185 | .base = undefined, | ||
| 186 | .table = event.RwLocked(Decl.Table).init(Decl.Table.init(comp.gpa())), | ||
| 187 | }; | ||
| 188 | self.base.init(.Decls, parent); | ||
| 189 | return self; | ||
| 190 | } | ||
| 191 | |||
| 192 | pub fn destroy(self: *Decls, comp: *Compilation) void { | ||
| 193 | self.table.deinit(); | ||
| 194 | comp.gpa().destroy(self); | ||
| 195 | } | ||
| 196 | }; | ||
| 197 | |||
| 198 | pub const Block = struct { | ||
| 199 | base: Scope, | ||
| 200 | incoming_values: std.ArrayList(*ir.Inst), | ||
| 201 | incoming_blocks: std.ArrayList(*ir.BasicBlock), | ||
| 202 | end_block: *ir.BasicBlock, | ||
| 203 | is_comptime: *ir.Inst, | ||
| 204 | |||
| 205 | safety: Safety, | ||
| 206 | |||
| 207 | const Safety = union(enum) { | ||
| 208 | Auto, | ||
| 209 | Manual: Manual, | ||
| 210 | |||
| 211 | const Manual = struct { | ||
| 212 | /// the source span that disabled the safety value | ||
| 213 | span: Span, | ||
| 214 | |||
| 215 | /// whether safety is enabled | ||
| 216 | enabled: bool, | ||
| 217 | }; | ||
| 218 | |||
| 219 | fn get(self: Safety, comp: *Compilation) bool { | ||
| 220 | return switch (self) { | ||
| 221 | .Auto => switch (comp.build_mode) { | ||
| 222 | .Debug, | ||
| 223 | .ReleaseSafe, | ||
| 224 | => true, | ||
| 225 | .ReleaseFast, | ||
| 226 | .ReleaseSmall, | ||
| 227 | => false, | ||
| 228 | }, | ||
| 229 | .Manual => |man| man.enabled, | ||
| 230 | }; | ||
| 231 | } | ||
| 232 | }; | ||
| 233 | |||
| 234 | /// Creates a Block scope with 1 reference | ||
| 235 | pub fn create(comp: *Compilation, parent: *Scope) !*Block { | ||
| 236 | const self = try comp.gpa().create(Block); | ||
| 237 | self.* = Block{ | ||
| 238 | .base = undefined, | ||
| 239 | .incoming_values = undefined, | ||
| 240 | .incoming_blocks = undefined, | ||
| 241 | .end_block = undefined, | ||
| 242 | .is_comptime = undefined, | ||
| 243 | .safety = Safety.Auto, | ||
| 244 | }; | ||
| 245 | self.base.init(.Block, parent); | ||
| 246 | return self; | ||
| 247 | } | ||
| 248 | |||
| 249 | pub fn destroy(self: *Block, comp: *Compilation) void { | ||
| 250 | comp.gpa().destroy(self); | ||
| 251 | } | ||
| 252 | }; | ||
| 253 | |||
| 254 | pub const FnDef = struct { | ||
| 255 | base: Scope, | ||
| 256 | |||
| 257 | /// This reference is not counted so that the scope can get destroyed with the function | ||
| 258 | fn_val: ?*Value.Fn, | ||
| 259 | |||
| 260 | /// Creates a FnDef scope with 1 reference | ||
| 261 | /// Must set the fn_val later | ||
| 262 | pub fn create(comp: *Compilation, parent: *Scope) !*FnDef { | ||
| 263 | const self = try comp.gpa().create(FnDef); | ||
| 264 | self.* = FnDef{ | ||
| 265 | .base = undefined, | ||
| 266 | .fn_val = null, | ||
| 267 | }; | ||
| 268 | self.base.init(.FnDef, parent); | ||
| 269 | return self; | ||
| 270 | } | ||
| 271 | |||
| 272 | pub fn destroy(self: *FnDef, comp: *Compilation) void { | ||
| 273 | comp.gpa().destroy(self); | ||
| 274 | } | ||
| 275 | }; | ||
| 276 | |||
| 277 | pub const CompTime = struct { | ||
| 278 | base: Scope, | ||
| 279 | |||
| 280 | /// Creates a CompTime scope with 1 reference | ||
| 281 | pub fn create(comp: *Compilation, parent: *Scope) !*CompTime { | ||
| 282 | const self = try comp.gpa().create(CompTime); | ||
| 283 | self.* = CompTime{ .base = undefined }; | ||
| 284 | self.base.init(.CompTime, parent); | ||
| 285 | return self; | ||
| 286 | } | ||
| 287 | |||
| 288 | pub fn destroy(self: *CompTime, comp: *Compilation) void { | ||
| 289 | comp.gpa().destroy(self); | ||
| 290 | } | ||
| 291 | }; | ||
| 292 | |||
| 293 | pub const Defer = struct { | ||
| 294 | base: Scope, | ||
| 295 | defer_expr_scope: *DeferExpr, | ||
| 296 | kind: Kind, | ||
| 297 | |||
| 298 | pub const Kind = enum { | ||
| 299 | ScopeExit, | ||
| 300 | ErrorExit, | ||
| 301 | }; | ||
| 302 | |||
| 303 | /// Creates a Defer scope with 1 reference | ||
| 304 | pub fn create( | ||
| 305 | comp: *Compilation, | ||
| 306 | parent: *Scope, | ||
| 307 | kind: Kind, | ||
| 308 | defer_expr_scope: *DeferExpr, | ||
| 309 | ) !*Defer { | ||
| 310 | const self = try comp.gpa().create(Defer); | ||
| 311 | self.* = Defer{ | ||
| 312 | .base = undefined, | ||
| 313 | .defer_expr_scope = defer_expr_scope, | ||
| 314 | .kind = kind, | ||
| 315 | }; | ||
| 316 | self.base.init(.Defer, parent); | ||
| 317 | defer_expr_scope.base.ref(); | ||
| 318 | return self; | ||
| 319 | } | ||
| 320 | |||
| 321 | pub fn destroy(self: *Defer, comp: *Compilation) void { | ||
| 322 | self.defer_expr_scope.base.deref(comp); | ||
| 323 | comp.gpa().destroy(self); | ||
| 324 | } | ||
| 325 | }; | ||
| 326 | |||
| 327 | pub const DeferExpr = struct { | ||
| 328 | base: Scope, | ||
| 329 | expr_node: *ast.Node, | ||
| 330 | reported_err: bool, | ||
| 331 | |||
| 332 | /// Creates a DeferExpr scope with 1 reference | ||
| 333 | pub fn create(comp: *Compilation, parent: *Scope, expr_node: *ast.Node) !*DeferExpr { | ||
| 334 | const self = try comp.gpa().create(DeferExpr); | ||
| 335 | self.* = DeferExpr{ | ||
| 336 | .base = undefined, | ||
| 337 | .expr_node = expr_node, | ||
| 338 | .reported_err = false, | ||
| 339 | }; | ||
| 340 | self.base.init(.DeferExpr, parent); | ||
| 341 | return self; | ||
| 342 | } | ||
| 343 | |||
| 344 | pub fn destroy(self: *DeferExpr, comp: *Compilation) void { | ||
| 345 | comp.gpa().destroy(self); | ||
| 346 | } | ||
| 347 | }; | ||
| 348 | |||
| 349 | pub const Var = struct { | ||
| 350 | base: Scope, | ||
| 351 | name: []const u8, | ||
| 352 | src_node: *ast.Node, | ||
| 353 | data: Data, | ||
| 354 | |||
| 355 | pub const Data = union(enum) { | ||
| 356 | Param: Param, | ||
| 357 | Const: *Value, | ||
| 358 | }; | ||
| 359 | |||
| 360 | pub const Param = struct { | ||
| 361 | index: usize, | ||
| 362 | typ: *Type, | ||
| 363 | llvm_value: *llvm.Value, | ||
| 364 | }; | ||
| 365 | |||
| 366 | pub fn createParam( | ||
| 367 | comp: *Compilation, | ||
| 368 | parent: *Scope, | ||
| 369 | name: []const u8, | ||
| 370 | src_node: *ast.Node, | ||
| 371 | param_index: usize, | ||
| 372 | param_type: *Type, | ||
| 373 | ) !*Var { | ||
| 374 | const self = try create(comp, parent, name, src_node); | ||
| 375 | self.data = Data{ | ||
| 376 | .Param = Param{ | ||
| 377 | .index = param_index, | ||
| 378 | .typ = param_type, | ||
| 379 | .llvm_value = undefined, | ||
| 380 | }, | ||
| 381 | }; | ||
| 382 | return self; | ||
| 383 | } | ||
| 384 | |||
| 385 | pub fn createConst( | ||
| 386 | comp: *Compilation, | ||
| 387 | parent: *Scope, | ||
| 388 | name: []const u8, | ||
| 389 | src_node: *ast.Node, | ||
| 390 | value: *Value, | ||
| 391 | ) !*Var { | ||
| 392 | const self = try create(comp, parent, name, src_node); | ||
| 393 | self.data = Data{ .Const = value }; | ||
| 394 | value.ref(); | ||
| 395 | return self; | ||
| 396 | } | ||
| 397 | |||
| 398 | fn create(comp: *Compilation, parent: *Scope, name: []const u8, src_node: *ast.Node) !*Var { | ||
| 399 | const self = try comp.gpa().create(Var); | ||
| 400 | self.* = Var{ | ||
| 401 | .base = undefined, | ||
| 402 | .name = name, | ||
| 403 | .src_node = src_node, | ||
| 404 | .data = undefined, | ||
| 405 | }; | ||
| 406 | self.base.init(.Var, parent); | ||
| 407 | return self; | ||
| 408 | } | ||
| 409 | |||
| 410 | pub fn destroy(self: *Var, comp: *Compilation) void { | ||
| 411 | switch (self.data) { | ||
| 412 | .Param => {}, | ||
| 413 | .Const => |value| value.deref(comp), | ||
| 414 | } | ||
| 415 | comp.gpa().destroy(self); | ||
| 416 | } | ||
| 417 | }; | ||
| 418 | }; | ||
src-self-hosted/stage2.zig+1-253| ... | @@ -12,7 +12,6 @@ const ArrayListSentineled = std.ArrayListSentineled; | ... | @@ -12,7 +12,6 @@ const ArrayListSentineled = std.ArrayListSentineled; |
| 12 | const Target = std.Target; | 12 | const Target = std.Target; |
| 13 | const CrossTarget = std.zig.CrossTarget; | 13 | const CrossTarget = std.zig.CrossTarget; |
| 14 | const self_hosted_main = @import("main.zig"); | 14 | const self_hosted_main = @import("main.zig"); |
| 15 | const errmsg = @import("errmsg.zig"); | ||
| 16 | const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer; | 15 | const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer; |
| 17 | const assert = std.debug.assert; | 16 | const assert = std.debug.assert; |
| 18 | const LibCInstallation = @import("libc_installation.zig").LibCInstallation; | 17 | const LibCInstallation = @import("libc_installation.zig").LibCInstallation; |
| ... | @@ -168,8 +167,6 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error { | ... | @@ -168,8 +167,6 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error { |
| 168 | return .None; | 167 | return .None; |
| 169 | } | 168 | } |
| 170 | 169 | ||
| 171 | // TODO: just use the actual self-hosted zig fmt. Until https://github.com/ziglang/zig/issues/2377, | ||
| 172 | // we use a blocking implementation. | ||
| 173 | export fn stage2_fmt(argc: c_int, argv: [*]const [*:0]const u8) c_int { | 170 | export fn stage2_fmt(argc: c_int, argv: [*]const [*:0]const u8) c_int { |
| 174 | if (std.debug.runtime_safety) { | 171 | if (std.debug.runtime_safety) { |
| 175 | fmtMain(argc, argv) catch unreachable; | 172 | fmtMain(argc, argv) catch unreachable; |
| ... | @@ -191,258 +188,9 @@ fn fmtMain(argc: c_int, argv: [*]const [*:0]const u8) !void { | ... | @@ -191,258 +188,9 @@ fn fmtMain(argc: c_int, argv: [*]const [*:0]const u8) !void { |
| 191 | try args_list.append(mem.spanZ(argv[arg_i])); | 188 | try args_list.append(mem.spanZ(argv[arg_i])); |
| 192 | } | 189 | } |
| 193 | 190 | ||
| 194 | stdout = std.io.getStdOut().outStream(); | ||
| 195 | stderr_file = std.io.getStdErr(); | ||
| 196 | stderr = stderr_file.outStream(); | ||
| 197 | |||
| 198 | const args = args_list.span()[2..]; | 191 | const args = args_list.span()[2..]; |
| 199 | 192 | ||
| 200 | var color: errmsg.Color = .Auto; | 193 | return self_hosted_main.cmdFmt(allocator, args); |
| 201 | var stdin_flag: bool = false; | ||
| 202 | var check_flag: bool = false; | ||
| 203 | var input_files = ArrayList([]const u8).init(allocator); | ||
| 204 | |||
| 205 | { | ||
| 206 | var i: usize = 0; | ||
| 207 | while (i < args.len) : (i += 1) { | ||
| 208 | const arg = args[i]; | ||
| 209 | if (mem.startsWith(u8, arg, "-")) { | ||
| 210 | if (mem.eql(u8, arg, "--help")) { | ||
| 211 | try stdout.writeAll(self_hosted_main.usage_fmt); | ||
| 212 | process.exit(0); | ||
| 213 | } else if (mem.eql(u8, arg, "--color")) { | ||
| 214 | if (i + 1 >= args.len) { | ||
| 215 | try stderr.writeAll("expected [auto|on|off] after --color\n"); | ||
| 216 | process.exit(1); | ||
| 217 | } | ||
| 218 | i += 1; | ||
| 219 | const next_arg = args[i]; | ||
| 220 | if (mem.eql(u8, next_arg, "auto")) { | ||
| 221 | color = .Auto; | ||
| 222 | } else if (mem.eql(u8, next_arg, "on")) { | ||
| 223 | color = .On; | ||
| 224 | } else if (mem.eql(u8, next_arg, "off")) { | ||
| 225 | color = .Off; | ||
| 226 | } else { | ||
| 227 | try stderr.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); | ||
| 228 | process.exit(1); | ||
| 229 | } | ||
| 230 | } else if (mem.eql(u8, arg, "--stdin")) { | ||
| 231 | stdin_flag = true; | ||
| 232 | } else if (mem.eql(u8, arg, "--check")) { | ||
| 233 | check_flag = true; | ||
| 234 | } else { | ||
| 235 | try stderr.print("unrecognized parameter: '{}'", .{arg}); | ||
| 236 | process.exit(1); | ||
| 237 | } | ||
| 238 | } else { | ||
| 239 | try input_files.append(arg); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | } | ||
| 243 | |||
| 244 | if (stdin_flag) { | ||
| 245 | if (input_files.items.len != 0) { | ||
| 246 | try stderr.writeAll("cannot use --stdin with positional arguments\n"); | ||
| 247 | process.exit(1); | ||
| 248 | } | ||
| 249 | |||
| 250 | const stdin_file = io.getStdIn(); | ||
| 251 | var stdin = stdin_file.inStream(); | ||
| 252 | |||
| 253 | const source_code = try stdin.readAllAlloc(allocator, self_hosted_main.max_src_size); | ||
| 254 | defer allocator.free(source_code); | ||
| 255 | |||
| 256 | const tree = std.zig.parse(allocator, source_code) catch |err| { | ||
| 257 | try stderr.print("error parsing stdin: {}\n", .{err}); | ||
| 258 | process.exit(1); | ||
| 259 | }; | ||
| 260 | defer tree.deinit(); | ||
| 261 | |||
| 262 | var error_it = tree.errors.iterator(0); | ||
| 263 | while (error_it.next()) |parse_error| { | ||
| 264 | try printErrMsgToFile(allocator, parse_error, tree, "<stdin>", stderr_file, color); | ||
| 265 | } | ||
| 266 | if (tree.errors.len != 0) { | ||
| 267 | process.exit(1); | ||
| 268 | } | ||
| 269 | if (check_flag) { | ||
| 270 | const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree); | ||
| 271 | const code = if (anything_changed) @as(u8, 1) else @as(u8, 0); | ||
| 272 | process.exit(code); | ||
| 273 | } | ||
| 274 | |||
| 275 | _ = try std.zig.render(allocator, stdout, tree); | ||
| 276 | return; | ||
| 277 | } | ||
| 278 | |||
| 279 | if (input_files.items.len == 0) { | ||
| 280 | try stderr.writeAll("expected at least one source file argument\n"); | ||
| 281 | process.exit(1); | ||
| 282 | } | ||
| 283 | |||
| 284 | var fmt = Fmt{ | ||
| 285 | .seen = Fmt.SeenMap.init(allocator), | ||
| 286 | .any_error = false, | ||
| 287 | .color = color, | ||
| 288 | .allocator = allocator, | ||
| 289 | }; | ||
| 290 | |||
| 291 | for (input_files.span()) |file_path| { | ||
| 292 | try fmtPath(&fmt, file_path, check_flag); | ||
| 293 | } | ||
| 294 | if (fmt.any_error) { | ||
| 295 | process.exit(1); | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | const FmtError = error{ | ||
| 300 | SystemResources, | ||
| 301 | OperationAborted, | ||
| 302 | IoPending, | ||
| 303 | BrokenPipe, | ||
| 304 | Unexpected, | ||
| 305 | WouldBlock, | ||
| 306 | FileClosed, | ||
| 307 | DestinationAddressRequired, | ||
| 308 | DiskQuota, | ||
| 309 | FileTooBig, | ||
| 310 | InputOutput, | ||
| 311 | NoSpaceLeft, | ||
| 312 | AccessDenied, | ||
| 313 | OutOfMemory, | ||
| 314 | RenameAcrossMountPoints, | ||
| 315 | ReadOnlyFileSystem, | ||
| 316 | LinkQuotaExceeded, | ||
| 317 | FileBusy, | ||
| 318 | } || fs.File.OpenError; | ||
| 319 | |||
| 320 | fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { | ||
| 321 | // get the real path here to avoid Windows failing on relative file paths with . or .. in them | ||
| 322 | var real_path = fs.realpathAlloc(fmt.allocator, file_path) catch |err| { | ||
| 323 | try stderr.print("unable to open '{}': {}\n", .{ file_path, err }); | ||
| 324 | fmt.any_error = true; | ||
| 325 | return; | ||
| 326 | }; | ||
| 327 | defer fmt.allocator.free(real_path); | ||
| 328 | |||
| 329 | if (fmt.seen.exists(real_path)) return; | ||
| 330 | try fmt.seen.put(real_path); | ||
| 331 | |||
| 332 | const source_code = fs.cwd().readFileAlloc(fmt.allocator, real_path, self_hosted_main.max_src_size) catch |err| switch (err) { | ||
| 333 | error.IsDir, error.AccessDenied => { | ||
| 334 | // TODO make event based (and dir.next()) | ||
| 335 | var dir = try fs.cwd().openDir(file_path, .{ .iterate = true }); | ||
| 336 | defer dir.close(); | ||
| 337 | |||
| 338 | var dir_it = dir.iterate(); | ||
| 339 | |||
| 340 | while (try dir_it.next()) |entry| { | ||
| 341 | if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) { | ||
| 342 | const full_path = try fs.path.join(fmt.allocator, &[_][]const u8{ file_path, entry.name }); | ||
| 343 | try fmtPath(fmt, full_path, check_mode); | ||
| 344 | } | ||
| 345 | } | ||
| 346 | return; | ||
| 347 | }, | ||
| 348 | else => { | ||
| 349 | // TODO lock stderr printing | ||
| 350 | try stderr.print("unable to open '{}': {}\n", .{ file_path, err }); | ||
| 351 | fmt.any_error = true; | ||
| 352 | return; | ||
| 353 | }, | ||
| 354 | }; | ||
| 355 | defer fmt.allocator.free(source_code); | ||
| 356 | |||
| 357 | const tree = std.zig.parse(fmt.allocator, source_code) catch |err| { | ||
| 358 | try stderr.print("error parsing file '{}': {}\n", .{ file_path, err }); | ||
| 359 | fmt.any_error = true; | ||
| 360 | return; | ||
| 361 | }; | ||
| 362 | defer tree.deinit(); | ||
| 363 | |||
| 364 | var error_it = tree.errors.iterator(0); | ||
| 365 | while (error_it.next()) |parse_error| { | ||
| 366 | try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color); | ||
| 367 | } | ||
| 368 | if (tree.errors.len != 0) { | ||
| 369 | fmt.any_error = true; | ||
| 370 | return; | ||
| 371 | } | ||
| 372 | |||
| 373 | if (check_mode) { | ||
| 374 | const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree); | ||
| 375 | if (anything_changed) { | ||
| 376 | try stderr.print("{}\n", .{file_path}); | ||
| 377 | fmt.any_error = true; | ||
| 378 | } | ||
| 379 | } else { | ||
| 380 | const baf = try io.BufferedAtomicFile.create(fmt.allocator, fs.cwd(), real_path, .{}); | ||
| 381 | defer baf.destroy(); | ||
| 382 | |||
| 383 | const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree); | ||
| 384 | if (anything_changed) { | ||
| 385 | try stderr.print("{}\n", .{file_path}); | ||
| 386 | try baf.finish(); | ||
| 387 | } | ||
| 388 | } | ||
| 389 | } | ||
| 390 | |||
| 391 | const Fmt = struct { | ||
| 392 | seen: SeenMap, | ||
| 393 | any_error: bool, | ||
| 394 | color: errmsg.Color, | ||
| 395 | allocator: *mem.Allocator, | ||
| 396 | |||
| 397 | const SeenMap = std.BufSet; | ||
| 398 | }; | ||
| 399 | |||
| 400 | fn printErrMsgToFile( | ||
| 401 | allocator: *mem.Allocator, | ||
| 402 | parse_error: *const ast.Error, | ||
| 403 | tree: *ast.Tree, | ||
| 404 | path: []const u8, | ||
| 405 | file: fs.File, | ||
| 406 | color: errmsg.Color, | ||
| 407 | ) !void { | ||
| 408 | const color_on = switch (color) { | ||
| 409 | .Auto => file.isTty(), | ||
| 410 | .On => true, | ||
| 411 | .Off => false, | ||
| 412 | }; | ||
| 413 | const lok_token = parse_error.loc(); | ||
| 414 | const span = errmsg.Span{ | ||
| 415 | .first = lok_token, | ||
| 416 | .last = lok_token, | ||
| 417 | }; | ||
| 418 | |||
| 419 | const first_token = tree.tokens.at(span.first); | ||
| 420 | const last_token = tree.tokens.at(span.last); | ||
| 421 | const start_loc = tree.tokenLocationPtr(0, first_token); | ||
| 422 | const end_loc = tree.tokenLocationPtr(first_token.end, last_token); | ||
| 423 | |||
| 424 | var text_buf = std.ArrayList(u8).init(allocator); | ||
| 425 | defer text_buf.deinit(); | ||
| 426 | const out_stream = text_buf.outStream(); | ||
| 427 | try parse_error.render(&tree.tokens, out_stream); | ||
| 428 | const text = text_buf.span(); | ||
| 429 | |||
| 430 | const stream = file.outStream(); | ||
| 431 | try stream.print("{}:{}:{}: error: {}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text }); | ||
| 432 | |||
| 433 | if (!color_on) return; | ||
| 434 | |||
| 435 | // Print \r and \t as one space each so that column counts line up | ||
| 436 | for (tree.source[start_loc.line_start..start_loc.line_end]) |byte| { | ||
| 437 | try stream.writeByte(switch (byte) { | ||
| 438 | '\r', '\t' => ' ', | ||
| 439 | else => byte, | ||
| 440 | }); | ||
| 441 | } | ||
| 442 | try stream.writeByte('\n'); | ||
| 443 | try stream.writeByteNTimes(' ', start_loc.column); | ||
| 444 | try stream.writeByteNTimes('~', last_token.end - first_token.start); | ||
| 445 | try stream.writeByte('\n'); | ||
| 446 | } | 194 | } |
| 447 | 195 | ||
| 448 | export fn stage2_DepTokenizer_init(input: [*]const u8, len: usize) stage2_DepTokenizer { | 196 | export fn stage2_DepTokenizer_init(input: [*]const u8, len: usize) stage2_DepTokenizer { |
src-self-hosted/test.zig+97-102| ... | @@ -1,17 +1,18 @@ | ... | @@ -1,17 +1,18 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const link = @import("link.zig"); | 2 | const link = @import("link.zig"); |
| 3 | const ir = @import("ir.zig"); | 3 | const Module = @import("Module.zig"); |
| 4 | const Allocator = std.mem.Allocator; | 4 | const Allocator = std.mem.Allocator; |
| 5 | 5 | const zir = @import("zir.zig"); | |
| 6 | var global_ctx: TestContext = undefined; | 6 | const Package = @import("Package.zig"); |
| 7 | 7 | ||
| 8 | test "self-hosted" { | 8 | test "self-hosted" { |
| 9 | try global_ctx.init(); | 9 | var ctx: TestContext = undefined; |
| 10 | defer global_ctx.deinit(); | 10 | try ctx.init(); |
| 11 | defer ctx.deinit(); | ||
| 11 | 12 | ||
| 12 | try @import("stage2_tests").addCases(&global_ctx); | 13 | try @import("stage2_tests").addCases(&ctx); |
| 13 | 14 | ||
| 14 | try global_ctx.run(); | 15 | try ctx.run(); |
| 15 | } | 16 | } |
| 16 | 17 | ||
| 17 | pub const TestContext = struct { | 18 | pub const TestContext = struct { |
| ... | @@ -20,32 +21,34 @@ pub const TestContext = struct { | ... | @@ -20,32 +21,34 @@ pub const TestContext = struct { |
| 20 | 21 | ||
| 21 | pub const ZIRCompareOutputCase = struct { | 22 | pub const ZIRCompareOutputCase = struct { |
| 22 | name: []const u8, | 23 | name: []const u8, |
| 23 | src: [:0]const u8, | 24 | src_list: []const []const u8, |
| 24 | expected_stdout: []const u8, | 25 | expected_stdout_list: []const []const u8, |
| 25 | }; | 26 | }; |
| 26 | 27 | ||
| 27 | pub const ZIRTransformCase = struct { | 28 | pub const ZIRTransformCase = struct { |
| 28 | name: []const u8, | 29 | name: []const u8, |
| 29 | src: [:0]const u8, | 30 | src: [:0]const u8, |
| 30 | expected_zir: []const u8, | 31 | expected_zir: []const u8, |
| 32 | cross_target: std.zig.CrossTarget, | ||
| 31 | }; | 33 | }; |
| 32 | 34 | ||
| 33 | pub fn addZIRCompareOutput( | 35 | pub fn addZIRCompareOutput( |
| 34 | ctx: *TestContext, | 36 | ctx: *TestContext, |
| 35 | name: []const u8, | 37 | name: []const u8, |
| 36 | src: [:0]const u8, | 38 | src_list: []const []const u8, |
| 37 | expected_stdout: []const u8, | 39 | expected_stdout_list: []const []const u8, |
| 38 | ) void { | 40 | ) void { |
| 39 | ctx.zir_cmp_output_cases.append(.{ | 41 | ctx.zir_cmp_output_cases.append(.{ |
| 40 | .name = name, | 42 | .name = name, |
| 41 | .src = src, | 43 | .src_list = src_list, |
| 42 | .expected_stdout = expected_stdout, | 44 | .expected_stdout_list = expected_stdout_list, |
| 43 | }) catch unreachable; | 45 | }) catch unreachable; |
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | pub fn addZIRTransform( | 48 | pub fn addZIRTransform( |
| 47 | ctx: *TestContext, | 49 | ctx: *TestContext, |
| 48 | name: []const u8, | 50 | name: []const u8, |
| 51 | cross_target: std.zig.CrossTarget, | ||
| 49 | src: [:0]const u8, | 52 | src: [:0]const u8, |
| 50 | expected_zir: []const u8, | 53 | expected_zir: []const u8, |
| 51 | ) void { | 54 | ) void { |
| ... | @@ -53,6 +56,7 @@ pub const TestContext = struct { | ... | @@ -53,6 +56,7 @@ pub const TestContext = struct { |
| 53 | .name = name, | 56 | .name = name, |
| 54 | .src = src, | 57 | .src = src, |
| 55 | .expected_zir = expected_zir, | 58 | .expected_zir = expected_zir, |
| 59 | .cross_target = cross_target, | ||
| 56 | }) catch unreachable; | 60 | }) catch unreachable; |
| 57 | } | 61 | } |
| 58 | 62 | ||
| ... | @@ -84,7 +88,8 @@ pub const TestContext = struct { | ... | @@ -84,7 +88,8 @@ pub const TestContext = struct { |
| 84 | } | 88 | } |
| 85 | for (self.zir_transform_cases.items) |case| { | 89 | for (self.zir_transform_cases.items) |case| { |
| 86 | std.testing.base_allocator_instance.reset(); | 90 | std.testing.base_allocator_instance.reset(); |
| 87 | try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, native_info.target); | 91 | const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target); |
| 92 | try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, info.target); | ||
| 88 | try std.testing.allocator_instance.validate(); | 93 | try std.testing.allocator_instance.validate(); |
| 89 | } | 94 | } |
| 90 | } | 95 | } |
| ... | @@ -99,77 +104,68 @@ pub const TestContext = struct { | ... | @@ -99,77 +104,68 @@ pub const TestContext = struct { |
| 99 | var tmp = std.testing.tmpDir(.{}); | 104 | var tmp = std.testing.tmpDir(.{}); |
| 100 | defer tmp.cleanup(); | 105 | defer tmp.cleanup(); |
| 101 | 106 | ||
| 102 | var prg_node = root_node.start(case.name, 4); | 107 | const tmp_src_path = "test-case.zir"; |
| 108 | const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); | ||
| 109 | defer root_pkg.destroy(); | ||
| 110 | |||
| 111 | var prg_node = root_node.start(case.name, case.src_list.len); | ||
| 103 | prg_node.activate(); | 112 | prg_node.activate(); |
| 104 | defer prg_node.end(); | 113 | defer prg_node.end(); |
| 105 | 114 | ||
| 106 | var zir_module = x: { | 115 | var module = try Module.init(allocator, .{ |
| 107 | var parse_node = prg_node.start("parse", null); | 116 | .target = target, |
| 108 | parse_node.activate(); | 117 | .output_mode = .Exe, |
| 109 | defer parse_node.end(); | 118 | .optimize_mode = .Debug, |
| 110 | 119 | .bin_file_dir = tmp.dir, | |
| 111 | break :x try ir.text.parse(allocator, case.src); | 120 | .bin_file_path = "a.out", |
| 112 | }; | 121 | .root_pkg = root_pkg, |
| 113 | defer zir_module.deinit(allocator); | 122 | }); |
| 114 | if (zir_module.errors.len != 0) { | 123 | defer module.deinit(); |
| 115 | debugPrintErrors(case.src, zir_module.errors); | 124 | |
| 116 | return error.ParseFailure; | 125 | for (case.src_list) |source, i| { |
| 117 | } | 126 | var src_node = prg_node.start("update", 2); |
| 118 | 127 | src_node.activate(); | |
| 119 | var analyzed_module = x: { | 128 | defer src_node.end(); |
| 120 | var analyze_node = prg_node.start("analyze", null); | 129 | |
| 121 | analyze_node.activate(); | 130 | try tmp.dir.writeFile(tmp_src_path, source); |
| 122 | defer analyze_node.end(); | 131 | |
| 123 | 132 | var update_node = src_node.start("parse,analysis,codegen", null); | |
| 124 | break :x try ir.analyze(allocator, zir_module, .{ | 133 | update_node.activate(); |
| 125 | .target = target, | 134 | try module.makeBinFileWritable(); |
| 126 | .output_mode = .Exe, | 135 | try module.update(); |
| 127 | .link_mode = .Static, | 136 | update_node.end(); |
| 128 | .optimize_mode = .Debug, | 137 | |
| 129 | }); | 138 | var exec_result = x: { |
| 130 | }; | 139 | var exec_node = src_node.start("execute", null); |
| 131 | defer analyzed_module.deinit(allocator); | 140 | exec_node.activate(); |
| 132 | if (analyzed_module.errors.len != 0) { | 141 | defer exec_node.end(); |
| 133 | debugPrintErrors(case.src, analyzed_module.errors); | 142 | |
| 134 | return error.ParseFailure; | 143 | try module.makeBinFileExecutable(); |
| 135 | } | 144 | break :x try std.ChildProcess.exec(.{ |
| 136 | 145 | .allocator = allocator, | |
| 137 | var link_result = x: { | 146 | .argv = &[_][]const u8{"./a.out"}, |
| 138 | var link_node = prg_node.start("link", null); | 147 | .cwd_dir = tmp.dir, |
| 139 | link_node.activate(); | 148 | }); |
| 140 | defer link_node.end(); | 149 | }; |
| 141 | 150 | defer allocator.free(exec_result.stdout); | |
| 142 | break :x try link.updateFilePath(allocator, analyzed_module, tmp.dir, "a.out"); | 151 | defer allocator.free(exec_result.stderr); |
| 143 | }; | 152 | switch (exec_result.term) { |
| 144 | defer link_result.deinit(allocator); | 153 | .Exited => |code| { |
| 145 | if (link_result.errors.len != 0) { | 154 | if (code != 0) { |
| 146 | debugPrintErrors(case.src, link_result.errors); | 155 | std.debug.warn("elf file exited with code {}\n", .{code}); |
| 147 | return error.LinkFailure; | 156 | return error.BinaryBadExitCode; |
| 148 | } | 157 | } |
| 149 | 158 | }, | |
| 150 | var exec_result = x: { | 159 | else => return error.BinaryCrashed, |
| 151 | var exec_node = prg_node.start("execute", null); | 160 | } |
| 152 | exec_node.activate(); | 161 | const expected_stdout = case.expected_stdout_list[i]; |
| 153 | defer exec_node.end(); | 162 | if (!std.mem.eql(u8, expected_stdout, exec_result.stdout)) { |
| 154 | 163 | std.debug.panic( | |
| 155 | break :x try std.ChildProcess.exec(.{ | 164 | "update index {}, mismatched stdout\n====Expected (len={}):====\n{}\n====Actual (len={}):====\n{}\n========\n", |
| 156 | .allocator = allocator, | 165 | .{ i, expected_stdout.len, expected_stdout, exec_result.stdout.len, exec_result.stdout }, |
| 157 | .argv = &[_][]const u8{"./a.out"}, | 166 | ); |
| 158 | .cwd_dir = tmp.dir, | 167 | } |
| 159 | }); | ||
| 160 | }; | ||
| 161 | defer allocator.free(exec_result.stdout); | ||
| 162 | defer allocator.free(exec_result.stderr); | ||
| 163 | switch (exec_result.term) { | ||
| 164 | .Exited => |code| { | ||
| 165 | if (code != 0) { | ||
| 166 | std.debug.warn("elf file exited with code {}\n", .{code}); | ||
| 167 | return error.BinaryBadExitCode; | ||
| 168 | } | ||
| 169 | }, | ||
| 170 | else => return error.BinaryCrashed, | ||
| 171 | } | 168 | } |
| 172 | std.testing.expectEqualSlices(u8, case.expected_stdout, exec_result.stdout); | ||
| 173 | } | 169 | } |
| 174 | 170 | ||
| 175 | fn runOneZIRTransformCase( | 171 | fn runOneZIRTransformCase( |
| ... | @@ -179,38 +175,37 @@ pub const TestContext = struct { | ... | @@ -179,38 +175,37 @@ pub const TestContext = struct { |
| 179 | case: ZIRTransformCase, | 175 | case: ZIRTransformCase, |
| 180 | target: std.Target, | 176 | target: std.Target, |
| 181 | ) !void { | 177 | ) !void { |
| 182 | var prg_node = root_node.start(case.name, 4); | 178 | var tmp = std.testing.tmpDir(.{}); |
| 179 | defer tmp.cleanup(); | ||
| 180 | |||
| 181 | var prg_node = root_node.start(case.name, 3); | ||
| 183 | prg_node.activate(); | 182 | prg_node.activate(); |
| 184 | defer prg_node.end(); | 183 | defer prg_node.end(); |
| 185 | 184 | ||
| 186 | var parse_node = prg_node.start("parse", null); | 185 | const tmp_src_path = "test-case.zir"; |
| 187 | parse_node.activate(); | 186 | try tmp.dir.writeFile(tmp_src_path, case.src); |
| 188 | var zir_module = try ir.text.parse(allocator, case.src); | ||
| 189 | defer zir_module.deinit(allocator); | ||
| 190 | if (zir_module.errors.len != 0) { | ||
| 191 | debugPrintErrors(case.src, zir_module.errors); | ||
| 192 | return error.ParseFailure; | ||
| 193 | } | ||
| 194 | parse_node.end(); | ||
| 195 | 187 | ||
| 196 | var analyze_node = prg_node.start("analyze", null); | 188 | const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); |
| 197 | analyze_node.activate(); | 189 | defer root_pkg.destroy(); |
| 198 | var analyzed_module = try ir.analyze(allocator, zir_module, .{ | 190 | |
| 191 | var module = try Module.init(allocator, .{ | ||
| 199 | .target = target, | 192 | .target = target, |
| 200 | .output_mode = .Obj, | 193 | .output_mode = .Obj, |
| 201 | .link_mode = .Static, | ||
| 202 | .optimize_mode = .Debug, | 194 | .optimize_mode = .Debug, |
| 195 | .bin_file_dir = tmp.dir, | ||
| 196 | .bin_file_path = "test-case.o", | ||
| 197 | .root_pkg = root_pkg, | ||
| 203 | }); | 198 | }); |
| 204 | defer analyzed_module.deinit(allocator); | 199 | defer module.deinit(); |
| 205 | if (analyzed_module.errors.len != 0) { | 200 | |
| 206 | debugPrintErrors(case.src, analyzed_module.errors); | 201 | var module_node = prg_node.start("parse/analysis/codegen", null); |
| 207 | return error.ParseFailure; | 202 | module_node.activate(); |
| 208 | } | 203 | try module.update(); |
| 209 | analyze_node.end(); | 204 | module_node.end(); |
| 210 | 205 | ||
| 211 | var emit_node = prg_node.start("emit", null); | 206 | var emit_node = prg_node.start("emit", null); |
| 212 | emit_node.activate(); | 207 | emit_node.activate(); |
| 213 | var new_zir_module = try ir.text.emit_zir(allocator, analyzed_module); | 208 | var new_zir_module = try zir.emit(allocator, module); |
| 214 | defer new_zir_module.deinit(allocator); | 209 | defer new_zir_module.deinit(allocator); |
| 215 | emit_node.end(); | 210 | emit_node.end(); |
| 216 | 211 |
src-self-hosted/type.zig+231-3| ... | @@ -5,8 +5,7 @@ const Allocator = std.mem.Allocator; | ... | @@ -5,8 +5,7 @@ const Allocator = std.mem.Allocator; |
| 5 | const Target = std.Target; | 5 | const Target = std.Target; |
| 6 | 6 | ||
| 7 | /// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication. | 7 | /// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication. |
| 8 | /// It's important for this struct to be small. | 8 | /// It's important for this type to be small. |
| 9 | /// It is not copyable since it may contain references to its inner data. | ||
| 10 | /// Types are not de-duplicated, which helps with multi-threading since it obviates the requirement | 9 | /// Types are not de-duplicated, which helps with multi-threading since it obviates the requirement |
| 11 | /// of obtaining a lock on a global type table, as well as making the | 10 | /// of obtaining a lock on a global type table, as well as making the |
| 12 | /// garbage collection bookkeeping simpler. | 11 | /// garbage collection bookkeeping simpler. |
| ... | @@ -51,7 +50,9 @@ pub const Type = extern union { | ... | @@ -51,7 +50,9 @@ pub const Type = extern union { |
| 51 | .comptime_int => return .ComptimeInt, | 50 | .comptime_int => return .ComptimeInt, |
| 52 | .comptime_float => return .ComptimeFloat, | 51 | .comptime_float => return .ComptimeFloat, |
| 53 | .noreturn => return .NoReturn, | 52 | .noreturn => return .NoReturn, |
| 53 | .@"null" => return .Null, | ||
| 54 | 54 | ||
| 55 | .fn_noreturn_no_args => return .Fn, | ||
| 55 | .fn_naked_noreturn_no_args => return .Fn, | 56 | .fn_naked_noreturn_no_args => return .Fn, |
| 56 | .fn_ccc_void_no_args => return .Fn, | 57 | .fn_ccc_void_no_args => return .Fn, |
| 57 | 58 | ||
| ... | @@ -183,7 +184,10 @@ pub const Type = extern union { | ... | @@ -183,7 +184,10 @@ pub const Type = extern union { |
| 183 | .noreturn, | 184 | .noreturn, |
| 184 | => return out_stream.writeAll(@tagName(t)), | 185 | => return out_stream.writeAll(@tagName(t)), |
| 185 | 186 | ||
| 187 | .@"null" => return out_stream.writeAll("@TypeOf(null)"), | ||
| 188 | |||
| 186 | .const_slice_u8 => return out_stream.writeAll("[]const u8"), | 189 | .const_slice_u8 => return out_stream.writeAll("[]const u8"), |
| 190 | .fn_noreturn_no_args => return out_stream.writeAll("fn() noreturn"), | ||
| 187 | .fn_naked_noreturn_no_args => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), | 191 | .fn_naked_noreturn_no_args => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), |
| 188 | .fn_ccc_void_no_args => return out_stream.writeAll("fn() callconv(.C) void"), | 192 | .fn_ccc_void_no_args => return out_stream.writeAll("fn() callconv(.C) void"), |
| 189 | .single_const_pointer_to_comptime_int => return out_stream.writeAll("*const comptime_int"), | 193 | .single_const_pointer_to_comptime_int => return out_stream.writeAll("*const comptime_int"), |
| ... | @@ -244,6 +248,8 @@ pub const Type = extern union { | ... | @@ -244,6 +248,8 @@ pub const Type = extern union { |
| 244 | .comptime_int => return Value.initTag(.comptime_int_type), | 248 | .comptime_int => return Value.initTag(.comptime_int_type), |
| 245 | .comptime_float => return Value.initTag(.comptime_float_type), | 249 | .comptime_float => return Value.initTag(.comptime_float_type), |
| 246 | .noreturn => return Value.initTag(.noreturn_type), | 250 | .noreturn => return Value.initTag(.noreturn_type), |
| 251 | .@"null" => return Value.initTag(.null_type), | ||
| 252 | .fn_noreturn_no_args => return Value.initTag(.fn_noreturn_no_args_type), | ||
| 247 | .fn_naked_noreturn_no_args => return Value.initTag(.fn_naked_noreturn_no_args_type), | 253 | .fn_naked_noreturn_no_args => return Value.initTag(.fn_naked_noreturn_no_args_type), |
| 248 | .fn_ccc_void_no_args => return Value.initTag(.fn_ccc_void_no_args_type), | 254 | .fn_ccc_void_no_args => return Value.initTag(.fn_ccc_void_no_args_type), |
| 249 | .single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type), | 255 | .single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type), |
| ... | @@ -256,6 +262,110 @@ pub const Type = extern union { | ... | @@ -256,6 +262,110 @@ pub const Type = extern union { |
| 256 | } | 262 | } |
| 257 | } | 263 | } |
| 258 | 264 | ||
| 265 | pub fn hasCodeGenBits(self: Type) bool { | ||
| 266 | return switch (self.tag()) { | ||
| 267 | .u8, | ||
| 268 | .i8, | ||
| 269 | .isize, | ||
| 270 | .usize, | ||
| 271 | .c_short, | ||
| 272 | .c_ushort, | ||
| 273 | .c_int, | ||
| 274 | .c_uint, | ||
| 275 | .c_long, | ||
| 276 | .c_ulong, | ||
| 277 | .c_longlong, | ||
| 278 | .c_ulonglong, | ||
| 279 | .c_longdouble, | ||
| 280 | .f16, | ||
| 281 | .f32, | ||
| 282 | .f64, | ||
| 283 | .f128, | ||
| 284 | .bool, | ||
| 285 | .anyerror, | ||
| 286 | .fn_noreturn_no_args, | ||
| 287 | .fn_naked_noreturn_no_args, | ||
| 288 | .fn_ccc_void_no_args, | ||
| 289 | .single_const_pointer_to_comptime_int, | ||
| 290 | .const_slice_u8, | ||
| 291 | .array_u8_sentinel_0, | ||
| 292 | .array, // TODO check for zero bits | ||
| 293 | .single_const_pointer, | ||
| 294 | .int_signed, // TODO check for zero bits | ||
| 295 | .int_unsigned, // TODO check for zero bits | ||
| 296 | => true, | ||
| 297 | |||
| 298 | .c_void, | ||
| 299 | .void, | ||
| 300 | .type, | ||
| 301 | .comptime_int, | ||
| 302 | .comptime_float, | ||
| 303 | .noreturn, | ||
| 304 | .@"null", | ||
| 305 | => false, | ||
| 306 | }; | ||
| 307 | } | ||
| 308 | |||
| 309 | /// Asserts that hasCodeGenBits() is true. | ||
| 310 | pub fn abiAlignment(self: Type, target: Target) u32 { | ||
| 311 | return switch (self.tag()) { | ||
| 312 | .u8, | ||
| 313 | .i8, | ||
| 314 | .bool, | ||
| 315 | .fn_noreturn_no_args, // represents machine code; not a pointer | ||
| 316 | .fn_naked_noreturn_no_args, // represents machine code; not a pointer | ||
| 317 | .fn_ccc_void_no_args, // represents machine code; not a pointer | ||
| 318 | .array_u8_sentinel_0, | ||
| 319 | => return 1, | ||
| 320 | |||
| 321 | .isize, | ||
| 322 | .usize, | ||
| 323 | .single_const_pointer_to_comptime_int, | ||
| 324 | .const_slice_u8, | ||
| 325 | .single_const_pointer, | ||
| 326 | => return @divExact(target.cpu.arch.ptrBitWidth(), 8), | ||
| 327 | |||
| 328 | .c_short => return @divExact(CType.short.sizeInBits(target), 8), | ||
| 329 | .c_ushort => return @divExact(CType.ushort.sizeInBits(target), 8), | ||
| 330 | .c_int => return @divExact(CType.int.sizeInBits(target), 8), | ||
| 331 | .c_uint => return @divExact(CType.uint.sizeInBits(target), 8), | ||
| 332 | .c_long => return @divExact(CType.long.sizeInBits(target), 8), | ||
| 333 | .c_ulong => return @divExact(CType.ulong.sizeInBits(target), 8), | ||
| 334 | .c_longlong => return @divExact(CType.longlong.sizeInBits(target), 8), | ||
| 335 | .c_ulonglong => return @divExact(CType.ulonglong.sizeInBits(target), 8), | ||
| 336 | |||
| 337 | .f16 => return 2, | ||
| 338 | .f32 => return 4, | ||
| 339 | .f64 => return 8, | ||
| 340 | .f128 => return 16, | ||
| 341 | .c_longdouble => return 16, | ||
| 342 | |||
| 343 | .anyerror => return 2, // TODO revisit this when we have the concept of the error tag type | ||
| 344 | |||
| 345 | .array => return self.cast(Payload.Array).?.elem_type.abiAlignment(target), | ||
| 346 | |||
| 347 | .int_signed, .int_unsigned => { | ||
| 348 | const bits: u16 = if (self.cast(Payload.IntSigned)) |pl| | ||
| 349 | pl.bits | ||
| 350 | else if (self.cast(Payload.IntUnsigned)) |pl| | ||
| 351 | pl.bits | ||
| 352 | else | ||
| 353 | unreachable; | ||
| 354 | |||
| 355 | return std.math.ceilPowerOfTwoPromote(u16, (bits + 7) / 8); | ||
| 356 | }, | ||
| 357 | |||
| 358 | .c_void, | ||
| 359 | .void, | ||
| 360 | .type, | ||
| 361 | .comptime_int, | ||
| 362 | .comptime_float, | ||
| 363 | .noreturn, | ||
| 364 | .@"null", | ||
| 365 | => unreachable, | ||
| 366 | }; | ||
| 367 | } | ||
| 368 | |||
| 259 | pub fn isSinglePointer(self: Type) bool { | 369 | pub fn isSinglePointer(self: Type) bool { |
| 260 | return switch (self.tag()) { | 370 | return switch (self.tag()) { |
| 261 | .u8, | 371 | .u8, |
| ... | @@ -283,9 +393,11 @@ pub const Type = extern union { | ... | @@ -283,9 +393,11 @@ pub const Type = extern union { |
| 283 | .comptime_int, | 393 | .comptime_int, |
| 284 | .comptime_float, | 394 | .comptime_float, |
| 285 | .noreturn, | 395 | .noreturn, |
| 396 | .@"null", | ||
| 286 | .array, | 397 | .array, |
| 287 | .array_u8_sentinel_0, | 398 | .array_u8_sentinel_0, |
| 288 | .const_slice_u8, | 399 | .const_slice_u8, |
| 400 | .fn_noreturn_no_args, | ||
| 289 | .fn_naked_noreturn_no_args, | 401 | .fn_naked_noreturn_no_args, |
| 290 | .fn_ccc_void_no_args, | 402 | .fn_ccc_void_no_args, |
| 291 | .int_unsigned, | 403 | .int_unsigned, |
| ... | @@ -325,10 +437,12 @@ pub const Type = extern union { | ... | @@ -325,10 +437,12 @@ pub const Type = extern union { |
| 325 | .comptime_int, | 437 | .comptime_int, |
| 326 | .comptime_float, | 438 | .comptime_float, |
| 327 | .noreturn, | 439 | .noreturn, |
| 440 | .@"null", | ||
| 328 | .array, | 441 | .array, |
| 329 | .array_u8_sentinel_0, | 442 | .array_u8_sentinel_0, |
| 330 | .single_const_pointer, | 443 | .single_const_pointer, |
| 331 | .single_const_pointer_to_comptime_int, | 444 | .single_const_pointer_to_comptime_int, |
| 445 | .fn_noreturn_no_args, | ||
| 332 | .fn_naked_noreturn_no_args, | 446 | .fn_naked_noreturn_no_args, |
| 333 | .fn_ccc_void_no_args, | 447 | .fn_ccc_void_no_args, |
| 334 | .int_unsigned, | 448 | .int_unsigned, |
| ... | @@ -367,8 +481,10 @@ pub const Type = extern union { | ... | @@ -367,8 +481,10 @@ pub const Type = extern union { |
| 367 | .comptime_int, | 481 | .comptime_int, |
| 368 | .comptime_float, | 482 | .comptime_float, |
| 369 | .noreturn, | 483 | .noreturn, |
| 484 | .@"null", | ||
| 370 | .array, | 485 | .array, |
| 371 | .array_u8_sentinel_0, | 486 | .array_u8_sentinel_0, |
| 487 | .fn_noreturn_no_args, | ||
| 372 | .fn_naked_noreturn_no_args, | 488 | .fn_naked_noreturn_no_args, |
| 373 | .fn_ccc_void_no_args, | 489 | .fn_ccc_void_no_args, |
| 374 | .int_unsigned, | 490 | .int_unsigned, |
| ... | @@ -410,6 +526,8 @@ pub const Type = extern union { | ... | @@ -410,6 +526,8 @@ pub const Type = extern union { |
| 410 | .comptime_int, | 526 | .comptime_int, |
| 411 | .comptime_float, | 527 | .comptime_float, |
| 412 | .noreturn, | 528 | .noreturn, |
| 529 | .@"null", | ||
| 530 | .fn_noreturn_no_args, | ||
| 413 | .fn_naked_noreturn_no_args, | 531 | .fn_naked_noreturn_no_args, |
| 414 | .fn_ccc_void_no_args, | 532 | .fn_ccc_void_no_args, |
| 415 | .int_unsigned, | 533 | .int_unsigned, |
| ... | @@ -451,6 +569,8 @@ pub const Type = extern union { | ... | @@ -451,6 +569,8 @@ pub const Type = extern union { |
| 451 | .comptime_int, | 569 | .comptime_int, |
| 452 | .comptime_float, | 570 | .comptime_float, |
| 453 | .noreturn, | 571 | .noreturn, |
| 572 | .@"null", | ||
| 573 | .fn_noreturn_no_args, | ||
| 454 | .fn_naked_noreturn_no_args, | 574 | .fn_naked_noreturn_no_args, |
| 455 | .fn_ccc_void_no_args, | 575 | .fn_ccc_void_no_args, |
| 456 | .single_const_pointer, | 576 | .single_const_pointer, |
| ... | @@ -465,6 +585,50 @@ pub const Type = extern union { | ... | @@ -465,6 +585,50 @@ pub const Type = extern union { |
| 465 | }; | 585 | }; |
| 466 | } | 586 | } |
| 467 | 587 | ||
| 588 | /// Asserts the type is an array or vector. | ||
| 589 | pub fn arraySentinel(self: Type) ?Value { | ||
| 590 | return switch (self.tag()) { | ||
| 591 | .u8, | ||
| 592 | .i8, | ||
| 593 | .isize, | ||
| 594 | .usize, | ||
| 595 | .c_short, | ||
| 596 | .c_ushort, | ||
| 597 | .c_int, | ||
| 598 | .c_uint, | ||
| 599 | .c_long, | ||
| 600 | .c_ulong, | ||
| 601 | .c_longlong, | ||
| 602 | .c_ulonglong, | ||
| 603 | .c_longdouble, | ||
| 604 | .f16, | ||
| 605 | .f32, | ||
| 606 | .f64, | ||
| 607 | .f128, | ||
| 608 | .c_void, | ||
| 609 | .bool, | ||
| 610 | .void, | ||
| 611 | .type, | ||
| 612 | .anyerror, | ||
| 613 | .comptime_int, | ||
| 614 | .comptime_float, | ||
| 615 | .noreturn, | ||
| 616 | .@"null", | ||
| 617 | .fn_noreturn_no_args, | ||
| 618 | .fn_naked_noreturn_no_args, | ||
| 619 | .fn_ccc_void_no_args, | ||
| 620 | .single_const_pointer, | ||
| 621 | .single_const_pointer_to_comptime_int, | ||
| 622 | .const_slice_u8, | ||
| 623 | .int_unsigned, | ||
| 624 | .int_signed, | ||
| 625 | => unreachable, | ||
| 626 | |||
| 627 | .array => return null, | ||
| 628 | .array_u8_sentinel_0 => return Value.initTag(.zero), | ||
| 629 | }; | ||
| 630 | } | ||
| 631 | |||
| 468 | /// Returns true if and only if the type is a fixed-width, signed integer. | 632 | /// Returns true if and only if the type is a fixed-width, signed integer. |
| 469 | pub fn isSignedInt(self: Type) bool { | 633 | pub fn isSignedInt(self: Type) bool { |
| 470 | return switch (self.tag()) { | 634 | return switch (self.tag()) { |
| ... | @@ -481,6 +645,8 @@ pub const Type = extern union { | ... | @@ -481,6 +645,8 @@ pub const Type = extern union { |
| 481 | .comptime_int, | 645 | .comptime_int, |
| 482 | .comptime_float, | 646 | .comptime_float, |
| 483 | .noreturn, | 647 | .noreturn, |
| 648 | .@"null", | ||
| 649 | .fn_noreturn_no_args, | ||
| 484 | .fn_naked_noreturn_no_args, | 650 | .fn_naked_noreturn_no_args, |
| 485 | .fn_ccc_void_no_args, | 651 | .fn_ccc_void_no_args, |
| 486 | .array, | 652 | .array, |
| ... | @@ -524,6 +690,8 @@ pub const Type = extern union { | ... | @@ -524,6 +690,8 @@ pub const Type = extern union { |
| 524 | .comptime_int, | 690 | .comptime_int, |
| 525 | .comptime_float, | 691 | .comptime_float, |
| 526 | .noreturn, | 692 | .noreturn, |
| 693 | .@"null", | ||
| 694 | .fn_noreturn_no_args, | ||
| 527 | .fn_naked_noreturn_no_args, | 695 | .fn_naked_noreturn_no_args, |
| 528 | .fn_ccc_void_no_args, | 696 | .fn_ccc_void_no_args, |
| 529 | .array, | 697 | .array, |
| ... | @@ -579,6 +747,7 @@ pub const Type = extern union { | ... | @@ -579,6 +747,7 @@ pub const Type = extern union { |
| 579 | /// Asserts the type is a function. | 747 | /// Asserts the type is a function. |
| 580 | pub fn fnParamLen(self: Type) usize { | 748 | pub fn fnParamLen(self: Type) usize { |
| 581 | return switch (self.tag()) { | 749 | return switch (self.tag()) { |
| 750 | .fn_noreturn_no_args => 0, | ||
| 582 | .fn_naked_noreturn_no_args => 0, | 751 | .fn_naked_noreturn_no_args => 0, |
| 583 | .fn_ccc_void_no_args => 0, | 752 | .fn_ccc_void_no_args => 0, |
| 584 | 753 | ||
| ... | @@ -595,6 +764,7 @@ pub const Type = extern union { | ... | @@ -595,6 +764,7 @@ pub const Type = extern union { |
| 595 | .comptime_int, | 764 | .comptime_int, |
| 596 | .comptime_float, | 765 | .comptime_float, |
| 597 | .noreturn, | 766 | .noreturn, |
| 767 | .@"null", | ||
| 598 | .array, | 768 | .array, |
| 599 | .single_const_pointer, | 769 | .single_const_pointer, |
| 600 | .single_const_pointer_to_comptime_int, | 770 | .single_const_pointer_to_comptime_int, |
| ... | @@ -622,6 +792,7 @@ pub const Type = extern union { | ... | @@ -622,6 +792,7 @@ pub const Type = extern union { |
| 622 | /// given by `fnParamLen`. | 792 | /// given by `fnParamLen`. |
| 623 | pub fn fnParamTypes(self: Type, types: []Type) void { | 793 | pub fn fnParamTypes(self: Type, types: []Type) void { |
| 624 | switch (self.tag()) { | 794 | switch (self.tag()) { |
| 795 | .fn_noreturn_no_args => return, | ||
| 625 | .fn_naked_noreturn_no_args => return, | 796 | .fn_naked_noreturn_no_args => return, |
| 626 | .fn_ccc_void_no_args => return, | 797 | .fn_ccc_void_no_args => return, |
| 627 | 798 | ||
| ... | @@ -638,6 +809,7 @@ pub const Type = extern union { | ... | @@ -638,6 +809,7 @@ pub const Type = extern union { |
| 638 | .comptime_int, | 809 | .comptime_int, |
| 639 | .comptime_float, | 810 | .comptime_float, |
| 640 | .noreturn, | 811 | .noreturn, |
| 812 | .@"null", | ||
| 641 | .array, | 813 | .array, |
| 642 | .single_const_pointer, | 814 | .single_const_pointer, |
| 643 | .single_const_pointer_to_comptime_int, | 815 | .single_const_pointer_to_comptime_int, |
| ... | @@ -664,6 +836,7 @@ pub const Type = extern union { | ... | @@ -664,6 +836,7 @@ pub const Type = extern union { |
| 664 | /// Asserts the type is a function. | 836 | /// Asserts the type is a function. |
| 665 | pub fn fnReturnType(self: Type) Type { | 837 | pub fn fnReturnType(self: Type) Type { |
| 666 | return switch (self.tag()) { | 838 | return switch (self.tag()) { |
| 839 | .fn_noreturn_no_args => Type.initTag(.noreturn), | ||
| 667 | .fn_naked_noreturn_no_args => Type.initTag(.noreturn), | 840 | .fn_naked_noreturn_no_args => Type.initTag(.noreturn), |
| 668 | .fn_ccc_void_no_args => Type.initTag(.void), | 841 | .fn_ccc_void_no_args => Type.initTag(.void), |
| 669 | 842 | ||
| ... | @@ -680,6 +853,7 @@ pub const Type = extern union { | ... | @@ -680,6 +853,7 @@ pub const Type = extern union { |
| 680 | .comptime_int, | 853 | .comptime_int, |
| 681 | .comptime_float, | 854 | .comptime_float, |
| 682 | .noreturn, | 855 | .noreturn, |
| 856 | .@"null", | ||
| 683 | .array, | 857 | .array, |
| 684 | .single_const_pointer, | 858 | .single_const_pointer, |
| 685 | .single_const_pointer_to_comptime_int, | 859 | .single_const_pointer_to_comptime_int, |
| ... | @@ -706,6 +880,7 @@ pub const Type = extern union { | ... | @@ -706,6 +880,7 @@ pub const Type = extern union { |
| 706 | /// Asserts the type is a function. | 880 | /// Asserts the type is a function. |
| 707 | pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention { | 881 | pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention { |
| 708 | return switch (self.tag()) { | 882 | return switch (self.tag()) { |
| 883 | .fn_noreturn_no_args => .Unspecified, | ||
| 709 | .fn_naked_noreturn_no_args => .Naked, | 884 | .fn_naked_noreturn_no_args => .Naked, |
| 710 | .fn_ccc_void_no_args => .C, | 885 | .fn_ccc_void_no_args => .C, |
| 711 | 886 | ||
| ... | @@ -722,6 +897,51 @@ pub const Type = extern union { | ... | @@ -722,6 +897,51 @@ pub const Type = extern union { |
| 722 | .comptime_int, | 897 | .comptime_int, |
| 723 | .comptime_float, | 898 | .comptime_float, |
| 724 | .noreturn, | 899 | .noreturn, |
| 900 | .@"null", | ||
| 901 | .array, | ||
| 902 | .single_const_pointer, | ||
| 903 | .single_const_pointer_to_comptime_int, | ||
| 904 | .array_u8_sentinel_0, | ||
| 905 | .const_slice_u8, | ||
| 906 | .u8, | ||
| 907 | .i8, | ||
| 908 | .usize, | ||
| 909 | .isize, | ||
| 910 | .c_short, | ||
| 911 | .c_ushort, | ||
| 912 | .c_int, | ||
| 913 | .c_uint, | ||
| 914 | .c_long, | ||
| 915 | .c_ulong, | ||
| 916 | .c_longlong, | ||
| 917 | .c_ulonglong, | ||
| 918 | .int_unsigned, | ||
| 919 | .int_signed, | ||
| 920 | => unreachable, | ||
| 921 | }; | ||
| 922 | } | ||
| 923 | |||
| 924 | /// Asserts the type is a function. | ||
| 925 | pub fn fnIsVarArgs(self: Type) bool { | ||
| 926 | return switch (self.tag()) { | ||
| 927 | .fn_noreturn_no_args => false, | ||
| 928 | .fn_naked_noreturn_no_args => false, | ||
| 929 | .fn_ccc_void_no_args => false, | ||
| 930 | |||
| 931 | .f16, | ||
| 932 | .f32, | ||
| 933 | .f64, | ||
| 934 | .f128, | ||
| 935 | .c_longdouble, | ||
| 936 | .c_void, | ||
| 937 | .bool, | ||
| 938 | .void, | ||
| 939 | .type, | ||
| 940 | .anyerror, | ||
| 941 | .comptime_int, | ||
| 942 | .comptime_float, | ||
| 943 | .noreturn, | ||
| 944 | .@"null", | ||
| 725 | .array, | 945 | .array, |
| 726 | .single_const_pointer, | 946 | .single_const_pointer, |
| 727 | .single_const_pointer_to_comptime_int, | 947 | .single_const_pointer_to_comptime_int, |
| ... | @@ -776,6 +996,8 @@ pub const Type = extern union { | ... | @@ -776,6 +996,8 @@ pub const Type = extern union { |
| 776 | .type, | 996 | .type, |
| 777 | .anyerror, | 997 | .anyerror, |
| 778 | .noreturn, | 998 | .noreturn, |
| 999 | .@"null", | ||
| 1000 | .fn_noreturn_no_args, | ||
| 779 | .fn_naked_noreturn_no_args, | 1001 | .fn_naked_noreturn_no_args, |
| 780 | .fn_ccc_void_no_args, | 1002 | .fn_ccc_void_no_args, |
| 781 | .array, | 1003 | .array, |
| ... | @@ -812,6 +1034,7 @@ pub const Type = extern union { | ... | @@ -812,6 +1034,7 @@ pub const Type = extern union { |
| 812 | .bool, | 1034 | .bool, |
| 813 | .type, | 1035 | .type, |
| 814 | .anyerror, | 1036 | .anyerror, |
| 1037 | .fn_noreturn_no_args, | ||
| 815 | .fn_naked_noreturn_no_args, | 1038 | .fn_naked_noreturn_no_args, |
| 816 | .fn_ccc_void_no_args, | 1039 | .fn_ccc_void_no_args, |
| 817 | .single_const_pointer_to_comptime_int, | 1040 | .single_const_pointer_to_comptime_int, |
| ... | @@ -822,6 +1045,7 @@ pub const Type = extern union { | ... | @@ -822,6 +1045,7 @@ pub const Type = extern union { |
| 822 | .c_void, | 1045 | .c_void, |
| 823 | .void, | 1046 | .void, |
| 824 | .noreturn, | 1047 | .noreturn, |
| 1048 | .@"null", | ||
| 825 | => return true, | 1049 | => return true, |
| 826 | 1050 | ||
| 827 | .int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0, | 1051 | .int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0, |
| ... | @@ -865,6 +1089,7 @@ pub const Type = extern union { | ... | @@ -865,6 +1089,7 @@ pub const Type = extern union { |
| 865 | .bool, | 1089 | .bool, |
| 866 | .type, | 1090 | .type, |
| 867 | .anyerror, | 1091 | .anyerror, |
| 1092 | .fn_noreturn_no_args, | ||
| 868 | .fn_naked_noreturn_no_args, | 1093 | .fn_naked_noreturn_no_args, |
| 869 | .fn_ccc_void_no_args, | 1094 | .fn_ccc_void_no_args, |
| 870 | .single_const_pointer_to_comptime_int, | 1095 | .single_const_pointer_to_comptime_int, |
| ... | @@ -873,6 +1098,7 @@ pub const Type = extern union { | ... | @@ -873,6 +1098,7 @@ pub const Type = extern union { |
| 873 | .c_void, | 1098 | .c_void, |
| 874 | .void, | 1099 | .void, |
| 875 | .noreturn, | 1100 | .noreturn, |
| 1101 | .@"null", | ||
| 876 | .int_unsigned, | 1102 | .int_unsigned, |
| 877 | .int_signed, | 1103 | .int_signed, |
| 878 | .array, | 1104 | .array, |
| ... | @@ -902,11 +1128,11 @@ pub const Type = extern union { | ... | @@ -902,11 +1128,11 @@ pub const Type = extern union { |
| 902 | c_longlong, | 1128 | c_longlong, |
| 903 | c_ulonglong, | 1129 | c_ulonglong, |
| 904 | c_longdouble, | 1130 | c_longdouble, |
| 905 | c_void, | ||
| 906 | f16, | 1131 | f16, |
| 907 | f32, | 1132 | f32, |
| 908 | f64, | 1133 | f64, |
| 909 | f128, | 1134 | f128, |
| 1135 | c_void, | ||
| 910 | bool, | 1136 | bool, |
| 911 | void, | 1137 | void, |
| 912 | type, | 1138 | type, |
| ... | @@ -914,6 +1140,8 @@ pub const Type = extern union { | ... | @@ -914,6 +1140,8 @@ pub const Type = extern union { |
| 914 | comptime_int, | 1140 | comptime_int, |
| 915 | comptime_float, | 1141 | comptime_float, |
| 916 | noreturn, | 1142 | noreturn, |
| 1143 | @"null", | ||
| 1144 | fn_noreturn_no_args, | ||
| 917 | fn_naked_noreturn_no_args, | 1145 | fn_naked_noreturn_no_args, |
| 918 | fn_ccc_void_no_args, | 1146 | fn_ccc_void_no_args, |
| 919 | single_const_pointer_to_comptime_int, | 1147 | single_const_pointer_to_comptime_int, |
src-self-hosted/util.zig deleted-47| ... | @@ -1,47 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Target = std.Target; | ||
| 3 | const llvm = @import("llvm.zig"); | ||
| 4 | |||
| 5 | pub fn getDarwinArchString(self: Target) [:0]const u8 { | ||
| 6 | switch (self.cpu.arch) { | ||
| 7 | .aarch64 => return "arm64", | ||
| 8 | .thumb, | ||
| 9 | .arm, | ||
| 10 | => return "arm", | ||
| 11 | .powerpc => return "ppc", | ||
| 12 | .powerpc64 => return "ppc64", | ||
| 13 | .powerpc64le => return "ppc64le", | ||
| 14 | // @tagName should be able to return sentinel terminated slice | ||
| 15 | else => @panic("TODO https://github.com/ziglang/zig/issues/3779"), //return @tagName(arch), | ||
| 16 | } | ||
| 17 | } | ||
| 18 | |||
| 19 | pub fn llvmTargetFromTriple(triple: [:0]const u8) !*llvm.Target { | ||
| 20 | var result: *llvm.Target = undefined; | ||
| 21 | var err_msg: [*:0]u8 = undefined; | ||
| 22 | if (llvm.GetTargetFromTriple(triple, &result, &err_msg) != 0) { | ||
| 23 | std.debug.warn("triple: {s} error: {s}\n", .{ triple, err_msg }); | ||
| 24 | return error.UnsupportedTarget; | ||
| 25 | } | ||
| 26 | return result; | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn initializeAllTargets() void { | ||
| 30 | llvm.InitializeAllTargets(); | ||
| 31 | llvm.InitializeAllTargetInfos(); | ||
| 32 | llvm.InitializeAllTargetMCs(); | ||
| 33 | llvm.InitializeAllAsmPrinters(); | ||
| 34 | llvm.InitializeAllAsmParsers(); | ||
| 35 | } | ||
| 36 | |||
| 37 | pub fn getLLVMTriple(allocator: *std.mem.Allocator, target: std.Target) ![:0]u8 { | ||
| 38 | var result = try std.ArrayListSentineled(u8, 0).initSize(allocator, 0); | ||
| 39 | defer result.deinit(); | ||
| 40 | |||
| 41 | try result.outStream().print( | ||
| 42 | "{}-unknown-{}-{}", | ||
| 43 | .{ @tagName(target.cpu.arch), @tagName(target.os.tag), @tagName(target.abi) }, | ||
| 44 | ); | ||
| 45 | |||
| 46 | return result.toOwnedSlice(); | ||
| 47 | } | ||
src-self-hosted/value.zig+124-80| ... | @@ -6,10 +6,11 @@ const BigIntConst = std.math.big.int.Const; | ... | @@ -6,10 +6,11 @@ const BigIntConst = std.math.big.int.Const; |
| 6 | const BigIntMutable = std.math.big.int.Mutable; | 6 | const BigIntMutable = std.math.big.int.Mutable; |
| 7 | const Target = std.Target; | 7 | const Target = std.Target; |
| 8 | const Allocator = std.mem.Allocator; | 8 | const Allocator = std.mem.Allocator; |
| 9 | const Module = @import("Module.zig"); | ||
| 9 | 10 | ||
| 10 | /// This is the raw data, with no bookkeeping, no memory awareness, | 11 | /// This is the raw data, with no bookkeeping, no memory awareness, |
| 11 | /// no de-duplication, and no type system awareness. | 12 | /// no de-duplication, and no type system awareness. |
| 12 | /// It's important for this struct to be small. | 13 | /// It's important for this type to be small. |
| 13 | /// This union takes advantage of the fact that the first page of memory | 14 | /// This union takes advantage of the fact that the first page of memory |
| 14 | /// is unmapped, giving us 4096 possible enum tags that have no payload. | 15 | /// is unmapped, giving us 4096 possible enum tags that have no payload. |
| 15 | pub const Value = extern union { | 16 | pub const Value = extern union { |
| ... | @@ -45,6 +46,8 @@ pub const Value = extern union { | ... | @@ -45,6 +46,8 @@ pub const Value = extern union { |
| 45 | comptime_int_type, | 46 | comptime_int_type, |
| 46 | comptime_float_type, | 47 | comptime_float_type, |
| 47 | noreturn_type, | 48 | noreturn_type, |
| 49 | null_type, | ||
| 50 | fn_noreturn_no_args_type, | ||
| 48 | fn_naked_noreturn_no_args_type, | 51 | fn_naked_noreturn_no_args_type, |
| 49 | fn_ccc_void_no_args_type, | 52 | fn_ccc_void_no_args_type, |
| 50 | single_const_pointer_to_comptime_int_type, | 53 | single_const_pointer_to_comptime_int_type, |
| ... | @@ -64,8 +67,9 @@ pub const Value = extern union { | ... | @@ -64,8 +67,9 @@ pub const Value = extern union { |
| 64 | int_big_positive, | 67 | int_big_positive, |
| 65 | int_big_negative, | 68 | int_big_negative, |
| 66 | function, | 69 | function, |
| 67 | ref, | ||
| 68 | ref_val, | 70 | ref_val, |
| 71 | decl_ref, | ||
| 72 | elem_ptr, | ||
| 69 | bytes, | 73 | bytes, |
| 70 | repeated, // the value is a value repeated some number of times | 74 | repeated, // the value is a value repeated some number of times |
| 71 | 75 | ||
| ... | @@ -136,6 +140,8 @@ pub const Value = extern union { | ... | @@ -136,6 +140,8 @@ pub const Value = extern union { |
| 136 | .comptime_int_type => return out_stream.writeAll("comptime_int"), | 140 | .comptime_int_type => return out_stream.writeAll("comptime_int"), |
| 137 | .comptime_float_type => return out_stream.writeAll("comptime_float"), | 141 | .comptime_float_type => return out_stream.writeAll("comptime_float"), |
| 138 | .noreturn_type => return out_stream.writeAll("noreturn"), | 142 | .noreturn_type => return out_stream.writeAll("noreturn"), |
| 143 | .null_type => return out_stream.writeAll("@TypeOf(null)"), | ||
| 144 | .fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"), | ||
| 139 | .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), | 145 | .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), |
| 140 | .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"), | 146 | .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"), |
| 141 | .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"), | 147 | .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"), |
| ... | @@ -153,11 +159,16 @@ pub const Value = extern union { | ... | @@ -153,11 +159,16 @@ pub const Value = extern union { |
| 153 | .int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}), | 159 | .int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}), |
| 154 | .int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}), | 160 | .int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}), |
| 155 | .function => return out_stream.writeAll("(function)"), | 161 | .function => return out_stream.writeAll("(function)"), |
| 156 | .ref => return out_stream.writeAll("(ref)"), | ||
| 157 | .ref_val => { | 162 | .ref_val => { |
| 158 | try out_stream.writeAll("*const "); | 163 | const ref_val = val.cast(Payload.RefVal).?; |
| 159 | val = val.cast(Payload.RefVal).?.val; | 164 | try out_stream.writeAll("&const "); |
| 160 | continue; | 165 | val = ref_val.val; |
| 166 | }, | ||
| 167 | .decl_ref => return out_stream.writeAll("(decl ref)"), | ||
| 168 | .elem_ptr => { | ||
| 169 | const elem_ptr = val.cast(Payload.ElemPtr).?; | ||
| 170 | try out_stream.print("&[{}] ", .{elem_ptr.index}); | ||
| 171 | val = elem_ptr.array_ptr; | ||
| 161 | }, | 172 | }, |
| 162 | .bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream), | 173 | .bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream), |
| 163 | .repeated => { | 174 | .repeated => { |
| ... | @@ -169,10 +180,17 @@ pub const Value = extern union { | ... | @@ -169,10 +180,17 @@ pub const Value = extern union { |
| 169 | 180 | ||
| 170 | /// Asserts that the value is representable as an array of bytes. | 181 | /// Asserts that the value is representable as an array of bytes. |
| 171 | /// Copies the value into a freshly allocated slice of memory, which is owned by the caller. | 182 | /// Copies the value into a freshly allocated slice of memory, which is owned by the caller. |
| 172 | pub fn toAllocatedBytes(self: Value, allocator: *Allocator) Allocator.Error![]u8 { | 183 | pub fn toAllocatedBytes(self: Value, allocator: *Allocator) ![]u8 { |
| 173 | if (self.cast(Payload.Bytes)) |bytes| { | 184 | if (self.cast(Payload.Bytes)) |bytes| { |
| 174 | return std.mem.dupe(allocator, u8, bytes.data); | 185 | return std.mem.dupe(allocator, u8, bytes.data); |
| 175 | } | 186 | } |
| 187 | if (self.cast(Payload.Repeated)) |repeated| { | ||
| 188 | @panic("TODO implement toAllocatedBytes for this Value tag"); | ||
| 189 | } | ||
| 190 | if (self.cast(Payload.DeclRef)) |declref| { | ||
| 191 | const val = try declref.decl.value(); | ||
| 192 | return val.toAllocatedBytes(allocator); | ||
| 193 | } | ||
| 176 | unreachable; | 194 | unreachable; |
| 177 | } | 195 | } |
| 178 | 196 | ||
| ... | @@ -181,31 +199,33 @@ pub const Value = extern union { | ... | @@ -181,31 +199,33 @@ pub const Value = extern union { |
| 181 | return switch (self.tag()) { | 199 | return switch (self.tag()) { |
| 182 | .ty => self.cast(Payload.Ty).?.ty, | 200 | .ty => self.cast(Payload.Ty).?.ty, |
| 183 | 201 | ||
| 184 | .u8_type => Type.initTag(.@"u8"), | 202 | .u8_type => Type.initTag(.u8), |
| 185 | .i8_type => Type.initTag(.@"i8"), | 203 | .i8_type => Type.initTag(.i8), |
| 186 | .isize_type => Type.initTag(.@"isize"), | 204 | .isize_type => Type.initTag(.isize), |
| 187 | .usize_type => Type.initTag(.@"usize"), | 205 | .usize_type => Type.initTag(.usize), |
| 188 | .c_short_type => Type.initTag(.@"c_short"), | 206 | .c_short_type => Type.initTag(.c_short), |
| 189 | .c_ushort_type => Type.initTag(.@"c_ushort"), | 207 | .c_ushort_type => Type.initTag(.c_ushort), |
| 190 | .c_int_type => Type.initTag(.@"c_int"), | 208 | .c_int_type => Type.initTag(.c_int), |
| 191 | .c_uint_type => Type.initTag(.@"c_uint"), | 209 | .c_uint_type => Type.initTag(.c_uint), |
| 192 | .c_long_type => Type.initTag(.@"c_long"), | 210 | .c_long_type => Type.initTag(.c_long), |
| 193 | .c_ulong_type => Type.initTag(.@"c_ulong"), | 211 | .c_ulong_type => Type.initTag(.c_ulong), |
| 194 | .c_longlong_type => Type.initTag(.@"c_longlong"), | 212 | .c_longlong_type => Type.initTag(.c_longlong), |
| 195 | .c_ulonglong_type => Type.initTag(.@"c_ulonglong"), | 213 | .c_ulonglong_type => Type.initTag(.c_ulonglong), |
| 196 | .c_longdouble_type => Type.initTag(.@"c_longdouble"), | 214 | .c_longdouble_type => Type.initTag(.c_longdouble), |
| 197 | .f16_type => Type.initTag(.@"f16"), | 215 | .f16_type => Type.initTag(.f16), |
| 198 | .f32_type => Type.initTag(.@"f32"), | 216 | .f32_type => Type.initTag(.f32), |
| 199 | .f64_type => Type.initTag(.@"f64"), | 217 | .f64_type => Type.initTag(.f64), |
| 200 | .f128_type => Type.initTag(.@"f128"), | 218 | .f128_type => Type.initTag(.f128), |
| 201 | .c_void_type => Type.initTag(.@"c_void"), | 219 | .c_void_type => Type.initTag(.c_void), |
| 202 | .bool_type => Type.initTag(.@"bool"), | 220 | .bool_type => Type.initTag(.bool), |
| 203 | .void_type => Type.initTag(.@"void"), | 221 | .void_type => Type.initTag(.void), |
| 204 | .type_type => Type.initTag(.@"type"), | 222 | .type_type => Type.initTag(.type), |
| 205 | .anyerror_type => Type.initTag(.@"anyerror"), | 223 | .anyerror_type => Type.initTag(.anyerror), |
| 206 | .comptime_int_type => Type.initTag(.@"comptime_int"), | 224 | .comptime_int_type => Type.initTag(.comptime_int), |
| 207 | .comptime_float_type => Type.initTag(.@"comptime_float"), | 225 | .comptime_float_type => Type.initTag(.comptime_float), |
| 208 | .noreturn_type => Type.initTag(.@"noreturn"), | 226 | .noreturn_type => Type.initTag(.noreturn), |
| 227 | .null_type => Type.initTag(.@"null"), | ||
| 228 | .fn_noreturn_no_args_type => Type.initTag(.fn_noreturn_no_args), | ||
| 209 | .fn_naked_noreturn_no_args_type => Type.initTag(.fn_naked_noreturn_no_args), | 229 | .fn_naked_noreturn_no_args_type => Type.initTag(.fn_naked_noreturn_no_args), |
| 210 | .fn_ccc_void_no_args_type => Type.initTag(.fn_ccc_void_no_args), | 230 | .fn_ccc_void_no_args_type => Type.initTag(.fn_ccc_void_no_args), |
| 211 | .single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int), | 231 | .single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int), |
| ... | @@ -222,8 +242,9 @@ pub const Value = extern union { | ... | @@ -222,8 +242,9 @@ pub const Value = extern union { |
| 222 | .int_big_positive, | 242 | .int_big_positive, |
| 223 | .int_big_negative, | 243 | .int_big_negative, |
| 224 | .function, | 244 | .function, |
| 225 | .ref, | ||
| 226 | .ref_val, | 245 | .ref_val, |
| 246 | .decl_ref, | ||
| 247 | .elem_ptr, | ||
| 227 | .bytes, | 248 | .bytes, |
| 228 | .repeated, | 249 | .repeated, |
| 229 | => unreachable, | 250 | => unreachable, |
| ... | @@ -259,6 +280,8 @@ pub const Value = extern union { | ... | @@ -259,6 +280,8 @@ pub const Value = extern union { |
| 259 | .comptime_int_type, | 280 | .comptime_int_type, |
| 260 | .comptime_float_type, | 281 | .comptime_float_type, |
| 261 | .noreturn_type, | 282 | .noreturn_type, |
| 283 | .null_type, | ||
| 284 | .fn_noreturn_no_args_type, | ||
| 262 | .fn_naked_noreturn_no_args_type, | 285 | .fn_naked_noreturn_no_args_type, |
| 263 | .fn_ccc_void_no_args_type, | 286 | .fn_ccc_void_no_args_type, |
| 264 | .single_const_pointer_to_comptime_int_type, | 287 | .single_const_pointer_to_comptime_int_type, |
| ... | @@ -267,8 +290,9 @@ pub const Value = extern union { | ... | @@ -267,8 +290,9 @@ pub const Value = extern union { |
| 267 | .bool_false, | 290 | .bool_false, |
| 268 | .null_value, | 291 | .null_value, |
| 269 | .function, | 292 | .function, |
| 270 | .ref, | ||
| 271 | .ref_val, | 293 | .ref_val, |
| 294 | .decl_ref, | ||
| 295 | .elem_ptr, | ||
| 272 | .bytes, | 296 | .bytes, |
| 273 | .undef, | 297 | .undef, |
| 274 | .repeated, | 298 | .repeated, |
| ... | @@ -314,6 +338,8 @@ pub const Value = extern union { | ... | @@ -314,6 +338,8 @@ pub const Value = extern union { |
| 314 | .comptime_int_type, | 338 | .comptime_int_type, |
| 315 | .comptime_float_type, | 339 | .comptime_float_type, |
| 316 | .noreturn_type, | 340 | .noreturn_type, |
| 341 | .null_type, | ||
| 342 | .fn_noreturn_no_args_type, | ||
| 317 | .fn_naked_noreturn_no_args_type, | 343 | .fn_naked_noreturn_no_args_type, |
| 318 | .fn_ccc_void_no_args_type, | 344 | .fn_ccc_void_no_args_type, |
| 319 | .single_const_pointer_to_comptime_int_type, | 345 | .single_const_pointer_to_comptime_int_type, |
| ... | @@ -322,8 +348,9 @@ pub const Value = extern union { | ... | @@ -322,8 +348,9 @@ pub const Value = extern union { |
| 322 | .bool_false, | 348 | .bool_false, |
| 323 | .null_value, | 349 | .null_value, |
| 324 | .function, | 350 | .function, |
| 325 | .ref, | ||
| 326 | .ref_val, | 351 | .ref_val, |
| 352 | .decl_ref, | ||
| 353 | .elem_ptr, | ||
| 327 | .bytes, | 354 | .bytes, |
| 328 | .undef, | 355 | .undef, |
| 329 | .repeated, | 356 | .repeated, |
| ... | @@ -370,6 +397,8 @@ pub const Value = extern union { | ... | @@ -370,6 +397,8 @@ pub const Value = extern union { |
| 370 | .comptime_int_type, | 397 | .comptime_int_type, |
| 371 | .comptime_float_type, | 398 | .comptime_float_type, |
| 372 | .noreturn_type, | 399 | .noreturn_type, |
| 400 | .null_type, | ||
| 401 | .fn_noreturn_no_args_type, | ||
| 373 | .fn_naked_noreturn_no_args_type, | 402 | .fn_naked_noreturn_no_args_type, |
| 374 | .fn_ccc_void_no_args_type, | 403 | .fn_ccc_void_no_args_type, |
| 375 | .single_const_pointer_to_comptime_int_type, | 404 | .single_const_pointer_to_comptime_int_type, |
| ... | @@ -378,8 +407,9 @@ pub const Value = extern union { | ... | @@ -378,8 +407,9 @@ pub const Value = extern union { |
| 378 | .bool_false, | 407 | .bool_false, |
| 379 | .null_value, | 408 | .null_value, |
| 380 | .function, | 409 | .function, |
| 381 | .ref, | ||
| 382 | .ref_val, | 410 | .ref_val, |
| 411 | .decl_ref, | ||
| 412 | .elem_ptr, | ||
| 383 | .bytes, | 413 | .bytes, |
| 384 | .undef, | 414 | .undef, |
| 385 | .repeated, | 415 | .repeated, |
| ... | @@ -431,6 +461,8 @@ pub const Value = extern union { | ... | @@ -431,6 +461,8 @@ pub const Value = extern union { |
| 431 | .comptime_int_type, | 461 | .comptime_int_type, |
| 432 | .comptime_float_type, | 462 | .comptime_float_type, |
| 433 | .noreturn_type, | 463 | .noreturn_type, |
| 464 | .null_type, | ||
| 465 | .fn_noreturn_no_args_type, | ||
| 434 | .fn_naked_noreturn_no_args_type, | 466 | .fn_naked_noreturn_no_args_type, |
| 435 | .fn_ccc_void_no_args_type, | 467 | .fn_ccc_void_no_args_type, |
| 436 | .single_const_pointer_to_comptime_int_type, | 468 | .single_const_pointer_to_comptime_int_type, |
| ... | @@ -439,8 +471,9 @@ pub const Value = extern union { | ... | @@ -439,8 +471,9 @@ pub const Value = extern union { |
| 439 | .bool_false, | 471 | .bool_false, |
| 440 | .null_value, | 472 | .null_value, |
| 441 | .function, | 473 | .function, |
| 442 | .ref, | ||
| 443 | .ref_val, | 474 | .ref_val, |
| 475 | .decl_ref, | ||
| 476 | .elem_ptr, | ||
| 444 | .bytes, | 477 | .bytes, |
| 445 | .repeated, | 478 | .repeated, |
| 446 | => unreachable, | 479 | => unreachable, |
| ... | @@ -521,6 +554,8 @@ pub const Value = extern union { | ... | @@ -521,6 +554,8 @@ pub const Value = extern union { |
| 521 | .comptime_int_type, | 554 | .comptime_int_type, |
| 522 | .comptime_float_type, | 555 | .comptime_float_type, |
| 523 | .noreturn_type, | 556 | .noreturn_type, |
| 557 | .null_type, | ||
| 558 | .fn_noreturn_no_args_type, | ||
| 524 | .fn_naked_noreturn_no_args_type, | 559 | .fn_naked_noreturn_no_args_type, |
| 525 | .fn_ccc_void_no_args_type, | 560 | .fn_ccc_void_no_args_type, |
| 526 | .single_const_pointer_to_comptime_int_type, | 561 | .single_const_pointer_to_comptime_int_type, |
| ... | @@ -529,8 +564,9 @@ pub const Value = extern union { | ... | @@ -529,8 +564,9 @@ pub const Value = extern union { |
| 529 | .bool_false, | 564 | .bool_false, |
| 530 | .null_value, | 565 | .null_value, |
| 531 | .function, | 566 | .function, |
| 532 | .ref, | ||
| 533 | .ref_val, | 567 | .ref_val, |
| 568 | .decl_ref, | ||
| 569 | .elem_ptr, | ||
| 534 | .bytes, | 570 | .bytes, |
| 535 | .repeated, | 571 | .repeated, |
| 536 | .undef, | 572 | .undef, |
| ... | @@ -573,6 +609,8 @@ pub const Value = extern union { | ... | @@ -573,6 +609,8 @@ pub const Value = extern union { |
| 573 | .comptime_int_type, | 609 | .comptime_int_type, |
| 574 | .comptime_float_type, | 610 | .comptime_float_type, |
| 575 | .noreturn_type, | 611 | .noreturn_type, |
| 612 | .null_type, | ||
| 613 | .fn_noreturn_no_args_type, | ||
| 576 | .fn_naked_noreturn_no_args_type, | 614 | .fn_naked_noreturn_no_args_type, |
| 577 | .fn_ccc_void_no_args_type, | 615 | .fn_ccc_void_no_args_type, |
| 578 | .single_const_pointer_to_comptime_int_type, | 616 | .single_const_pointer_to_comptime_int_type, |
| ... | @@ -581,8 +619,9 @@ pub const Value = extern union { | ... | @@ -581,8 +619,9 @@ pub const Value = extern union { |
| 581 | .bool_false, | 619 | .bool_false, |
| 582 | .null_value, | 620 | .null_value, |
| 583 | .function, | 621 | .function, |
| 584 | .ref, | ||
| 585 | .ref_val, | 622 | .ref_val, |
| 623 | .decl_ref, | ||
| 624 | .elem_ptr, | ||
| 586 | .bytes, | 625 | .bytes, |
| 587 | .repeated, | 626 | .repeated, |
| 588 | .undef, | 627 | .undef, |
| ... | @@ -636,7 +675,8 @@ pub const Value = extern union { | ... | @@ -636,7 +675,8 @@ pub const Value = extern union { |
| 636 | } | 675 | } |
| 637 | 676 | ||
| 638 | /// Asserts the value is a pointer and dereferences it. | 677 | /// Asserts the value is a pointer and dereferences it. |
| 639 | pub fn pointerDeref(self: Value) Value { | 678 | /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis. |
| 679 | pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { | ||
| 640 | return switch (self.tag()) { | 680 | return switch (self.tag()) { |
| 641 | .ty, | 681 | .ty, |
| 642 | .u8_type, | 682 | .u8_type, |
| ... | @@ -664,6 +704,8 @@ pub const Value = extern union { | ... | @@ -664,6 +704,8 @@ pub const Value = extern union { |
| 664 | .comptime_int_type, | 704 | .comptime_int_type, |
| 665 | .comptime_float_type, | 705 | .comptime_float_type, |
| 666 | .noreturn_type, | 706 | .noreturn_type, |
| 707 | .null_type, | ||
| 708 | .fn_noreturn_no_args_type, | ||
| 667 | .fn_naked_noreturn_no_args_type, | 709 | .fn_naked_noreturn_no_args_type, |
| 668 | .fn_ccc_void_no_args_type, | 710 | .fn_ccc_void_no_args_type, |
| 669 | .single_const_pointer_to_comptime_int_type, | 711 | .single_const_pointer_to_comptime_int_type, |
| ... | @@ -683,14 +725,19 @@ pub const Value = extern union { | ... | @@ -683,14 +725,19 @@ pub const Value = extern union { |
| 683 | => unreachable, | 725 | => unreachable, |
| 684 | 726 | ||
| 685 | .the_one_possible_value => Value.initTag(.the_one_possible_value), | 727 | .the_one_possible_value => Value.initTag(.the_one_possible_value), |
| 686 | .ref => self.cast(Payload.Ref).?.cell.contents, | ||
| 687 | .ref_val => self.cast(Payload.RefVal).?.val, | 728 | .ref_val => self.cast(Payload.RefVal).?.val, |
| 729 | .decl_ref => self.cast(Payload.DeclRef).?.decl.value(), | ||
| 730 | .elem_ptr => { | ||
| 731 | const elem_ptr = self.cast(Payload.ElemPtr).?; | ||
| 732 | const array_val = try elem_ptr.array_ptr.pointerDeref(allocator); | ||
| 733 | return array_val.elemValue(allocator, elem_ptr.index); | ||
| 734 | }, | ||
| 688 | }; | 735 | }; |
| 689 | } | 736 | } |
| 690 | 737 | ||
| 691 | /// Asserts the value is a single-item pointer to an array, or an array, | 738 | /// Asserts the value is a single-item pointer to an array, or an array, |
| 692 | /// or an unknown-length pointer, and returns the element value at the index. | 739 | /// or an unknown-length pointer, and returns the element value at the index. |
| 693 | pub fn elemValueAt(self: Value, allocator: *Allocator, index: usize) Allocator.Error!Value { | 740 | pub fn elemValue(self: Value, allocator: *Allocator, index: usize) error{OutOfMemory}!Value { |
| 694 | switch (self.tag()) { | 741 | switch (self.tag()) { |
| 695 | .ty, | 742 | .ty, |
| 696 | .u8_type, | 743 | .u8_type, |
| ... | @@ -718,6 +765,8 @@ pub const Value = extern union { | ... | @@ -718,6 +765,8 @@ pub const Value = extern union { |
| 718 | .comptime_int_type, | 765 | .comptime_int_type, |
| 719 | .comptime_float_type, | 766 | .comptime_float_type, |
| 720 | .noreturn_type, | 767 | .noreturn_type, |
| 768 | .null_type, | ||
| 769 | .fn_noreturn_no_args_type, | ||
| 721 | .fn_naked_noreturn_no_args_type, | 770 | .fn_naked_noreturn_no_args_type, |
| 722 | .fn_ccc_void_no_args_type, | 771 | .fn_ccc_void_no_args_type, |
| 723 | .single_const_pointer_to_comptime_int_type, | 772 | .single_const_pointer_to_comptime_int_type, |
| ... | @@ -733,13 +782,13 @@ pub const Value = extern union { | ... | @@ -733,13 +782,13 @@ pub const Value = extern union { |
| 733 | .int_big_positive, | 782 | .int_big_positive, |
| 734 | .int_big_negative, | 783 | .int_big_negative, |
| 735 | .undef, | 784 | .undef, |
| 785 | .elem_ptr, | ||
| 786 | .ref_val, | ||
| 787 | .decl_ref, | ||
| 736 | => unreachable, | 788 | => unreachable, |
| 737 | 789 | ||
| 738 | .ref => @panic("TODO figure out how MemoryCell works"), | ||
| 739 | .ref_val => @panic("TODO figure out how MemoryCell works"), | ||
| 740 | |||
| 741 | .bytes => { | 790 | .bytes => { |
| 742 | const int_payload = try allocator.create(Value.Payload.Int_u64); | 791 | const int_payload = try allocator.create(Payload.Int_u64); |
| 743 | int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] }; | 792 | int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] }; |
| 744 | return Value.initPayload(&int_payload.base); | 793 | return Value.initPayload(&int_payload.base); |
| 745 | }, | 794 | }, |
| ... | @@ -749,6 +798,17 @@ pub const Value = extern union { | ... | @@ -749,6 +798,17 @@ pub const Value = extern union { |
| 749 | } | 798 | } |
| 750 | } | 799 | } |
| 751 | 800 | ||
| 801 | /// Returns a pointer to the element value at the index. | ||
| 802 | pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value { | ||
| 803 | const payload = try allocator.create(Payload.ElemPtr); | ||
| 804 | if (self.cast(Payload.ElemPtr)) |elem_ptr| { | ||
| 805 | payload.* = .{ .array_ptr = elem_ptr.array_ptr, .index = elem_ptr.index + index }; | ||
| 806 | } else { | ||
| 807 | payload.* = .{ .array_ptr = self, .index = index }; | ||
| 808 | } | ||
| 809 | return Value.initPayload(&payload.base); | ||
| 810 | } | ||
| 811 | |||
| 752 | pub fn isUndef(self: Value) bool { | 812 | pub fn isUndef(self: Value) bool { |
| 753 | return self.tag() == .undef; | 813 | return self.tag() == .undef; |
| 754 | } | 814 | } |
| ... | @@ -783,6 +843,8 @@ pub const Value = extern union { | ... | @@ -783,6 +843,8 @@ pub const Value = extern union { |
| 783 | .comptime_int_type, | 843 | .comptime_int_type, |
| 784 | .comptime_float_type, | 844 | .comptime_float_type, |
| 785 | .noreturn_type, | 845 | .noreturn_type, |
| 846 | .null_type, | ||
| 847 | .fn_noreturn_no_args_type, | ||
| 786 | .fn_naked_noreturn_no_args_type, | 848 | .fn_naked_noreturn_no_args_type, |
| 787 | .fn_ccc_void_no_args_type, | 849 | .fn_ccc_void_no_args_type, |
| 788 | .single_const_pointer_to_comptime_int_type, | 850 | .single_const_pointer_to_comptime_int_type, |
| ... | @@ -796,8 +858,9 @@ pub const Value = extern union { | ... | @@ -796,8 +858,9 @@ pub const Value = extern union { |
| 796 | .int_i64, | 858 | .int_i64, |
| 797 | .int_big_positive, | 859 | .int_big_positive, |
| 798 | .int_big_negative, | 860 | .int_big_negative, |
| 799 | .ref, | ||
| 800 | .ref_val, | 861 | .ref_val, |
| 862 | .decl_ref, | ||
| 863 | .elem_ptr, | ||
| 801 | .bytes, | 864 | .bytes, |
| 802 | .repeated, | 865 | .repeated, |
| 803 | => false, | 866 | => false, |
| ... | @@ -841,8 +904,7 @@ pub const Value = extern union { | ... | @@ -841,8 +904,7 @@ pub const Value = extern union { |
| 841 | 904 | ||
| 842 | pub const Function = struct { | 905 | pub const Function = struct { |
| 843 | base: Payload = Payload{ .tag = .function }, | 906 | base: Payload = Payload{ .tag = .function }, |
| 844 | /// Index into the `fns` array of the `ir.Module` | 907 | func: *Module.Fn, |
| 845 | index: usize, | ||
| 846 | }; | 908 | }; |
| 847 | 909 | ||
| 848 | pub const ArraySentinel0_u8_Type = struct { | 910 | pub const ArraySentinel0_u8_Type = struct { |
| ... | @@ -855,16 +917,24 @@ pub const Value = extern union { | ... | @@ -855,16 +917,24 @@ pub const Value = extern union { |
| 855 | elem_type: *Type, | 917 | elem_type: *Type, |
| 856 | }; | 918 | }; |
| 857 | 919 | ||
| 858 | pub const Ref = struct { | 920 | /// Represents a pointer to another immutable value. |
| 859 | base: Payload = Payload{ .tag = .ref }, | ||
| 860 | cell: *MemoryCell, | ||
| 861 | }; | ||
| 862 | |||
| 863 | pub const RefVal = struct { | 921 | pub const RefVal = struct { |
| 864 | base: Payload = Payload{ .tag = .ref_val }, | 922 | base: Payload = Payload{ .tag = .ref_val }, |
| 865 | val: Value, | 923 | val: Value, |
| 866 | }; | 924 | }; |
| 867 | 925 | ||
| 926 | /// Represents a pointer to a decl, not the value of the decl. | ||
| 927 | pub const DeclRef = struct { | ||
| 928 | base: Payload = Payload{ .tag = .decl_ref }, | ||
| 929 | decl: *Module.Decl, | ||
| 930 | }; | ||
| 931 | |||
| 932 | pub const ElemPtr = struct { | ||
| 933 | base: Payload = Payload{ .tag = .elem_ptr }, | ||
| 934 | array_ptr: Value, | ||
| 935 | index: usize, | ||
| 936 | }; | ||
| 937 | |||
| 868 | pub const Bytes = struct { | 938 | pub const Bytes = struct { |
| 869 | base: Payload = Payload{ .tag = .bytes }, | 939 | base: Payload = Payload{ .tag = .bytes }, |
| 870 | data: []const u8, | 940 | data: []const u8, |
| ... | @@ -890,29 +960,3 @@ pub const Value = extern union { | ... | @@ -890,29 +960,3 @@ pub const Value = extern union { |
| 890 | limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb, | 960 | limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb, |
| 891 | }; | 961 | }; |
| 892 | }; | 962 | }; |
| 893 | |||
| 894 | /// This is the heart of resource management of the Zig compiler. The Zig compiler uses | ||
| 895 | /// stop-the-world mark-and-sweep garbage collection during compilation to manage the resources | ||
| 896 | /// associated with evaluating compile-time code and semantic analysis. Each `MemoryCell` represents | ||
| 897 | /// a root. | ||
| 898 | pub const MemoryCell = struct { | ||
| 899 | parent: Parent, | ||
| 900 | contents: Value, | ||
| 901 | |||
| 902 | pub const Parent = union(enum) { | ||
| 903 | none, | ||
| 904 | struct_field: struct { | ||
| 905 | struct_base: *MemoryCell, | ||
| 906 | field_index: usize, | ||
| 907 | }, | ||
| 908 | array_elem: struct { | ||
| 909 | array_base: *MemoryCell, | ||
| 910 | elem_index: usize, | ||
| 911 | }, | ||
| 912 | union_field: *MemoryCell, | ||
| 913 | err_union_code: *MemoryCell, | ||
| 914 | err_union_payload: *MemoryCell, | ||
| 915 | optional_payload: *MemoryCell, | ||
| 916 | optional_flag: *MemoryCell, | ||
| 917 | }; | ||
| 918 | }; |
src-self-hosted/visib.zig deleted-4| ... | @@ -1,4 +0,0 @@ | ||
| 1 | pub const Visib = enum { | ||
| 2 | Private, | ||
| 3 | Pub, | ||
| 4 | }; | ||
src-self-hosted/zir.zig created+1477| ... | @@ -0,0 +1,1477 @@ | ||
| 1 | //! This file has to do with parsing and rendering the ZIR text format. | ||
| 2 | |||
| 3 | const std = @import("std"); | ||
| 4 | const mem = std.mem; | ||
| 5 | const Allocator = std.mem.Allocator; | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | const BigIntConst = std.math.big.int.Const; | ||
| 8 | const BigIntMutable = std.math.big.int.Mutable; | ||
| 9 | const Type = @import("type.zig").Type; | ||
| 10 | const Value = @import("value.zig").Value; | ||
| 11 | const TypedValue = @import("TypedValue.zig"); | ||
| 12 | const ir = @import("ir.zig"); | ||
| 13 | const IrModule = @import("Module.zig"); | ||
| 14 | |||
| 15 | /// These are instructions that correspond to the ZIR text format. See `ir.Inst` for | ||
| 16 | /// in-memory, analyzed instructions with types and values. | ||
| 17 | pub const Inst = struct { | ||
| 18 | tag: Tag, | ||
| 19 | /// Byte offset into the source. | ||
| 20 | src: usize, | ||
| 21 | name: []const u8, | ||
| 22 | |||
| 23 | /// Slice into the source of the part after the = and before the next instruction. | ||
| 24 | contents: []const u8 = &[0]u8{}, | ||
| 25 | |||
| 26 | /// These names are used directly as the instruction names in the text format. | ||
| 27 | pub const Tag = enum { | ||
| 28 | breakpoint, | ||
| 29 | call, | ||
| 30 | /// Represents a reference to a global decl by name. | ||
| 31 | /// The syntax `@foo` is equivalent to `declref("foo")`. | ||
| 32 | declref, | ||
| 33 | str, | ||
| 34 | int, | ||
| 35 | ptrtoint, | ||
| 36 | fieldptr, | ||
| 37 | deref, | ||
| 38 | as, | ||
| 39 | @"asm", | ||
| 40 | @"unreachable", | ||
| 41 | @"return", | ||
| 42 | @"fn", | ||
| 43 | @"export", | ||
| 44 | primitive, | ||
| 45 | ref, | ||
| 46 | fntype, | ||
| 47 | intcast, | ||
| 48 | bitcast, | ||
| 49 | elemptr, | ||
| 50 | add, | ||
| 51 | cmp, | ||
| 52 | condbr, | ||
| 53 | isnull, | ||
| 54 | isnonnull, | ||
| 55 | }; | ||
| 56 | |||
| 57 | pub fn TagToType(tag: Tag) type { | ||
| 58 | return switch (tag) { | ||
| 59 | .breakpoint => Breakpoint, | ||
| 60 | .call => Call, | ||
| 61 | .declref => DeclRef, | ||
| 62 | .str => Str, | ||
| 63 | .int => Int, | ||
| 64 | .ptrtoint => PtrToInt, | ||
| 65 | .fieldptr => FieldPtr, | ||
| 66 | .deref => Deref, | ||
| 67 | .as => As, | ||
| 68 | .@"asm" => Asm, | ||
| 69 | .@"unreachable" => Unreachable, | ||
| 70 | .@"return" => Return, | ||
| 71 | .@"fn" => Fn, | ||
| 72 | .@"export" => Export, | ||
| 73 | .primitive => Primitive, | ||
| 74 | .ref => Ref, | ||
| 75 | .fntype => FnType, | ||
| 76 | .intcast => IntCast, | ||
| 77 | .bitcast => BitCast, | ||
| 78 | .elemptr => ElemPtr, | ||
| 79 | .add => Add, | ||
| 80 | .cmp => Cmp, | ||
| 81 | .condbr => CondBr, | ||
| 82 | .isnull => IsNull, | ||
| 83 | .isnonnull => IsNonNull, | ||
| 84 | }; | ||
| 85 | } | ||
| 86 | |||
| 87 | pub fn cast(base: *Inst, comptime T: type) ?*T { | ||
| 88 | if (base.tag != T.base_tag) | ||
| 89 | return null; | ||
| 90 | |||
| 91 | return @fieldParentPtr(T, "base", base); | ||
| 92 | } | ||
| 93 | |||
| 94 | pub const Breakpoint = struct { | ||
| 95 | pub const base_tag = Tag.breakpoint; | ||
| 96 | base: Inst, | ||
| 97 | |||
| 98 | positionals: struct {}, | ||
| 99 | kw_args: struct {}, | ||
| 100 | }; | ||
| 101 | |||
| 102 | pub const Call = struct { | ||
| 103 | pub const base_tag = Tag.call; | ||
| 104 | base: Inst, | ||
| 105 | |||
| 106 | positionals: struct { | ||
| 107 | func: *Inst, | ||
| 108 | args: []*Inst, | ||
| 109 | }, | ||
| 110 | kw_args: struct { | ||
| 111 | modifier: std.builtin.CallOptions.Modifier = .auto, | ||
| 112 | }, | ||
| 113 | }; | ||
| 114 | |||
| 115 | pub const DeclRef = struct { | ||
| 116 | pub const base_tag = Tag.declref; | ||
| 117 | base: Inst, | ||
| 118 | |||
| 119 | positionals: struct { | ||
| 120 | name: *Inst, | ||
| 121 | }, | ||
| 122 | kw_args: struct {}, | ||
| 123 | }; | ||
| 124 | |||
| 125 | pub const Str = struct { | ||
| 126 | pub const base_tag = Tag.str; | ||
| 127 | base: Inst, | ||
| 128 | |||
| 129 | positionals: struct { | ||
| 130 | bytes: []const u8, | ||
| 131 | }, | ||
| 132 | kw_args: struct {}, | ||
| 133 | }; | ||
| 134 | |||
| 135 | pub const Int = struct { | ||
| 136 | pub const base_tag = Tag.int; | ||
| 137 | base: Inst, | ||
| 138 | |||
| 139 | positionals: struct { | ||
| 140 | int: BigIntConst, | ||
| 141 | }, | ||
| 142 | kw_args: struct {}, | ||
| 143 | }; | ||
| 144 | |||
| 145 | pub const PtrToInt = struct { | ||
| 146 | pub const base_tag = Tag.ptrtoint; | ||
| 147 | base: Inst, | ||
| 148 | |||
| 149 | positionals: struct { | ||
| 150 | ptr: *Inst, | ||
| 151 | }, | ||
| 152 | kw_args: struct {}, | ||
| 153 | }; | ||
| 154 | |||
| 155 | pub const FieldPtr = struct { | ||
| 156 | pub const base_tag = Tag.fieldptr; | ||
| 157 | base: Inst, | ||
| 158 | |||
| 159 | positionals: struct { | ||
| 160 | object_ptr: *Inst, | ||
| 161 | field_name: *Inst, | ||
| 162 | }, | ||
| 163 | kw_args: struct {}, | ||
| 164 | }; | ||
| 165 | |||
| 166 | pub const Deref = struct { | ||
| 167 | pub const base_tag = Tag.deref; | ||
| 168 | base: Inst, | ||
| 169 | |||
| 170 | positionals: struct { | ||
| 171 | ptr: *Inst, | ||
| 172 | }, | ||
| 173 | kw_args: struct {}, | ||
| 174 | }; | ||
| 175 | |||
| 176 | pub const As = struct { | ||
| 177 | pub const base_tag = Tag.as; | ||
| 178 | base: Inst, | ||
| 179 | |||
| 180 | positionals: struct { | ||
| 181 | dest_type: *Inst, | ||
| 182 | value: *Inst, | ||
| 183 | }, | ||
| 184 | kw_args: struct {}, | ||
| 185 | }; | ||
| 186 | |||
| 187 | pub const Asm = struct { | ||
| 188 | pub const base_tag = Tag.@"asm"; | ||
| 189 | base: Inst, | ||
| 190 | |||
| 191 | positionals: struct { | ||
| 192 | asm_source: *Inst, | ||
| 193 | return_type: *Inst, | ||
| 194 | }, | ||
| 195 | kw_args: struct { | ||
| 196 | @"volatile": bool = false, | ||
| 197 | output: ?*Inst = null, | ||
| 198 | inputs: []*Inst = &[0]*Inst{}, | ||
| 199 | clobbers: []*Inst = &[0]*Inst{}, | ||
| 200 | args: []*Inst = &[0]*Inst{}, | ||
| 201 | }, | ||
| 202 | }; | ||
| 203 | |||
| 204 | pub const Unreachable = struct { | ||
| 205 | pub const base_tag = Tag.@"unreachable"; | ||
| 206 | base: Inst, | ||
| 207 | |||
| 208 | positionals: struct {}, | ||
| 209 | kw_args: struct {}, | ||
| 210 | }; | ||
| 211 | |||
| 212 | pub const Return = struct { | ||
| 213 | pub const base_tag = Tag.@"return"; | ||
| 214 | base: Inst, | ||
| 215 | |||
| 216 | positionals: struct {}, | ||
| 217 | kw_args: struct {}, | ||
| 218 | }; | ||
| 219 | |||
| 220 | pub const Fn = struct { | ||
| 221 | pub const base_tag = Tag.@"fn"; | ||
| 222 | base: Inst, | ||
| 223 | |||
| 224 | positionals: struct { | ||
| 225 | fn_type: *Inst, | ||
| 226 | body: Module.Body, | ||
| 227 | }, | ||
| 228 | kw_args: struct {}, | ||
| 229 | }; | ||
| 230 | |||
| 231 | pub const Export = struct { | ||
| 232 | pub const base_tag = Tag.@"export"; | ||
| 233 | base: Inst, | ||
| 234 | |||
| 235 | positionals: struct { | ||
| 236 | symbol_name: *Inst, | ||
| 237 | value: *Inst, | ||
| 238 | }, | ||
| 239 | kw_args: struct {}, | ||
| 240 | }; | ||
| 241 | |||
| 242 | pub const Ref = struct { | ||
| 243 | pub const base_tag = Tag.ref; | ||
| 244 | base: Inst, | ||
| 245 | |||
| 246 | positionals: struct { | ||
| 247 | operand: *Inst, | ||
| 248 | }, | ||
| 249 | kw_args: struct {}, | ||
| 250 | }; | ||
| 251 | |||
| 252 | pub const Primitive = struct { | ||
| 253 | pub const base_tag = Tag.primitive; | ||
| 254 | base: Inst, | ||
| 255 | |||
| 256 | positionals: struct { | ||
| 257 | tag: BuiltinType, | ||
| 258 | }, | ||
| 259 | kw_args: struct {}, | ||
| 260 | |||
| 261 | pub const BuiltinType = enum { | ||
| 262 | isize, | ||
| 263 | usize, | ||
| 264 | c_short, | ||
| 265 | c_ushort, | ||
| 266 | c_int, | ||
| 267 | c_uint, | ||
| 268 | c_long, | ||
| 269 | c_ulong, | ||
| 270 | c_longlong, | ||
| 271 | c_ulonglong, | ||
| 272 | c_longdouble, | ||
| 273 | c_void, | ||
| 274 | f16, | ||
| 275 | f32, | ||
| 276 | f64, | ||
| 277 | f128, | ||
| 278 | bool, | ||
| 279 | void, | ||
| 280 | noreturn, | ||
| 281 | type, | ||
| 282 | anyerror, | ||
| 283 | comptime_int, | ||
| 284 | comptime_float, | ||
| 285 | |||
| 286 | pub fn toType(self: BuiltinType) Type { | ||
| 287 | return switch (self) { | ||
| 288 | .isize => Type.initTag(.isize), | ||
| 289 | .usize => Type.initTag(.usize), | ||
| 290 | .c_short => Type.initTag(.c_short), | ||
| 291 | .c_ushort => Type.initTag(.c_ushort), | ||
| 292 | .c_int => Type.initTag(.c_int), | ||
| 293 | .c_uint => Type.initTag(.c_uint), | ||
| 294 | .c_long => Type.initTag(.c_long), | ||
| 295 | .c_ulong => Type.initTag(.c_ulong), | ||
| 296 | .c_longlong => Type.initTag(.c_longlong), | ||
| 297 | .c_ulonglong => Type.initTag(.c_ulonglong), | ||
| 298 | .c_longdouble => Type.initTag(.c_longdouble), | ||
| 299 | .c_void => Type.initTag(.c_void), | ||
| 300 | .f16 => Type.initTag(.f16), | ||
| 301 | .f32 => Type.initTag(.f32), | ||
| 302 | .f64 => Type.initTag(.f64), | ||
| 303 | .f128 => Type.initTag(.f128), | ||
| 304 | .bool => Type.initTag(.bool), | ||
| 305 | .void => Type.initTag(.void), | ||
| 306 | .noreturn => Type.initTag(.noreturn), | ||
| 307 | .type => Type.initTag(.type), | ||
| 308 | .anyerror => Type.initTag(.anyerror), | ||
| 309 | .comptime_int => Type.initTag(.comptime_int), | ||
| 310 | .comptime_float => Type.initTag(.comptime_float), | ||
| 311 | }; | ||
| 312 | } | ||
| 313 | }; | ||
| 314 | }; | ||
| 315 | |||
| 316 | pub const FnType = struct { | ||
| 317 | pub const base_tag = Tag.fntype; | ||
| 318 | base: Inst, | ||
| 319 | |||
| 320 | positionals: struct { | ||
| 321 | param_types: []*Inst, | ||
| 322 | return_type: *Inst, | ||
| 323 | }, | ||
| 324 | kw_args: struct { | ||
| 325 | cc: std.builtin.CallingConvention = .Unspecified, | ||
| 326 | }, | ||
| 327 | }; | ||
| 328 | |||
| 329 | pub const IntCast = struct { | ||
| 330 | pub const base_tag = Tag.intcast; | ||
| 331 | base: Inst, | ||
| 332 | |||
| 333 | positionals: struct { | ||
| 334 | dest_type: *Inst, | ||
| 335 | value: *Inst, | ||
| 336 | }, | ||
| 337 | kw_args: struct {}, | ||
| 338 | }; | ||
| 339 | |||
| 340 | pub const BitCast = struct { | ||
| 341 | pub const base_tag = Tag.bitcast; | ||
| 342 | base: Inst, | ||
| 343 | |||
| 344 | positionals: struct { | ||
| 345 | dest_type: *Inst, | ||
| 346 | operand: *Inst, | ||
| 347 | }, | ||
| 348 | kw_args: struct {}, | ||
| 349 | }; | ||
| 350 | |||
| 351 | pub const ElemPtr = struct { | ||
| 352 | pub const base_tag = Tag.elemptr; | ||
| 353 | base: Inst, | ||
| 354 | |||
| 355 | positionals: struct { | ||
| 356 | array_ptr: *Inst, | ||
| 357 | index: *Inst, | ||
| 358 | }, | ||
| 359 | kw_args: struct {}, | ||
| 360 | }; | ||
| 361 | |||
| 362 | pub const Add = struct { | ||
| 363 | pub const base_tag = Tag.add; | ||
| 364 | base: Inst, | ||
| 365 | |||
| 366 | positionals: struct { | ||
| 367 | lhs: *Inst, | ||
| 368 | rhs: *Inst, | ||
| 369 | }, | ||
| 370 | kw_args: struct {}, | ||
| 371 | }; | ||
| 372 | |||
| 373 | pub const Cmp = struct { | ||
| 374 | pub const base_tag = Tag.cmp; | ||
| 375 | base: Inst, | ||
| 376 | |||
| 377 | positionals: struct { | ||
| 378 | lhs: *Inst, | ||
| 379 | op: std.math.CompareOperator, | ||
| 380 | rhs: *Inst, | ||
| 381 | }, | ||
| 382 | kw_args: struct {}, | ||
| 383 | }; | ||
| 384 | |||
| 385 | pub const CondBr = struct { | ||
| 386 | pub const base_tag = Tag.condbr; | ||
| 387 | base: Inst, | ||
| 388 | |||
| 389 | positionals: struct { | ||
| 390 | condition: *Inst, | ||
| 391 | true_body: Module.Body, | ||
| 392 | false_body: Module.Body, | ||
| 393 | }, | ||
| 394 | kw_args: struct {}, | ||
| 395 | }; | ||
| 396 | |||
| 397 | pub const IsNull = struct { | ||
| 398 | pub const base_tag = Tag.isnull; | ||
| 399 | base: Inst, | ||
| 400 | |||
| 401 | positionals: struct { | ||
| 402 | operand: *Inst, | ||
| 403 | }, | ||
| 404 | kw_args: struct {}, | ||
| 405 | }; | ||
| 406 | |||
| 407 | pub const IsNonNull = struct { | ||
| 408 | pub const base_tag = Tag.isnonnull; | ||
| 409 | base: Inst, | ||
| 410 | |||
| 411 | positionals: struct { | ||
| 412 | operand: *Inst, | ||
| 413 | }, | ||
| 414 | kw_args: struct {}, | ||
| 415 | }; | ||
| 416 | }; | ||
| 417 | |||
| 418 | pub const ErrorMsg = struct { | ||
| 419 | byte_offset: usize, | ||
| 420 | msg: []const u8, | ||
| 421 | }; | ||
| 422 | |||
| 423 | pub const Module = struct { | ||
| 424 | decls: []*Inst, | ||
| 425 | arena: std.heap.ArenaAllocator, | ||
| 426 | error_msg: ?ErrorMsg = null, | ||
| 427 | |||
| 428 | pub const Body = struct { | ||
| 429 | instructions: []*Inst, | ||
| 430 | }; | ||
| 431 | |||
| 432 | pub fn deinit(self: *Module, allocator: *Allocator) void { | ||
| 433 | allocator.free(self.decls); | ||
| 434 | self.arena.deinit(); | ||
| 435 | self.* = undefined; | ||
| 436 | } | ||
| 437 | |||
| 438 | /// This is a debugging utility for rendering the tree to stderr. | ||
| 439 | pub fn dump(self: Module) void { | ||
| 440 | self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {}; | ||
| 441 | } | ||
| 442 | |||
| 443 | const InstPtrTable = std.AutoHashMap(*Inst, struct { index: usize, fn_body: ?*Module.Body }); | ||
| 444 | |||
| 445 | /// The allocator is used for temporary storage, but this function always returns | ||
| 446 | /// with no resources allocated. | ||
| 447 | pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void { | ||
| 448 | // First, build a map of *Inst to @ or % indexes | ||
| 449 | var inst_table = InstPtrTable.init(allocator); | ||
| 450 | defer inst_table.deinit(); | ||
| 451 | |||
| 452 | try inst_table.ensureCapacity(self.decls.len); | ||
| 453 | |||
| 454 | for (self.decls) |decl, decl_i| { | ||
| 455 | try inst_table.putNoClobber(decl, .{ .index = decl_i, .fn_body = null }); | ||
| 456 | |||
| 457 | if (decl.cast(Inst.Fn)) |fn_inst| { | ||
| 458 | for (fn_inst.positionals.body.instructions) |inst, inst_i| { | ||
| 459 | try inst_table.putNoClobber(inst, .{ .index = inst_i, .fn_body = &fn_inst.positionals.body }); | ||
| 460 | } | ||
| 461 | } | ||
| 462 | } | ||
| 463 | |||
| 464 | for (self.decls) |decl, i| { | ||
| 465 | try stream.print("@{} ", .{i}); | ||
| 466 | try self.writeInstToStream(stream, decl, &inst_table); | ||
| 467 | try stream.writeByte('\n'); | ||
| 468 | } | ||
| 469 | } | ||
| 470 | |||
| 471 | fn writeInstToStream( | ||
| 472 | self: Module, | ||
| 473 | stream: var, | ||
| 474 | decl: *Inst, | ||
| 475 | inst_table: *const InstPtrTable, | ||
| 476 | ) @TypeOf(stream).Error!void { | ||
| 477 | // TODO I tried implementing this with an inline for loop and hit a compiler bug | ||
| 478 | switch (decl.tag) { | ||
| 479 | .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, decl, inst_table), | ||
| 480 | .call => return self.writeInstToStreamGeneric(stream, .call, decl, inst_table), | ||
| 481 | .declref => return self.writeInstToStreamGeneric(stream, .declref, decl, inst_table), | ||
| 482 | .str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table), | ||
| 483 | .int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table), | ||
| 484 | .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table), | ||
| 485 | .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, decl, inst_table), | ||
| 486 | .deref => return self.writeInstToStreamGeneric(stream, .deref, decl, inst_table), | ||
| 487 | .as => return self.writeInstToStreamGeneric(stream, .as, decl, inst_table), | ||
| 488 | .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", decl, inst_table), | ||
| 489 | .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", decl, inst_table), | ||
| 490 | .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", decl, inst_table), | ||
| 491 | .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table), | ||
| 492 | .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", decl, inst_table), | ||
| 493 | .ref => return self.writeInstToStreamGeneric(stream, .ref, decl, inst_table), | ||
| 494 | .primitive => return self.writeInstToStreamGeneric(stream, .primitive, decl, inst_table), | ||
| 495 | .fntype => return self.writeInstToStreamGeneric(stream, .fntype, decl, inst_table), | ||
| 496 | .intcast => return self.writeInstToStreamGeneric(stream, .intcast, decl, inst_table), | ||
| 497 | .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, decl, inst_table), | ||
| 498 | .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, decl, inst_table), | ||
| 499 | .add => return self.writeInstToStreamGeneric(stream, .add, decl, inst_table), | ||
| 500 | .cmp => return self.writeInstToStreamGeneric(stream, .cmp, decl, inst_table), | ||
| 501 | .condbr => return self.writeInstToStreamGeneric(stream, .condbr, decl, inst_table), | ||
| 502 | .isnull => return self.writeInstToStreamGeneric(stream, .isnull, decl, inst_table), | ||
| 503 | .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, decl, inst_table), | ||
| 504 | } | ||
| 505 | } | ||
| 506 | |||
| 507 | fn writeInstToStreamGeneric( | ||
| 508 | self: Module, | ||
| 509 | stream: var, | ||
| 510 | comptime inst_tag: Inst.Tag, | ||
| 511 | base: *Inst, | ||
| 512 | inst_table: *const InstPtrTable, | ||
| 513 | ) !void { | ||
| 514 | const SpecificInst = Inst.TagToType(inst_tag); | ||
| 515 | const inst = @fieldParentPtr(SpecificInst, "base", base); | ||
| 516 | const Positionals = @TypeOf(inst.positionals); | ||
| 517 | try stream.writeAll("= " ++ @tagName(inst_tag) ++ "("); | ||
| 518 | const pos_fields = @typeInfo(Positionals).Struct.fields; | ||
| 519 | inline for (pos_fields) |arg_field, i| { | ||
| 520 | if (i != 0) { | ||
| 521 | try stream.writeAll(", "); | ||
| 522 | } | ||
| 523 | try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table); | ||
| 524 | } | ||
| 525 | |||
| 526 | comptime var need_comma = pos_fields.len != 0; | ||
| 527 | const KW_Args = @TypeOf(inst.kw_args); | ||
| 528 | inline for (@typeInfo(KW_Args).Struct.fields) |arg_field, i| { | ||
| 529 | if (@typeInfo(arg_field.field_type) == .Optional) { | ||
| 530 | if (@field(inst.kw_args, arg_field.name)) |non_optional| { | ||
| 531 | if (need_comma) try stream.writeAll(", "); | ||
| 532 | try stream.print("{}=", .{arg_field.name}); | ||
| 533 | try self.writeParamToStream(stream, non_optional, inst_table); | ||
| 534 | need_comma = true; | ||
| 535 | } | ||
| 536 | } else { | ||
| 537 | if (need_comma) try stream.writeAll(", "); | ||
| 538 | try stream.print("{}=", .{arg_field.name}); | ||
| 539 | try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name), inst_table); | ||
| 540 | need_comma = true; | ||
| 541 | } | ||
| 542 | } | ||
| 543 | |||
| 544 | try stream.writeByte(')'); | ||
| 545 | } | ||
| 546 | |||
| 547 | fn writeParamToStream(self: Module, stream: var, param: var, inst_table: *const InstPtrTable) !void { | ||
| 548 | if (@typeInfo(@TypeOf(param)) == .Enum) { | ||
| 549 | return stream.writeAll(@tagName(param)); | ||
| 550 | } | ||
| 551 | switch (@TypeOf(param)) { | ||
| 552 | *Inst => return self.writeInstParamToStream(stream, param, inst_table), | ||
| 553 | []*Inst => { | ||
| 554 | try stream.writeByte('['); | ||
| 555 | for (param) |inst, i| { | ||
| 556 | if (i != 0) { | ||
| 557 | try stream.writeAll(", "); | ||
| 558 | } | ||
| 559 | try self.writeInstParamToStream(stream, inst, inst_table); | ||
| 560 | } | ||
| 561 | try stream.writeByte(']'); | ||
| 562 | }, | ||
| 563 | Module.Body => { | ||
| 564 | try stream.writeAll("{\n"); | ||
| 565 | for (param.instructions) |inst, i| { | ||
| 566 | try stream.print(" %{} ", .{i}); | ||
| 567 | try self.writeInstToStream(stream, inst, inst_table); | ||
| 568 | try stream.writeByte('\n'); | ||
| 569 | } | ||
| 570 | try stream.writeByte('}'); | ||
| 571 | }, | ||
| 572 | bool => return stream.writeByte("01"[@boolToInt(param)]), | ||
| 573 | []u8, []const u8 => return std.zig.renderStringLiteral(param, stream), | ||
| 574 | BigIntConst => return stream.print("{}", .{param}), | ||
| 575 | else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), | ||
| 576 | } | ||
| 577 | } | ||
| 578 | |||
| 579 | fn writeInstParamToStream(self: Module, stream: var, inst: *Inst, inst_table: *const InstPtrTable) !void { | ||
| 580 | const info = inst_table.getValue(inst).?; | ||
| 581 | const prefix = if (info.fn_body == null) "@" else "%"; | ||
| 582 | try stream.print("{}{}", .{ prefix, info.index }); | ||
| 583 | } | ||
| 584 | }; | ||
| 585 | |||
| 586 | pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module { | ||
| 587 | var global_name_map = std.StringHashMap(usize).init(allocator); | ||
| 588 | defer global_name_map.deinit(); | ||
| 589 | |||
| 590 | var parser: Parser = .{ | ||
| 591 | .allocator = allocator, | ||
| 592 | .arena = std.heap.ArenaAllocator.init(allocator), | ||
| 593 | .i = 0, | ||
| 594 | .source = source, | ||
| 595 | .global_name_map = &global_name_map, | ||
| 596 | .decls = .{}, | ||
| 597 | .unnamed_index = 0, | ||
| 598 | }; | ||
| 599 | errdefer parser.arena.deinit(); | ||
| 600 | |||
| 601 | parser.parseRoot() catch |err| switch (err) { | ||
| 602 | error.ParseFailure => { | ||
| 603 | assert(parser.error_msg != null); | ||
| 604 | }, | ||
| 605 | else => |e| return e, | ||
| 606 | }; | ||
| 607 | |||
| 608 | return Module{ | ||
| 609 | .decls = parser.decls.toOwnedSlice(allocator), | ||
| 610 | .arena = parser.arena, | ||
| 611 | .error_msg = parser.error_msg, | ||
| 612 | }; | ||
| 613 | } | ||
| 614 | |||
| 615 | const Parser = struct { | ||
| 616 | allocator: *Allocator, | ||
| 617 | arena: std.heap.ArenaAllocator, | ||
| 618 | i: usize, | ||
| 619 | source: [:0]const u8, | ||
| 620 | decls: std.ArrayListUnmanaged(*Inst), | ||
| 621 | global_name_map: *std.StringHashMap(usize), | ||
| 622 | error_msg: ?ErrorMsg = null, | ||
| 623 | unnamed_index: usize, | ||
| 624 | |||
| 625 | const Body = struct { | ||
| 626 | instructions: std.ArrayList(*Inst), | ||
| 627 | name_map: std.StringHashMap(usize), | ||
| 628 | }; | ||
| 629 | |||
| 630 | fn parseBody(self: *Parser) !Module.Body { | ||
| 631 | var body_context = Body{ | ||
| 632 | .instructions = std.ArrayList(*Inst).init(self.allocator), | ||
| 633 | .name_map = std.StringHashMap(usize).init(self.allocator), | ||
| 634 | }; | ||
| 635 | defer body_context.instructions.deinit(); | ||
| 636 | defer body_context.name_map.deinit(); | ||
| 637 | |||
| 638 | try requireEatBytes(self, "{"); | ||
| 639 | skipSpace(self); | ||
| 640 | |||
| 641 | while (true) : (self.i += 1) switch (self.source[self.i]) { | ||
| 642 | ';' => _ = try skipToAndOver(self, '\n'), | ||
| 643 | '%' => { | ||
| 644 | self.i += 1; | ||
| 645 | const ident = try skipToAndOver(self, ' '); | ||
| 646 | skipSpace(self); | ||
| 647 | try requireEatBytes(self, "="); | ||
| 648 | skipSpace(self); | ||
| 649 | const inst = try parseInstruction(self, &body_context, ident); | ||
| 650 | const ident_index = body_context.instructions.items.len; | ||
| 651 | if (try body_context.name_map.put(ident, ident_index)) |_| { | ||
| 652 | return self.fail("redefinition of identifier '{}'", .{ident}); | ||
| 653 | } | ||
| 654 | try body_context.instructions.append(inst); | ||
| 655 | continue; | ||
| 656 | }, | ||
| 657 | ' ', '\n' => continue, | ||
| 658 | '}' => { | ||
| 659 | self.i += 1; | ||
| 660 | break; | ||
| 661 | }, | ||
| 662 | else => |byte| return self.failByte(byte), | ||
| 663 | }; | ||
| 664 | |||
| 665 | // Move the instructions to the arena | ||
| 666 | const instrs = try self.arena.allocator.alloc(*Inst, body_context.instructions.items.len); | ||
| 667 | mem.copy(*Inst, instrs, body_context.instructions.items); | ||
| 668 | return Module.Body{ .instructions = instrs }; | ||
| 669 | } | ||
| 670 | |||
| 671 | fn parseStringLiteral(self: *Parser) ![]u8 { | ||
| 672 | const start = self.i; | ||
| 673 | try self.requireEatBytes("\""); | ||
| 674 | |||
| 675 | while (true) : (self.i += 1) switch (self.source[self.i]) { | ||
| 676 | '"' => { | ||
| 677 | self.i += 1; | ||
| 678 | const span = self.source[start..self.i]; | ||
| 679 | var bad_index: usize = undefined; | ||
| 680 | const parsed = std.zig.parseStringLiteral(&self.arena.allocator, span, &bad_index) catch |err| switch (err) { | ||
| 681 | error.InvalidCharacter => { | ||
| 682 | self.i = start + bad_index; | ||
| 683 | const bad_byte = self.source[self.i]; | ||
| 684 | return self.fail("invalid string literal character: '{c}'\n", .{bad_byte}); | ||
| 685 | }, | ||
| 686 | else => |e| return e, | ||
| 687 | }; | ||
| 688 | return parsed; | ||
| 689 | }, | ||
| 690 | '\\' => { | ||
| 691 | self.i += 1; | ||
| 692 | continue; | ||
| 693 | }, | ||
| 694 | 0 => return self.failByte(0), | ||
| 695 | else => continue, | ||
| 696 | }; | ||
| 697 | } | ||
| 698 | |||
| 699 | fn parseIntegerLiteral(self: *Parser) !BigIntConst { | ||
| 700 | const start = self.i; | ||
| 701 | if (self.source[self.i] == '-') self.i += 1; | ||
| 702 | while (true) : (self.i += 1) switch (self.source[self.i]) { | ||
| 703 | '0'...'9' => continue, | ||
| 704 | else => break, | ||
| 705 | }; | ||
| 706 | const number_text = self.source[start..self.i]; | ||
| 707 | const base = 10; | ||
| 708 | // TODO reuse the same array list for this | ||
| 709 | const limbs_buffer_len = std.math.big.int.calcSetStringLimbsBufferLen(base, number_text.len); | ||
| 710 | const limbs_buffer = try self.allocator.alloc(std.math.big.Limb, limbs_buffer_len); | ||
| 711 | defer self.allocator.free(limbs_buffer); | ||
| 712 | const limb_len = std.math.big.int.calcSetStringLimbCount(base, number_text.len); | ||
| 713 | const limbs = try self.arena.allocator.alloc(std.math.big.Limb, limb_len); | ||
| 714 | var result = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | ||
| 715 | result.setString(base, number_text, limbs_buffer, self.allocator) catch |err| switch (err) { | ||
| 716 | error.InvalidCharacter => { | ||
| 717 | self.i = start; | ||
| 718 | return self.fail("invalid digit in integer literal", .{}); | ||
| 719 | }, | ||
| 720 | }; | ||
| 721 | return result.toConst(); | ||
| 722 | } | ||
| 723 | |||
| 724 | fn parseRoot(self: *Parser) !void { | ||
| 725 | // The IR format is designed so that it can be tokenized and parsed at the same time. | ||
| 726 | while (true) { | ||
| 727 | switch (self.source[self.i]) { | ||
| 728 | ';' => _ = try skipToAndOver(self, '\n'), | ||
| 729 | '@' => { | ||
| 730 | self.i += 1; | ||
| 731 | const ident = try skipToAndOver(self, ' '); | ||
| 732 | skipSpace(self); | ||
| 733 | try requireEatBytes(self, "="); | ||
| 734 | skipSpace(self); | ||
| 735 | const inst = try parseInstruction(self, null, ident); | ||
| 736 | const ident_index = self.decls.items.len; | ||
| 737 | if (try self.global_name_map.put(ident, ident_index)) |_| { | ||
| 738 | return self.fail("redefinition of identifier '{}'", .{ident}); | ||
| 739 | } | ||
| 740 | try self.decls.append(self.allocator, inst); | ||
| 741 | }, | ||
| 742 | ' ', '\n' => self.i += 1, | ||
| 743 | 0 => break, | ||
| 744 | else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}), | ||
| 745 | } | ||
| 746 | } | ||
| 747 | } | ||
| 748 | |||
| 749 | fn eatByte(self: *Parser, byte: u8) bool { | ||
| 750 | if (self.source[self.i] != byte) return false; | ||
| 751 | self.i += 1; | ||
| 752 | return true; | ||
| 753 | } | ||
| 754 | |||
| 755 | fn skipSpace(self: *Parser) void { | ||
| 756 | while (self.source[self.i] == ' ' or self.source[self.i] == '\n') { | ||
| 757 | self.i += 1; | ||
| 758 | } | ||
| 759 | } | ||
| 760 | |||
| 761 | fn requireEatBytes(self: *Parser, bytes: []const u8) !void { | ||
| 762 | const start = self.i; | ||
| 763 | for (bytes) |byte| { | ||
| 764 | if (self.source[self.i] != byte) { | ||
| 765 | self.i = start; | ||
| 766 | return self.fail("expected '{}'", .{bytes}); | ||
| 767 | } | ||
| 768 | self.i += 1; | ||
| 769 | } | ||
| 770 | } | ||
| 771 | |||
| 772 | fn skipToAndOver(self: *Parser, byte: u8) ![]const u8 { | ||
| 773 | const start_i = self.i; | ||
| 774 | while (self.source[self.i] != 0) : (self.i += 1) { | ||
| 775 | if (self.source[self.i] == byte) { | ||
| 776 | const result = self.source[start_i..self.i]; | ||
| 777 | self.i += 1; | ||
| 778 | return result; | ||
| 779 | } | ||
| 780 | } | ||
| 781 | return self.fail("unexpected EOF", .{}); | ||
| 782 | } | ||
| 783 | |||
| 784 | /// ParseFailure is an internal error code; handled in `parse`. | ||
| 785 | const InnerError = error{ ParseFailure, OutOfMemory }; | ||
| 786 | |||
| 787 | fn failByte(self: *Parser, byte: u8) InnerError { | ||
| 788 | if (byte == 0) { | ||
| 789 | return self.fail("unexpected EOF", .{}); | ||
| 790 | } else { | ||
| 791 | return self.fail("unexpected byte: '{c}'", .{byte}); | ||
| 792 | } | ||
| 793 | } | ||
| 794 | |||
| 795 | fn fail(self: *Parser, comptime format: []const u8, args: var) InnerError { | ||
| 796 | @setCold(true); | ||
| 797 | self.error_msg = ErrorMsg{ | ||
| 798 | .byte_offset = self.i, | ||
| 799 | .msg = try std.fmt.allocPrint(&self.arena.allocator, format, args), | ||
| 800 | }; | ||
| 801 | return error.ParseFailure; | ||
| 802 | } | ||
| 803 | |||
| 804 | fn parseInstruction(self: *Parser, body_ctx: ?*Body, name: []const u8) InnerError!*Inst { | ||
| 805 | const contents_start = self.i; | ||
| 806 | const fn_name = try skipToAndOver(self, '('); | ||
| 807 | inline for (@typeInfo(Inst.Tag).Enum.fields) |field| { | ||
| 808 | if (mem.eql(u8, field.name, fn_name)) { | ||
| 809 | const tag = @field(Inst.Tag, field.name); | ||
| 810 | return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx, name, contents_start); | ||
| 811 | } | ||
| 812 | } | ||
| 813 | return self.fail("unknown instruction '{}'", .{fn_name}); | ||
| 814 | } | ||
| 815 | |||
| 816 | fn parseInstructionGeneric( | ||
| 817 | self: *Parser, | ||
| 818 | comptime fn_name: []const u8, | ||
| 819 | comptime InstType: type, | ||
| 820 | body_ctx: ?*Body, | ||
| 821 | inst_name: []const u8, | ||
| 822 | contents_start: usize, | ||
| 823 | ) InnerError!*Inst { | ||
| 824 | const inst_specific = try self.arena.allocator.create(InstType); | ||
| 825 | inst_specific.base = .{ | ||
| 826 | .name = inst_name, | ||
| 827 | .src = self.i, | ||
| 828 | .tag = InstType.base_tag, | ||
| 829 | }; | ||
| 830 | |||
| 831 | if (@hasField(InstType, "ty")) { | ||
| 832 | inst_specific.ty = opt_type orelse { | ||
| 833 | return self.fail("instruction '" ++ fn_name ++ "' requires type", .{}); | ||
| 834 | }; | ||
| 835 | } | ||
| 836 | |||
| 837 | const Positionals = @TypeOf(inst_specific.positionals); | ||
| 838 | inline for (@typeInfo(Positionals).Struct.fields) |arg_field| { | ||
| 839 | if (self.source[self.i] == ',') { | ||
| 840 | self.i += 1; | ||
| 841 | skipSpace(self); | ||
| 842 | } else if (self.source[self.i] == ')') { | ||
| 843 | return self.fail("expected positional parameter '{}'", .{arg_field.name}); | ||
| 844 | } | ||
| 845 | @field(inst_specific.positionals, arg_field.name) = try parseParameterGeneric( | ||
| 846 | self, | ||
| 847 | arg_field.field_type, | ||
| 848 | body_ctx, | ||
| 849 | ); | ||
| 850 | skipSpace(self); | ||
| 851 | } | ||
| 852 | |||
| 853 | const KW_Args = @TypeOf(inst_specific.kw_args); | ||
| 854 | inst_specific.kw_args = .{}; // assign defaults | ||
| 855 | skipSpace(self); | ||
| 856 | while (eatByte(self, ',')) { | ||
| 857 | skipSpace(self); | ||
| 858 | const name = try skipToAndOver(self, '='); | ||
| 859 | inline for (@typeInfo(KW_Args).Struct.fields) |arg_field| { | ||
| 860 | const field_name = arg_field.name; | ||
| 861 | if (mem.eql(u8, name, field_name)) { | ||
| 862 | const NonOptional = switch (@typeInfo(arg_field.field_type)) { | ||
| 863 | .Optional => |info| info.child, | ||
| 864 | else => arg_field.field_type, | ||
| 865 | }; | ||
| 866 | @field(inst_specific.kw_args, field_name) = try parseParameterGeneric(self, NonOptional, body_ctx); | ||
| 867 | break; | ||
| 868 | } | ||
| 869 | } else { | ||
| 870 | return self.fail("unrecognized keyword parameter: '{}'", .{name}); | ||
| 871 | } | ||
| 872 | skipSpace(self); | ||
| 873 | } | ||
| 874 | try requireEatBytes(self, ")"); | ||
| 875 | |||
| 876 | inst_specific.base.contents = self.source[contents_start..self.i]; | ||
| 877 | |||
| 878 | return &inst_specific.base; | ||
| 879 | } | ||
| 880 | |||
| 881 | fn parseParameterGeneric(self: *Parser, comptime T: type, body_ctx: ?*Body) !T { | ||
| 882 | if (@typeInfo(T) == .Enum) { | ||
| 883 | const start = self.i; | ||
| 884 | while (true) : (self.i += 1) switch (self.source[self.i]) { | ||
| 885 | ' ', '\n', ',', ')' => { | ||
| 886 | const enum_name = self.source[start..self.i]; | ||
| 887 | return std.meta.stringToEnum(T, enum_name) orelse { | ||
| 888 | return self.fail("tag '{}' not a member of enum '{}'", .{ enum_name, @typeName(T) }); | ||
| 889 | }; | ||
| 890 | }, | ||
| 891 | 0 => return self.failByte(0), | ||
| 892 | else => continue, | ||
| 893 | }; | ||
| 894 | } | ||
| 895 | switch (T) { | ||
| 896 | Module.Body => return parseBody(self), | ||
| 897 | bool => { | ||
| 898 | const bool_value = switch (self.source[self.i]) { | ||
| 899 | '0' => false, | ||
| 900 | '1' => true, | ||
| 901 | else => |byte| return self.fail("expected '0' or '1' for boolean value, found {c}", .{byte}), | ||
| 902 | }; | ||
| 903 | self.i += 1; | ||
| 904 | return bool_value; | ||
| 905 | }, | ||
| 906 | []*Inst => { | ||
| 907 | try requireEatBytes(self, "["); | ||
| 908 | skipSpace(self); | ||
| 909 | if (eatByte(self, ']')) return &[0]*Inst{}; | ||
| 910 | |||
| 911 | var instructions = std.ArrayList(*Inst).init(&self.arena.allocator); | ||
| 912 | while (true) { | ||
| 913 | skipSpace(self); | ||
| 914 | try instructions.append(try parseParameterInst(self, body_ctx)); | ||
| 915 | skipSpace(self); | ||
| 916 | if (!eatByte(self, ',')) break; | ||
| 917 | } | ||
| 918 | try requireEatBytes(self, "]"); | ||
| 919 | return instructions.toOwnedSlice(); | ||
| 920 | }, | ||
| 921 | *Inst => return parseParameterInst(self, body_ctx), | ||
| 922 | []u8, []const u8 => return self.parseStringLiteral(), | ||
| 923 | BigIntConst => return self.parseIntegerLiteral(), | ||
| 924 | else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), | ||
| 925 | } | ||
| 926 | return self.fail("TODO parse parameter {}", .{@typeName(T)}); | ||
| 927 | } | ||
| 928 | |||
| 929 | fn parseParameterInst(self: *Parser, body_ctx: ?*Body) !*Inst { | ||
| 930 | const local_ref = switch (self.source[self.i]) { | ||
| 931 | '@' => false, | ||
| 932 | '%' => true, | ||
| 933 | else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}), | ||
| 934 | }; | ||
| 935 | const map = if (local_ref) | ||
| 936 | if (body_ctx) |bc| | ||
| 937 | &bc.name_map | ||
| 938 | else | ||
| 939 | return self.fail("referencing a % instruction in global scope", .{}) | ||
| 940 | else | ||
| 941 | self.global_name_map; | ||
| 942 | |||
| 943 | self.i += 1; | ||
| 944 | const name_start = self.i; | ||
| 945 | while (true) : (self.i += 1) switch (self.source[self.i]) { | ||
| 946 | 0, ' ', '\n', ',', ')', ']' => break, | ||
| 947 | else => continue, | ||
| 948 | }; | ||
| 949 | const ident = self.source[name_start..self.i]; | ||
| 950 | const kv = map.get(ident) orelse { | ||
| 951 | const bad_name = self.source[name_start - 1 .. self.i]; | ||
| 952 | const src = name_start - 1; | ||
| 953 | if (local_ref) { | ||
| 954 | self.i = src; | ||
| 955 | return self.fail("unrecognized identifier: {}", .{bad_name}); | ||
| 956 | } else { | ||
| 957 | const name = try self.arena.allocator.create(Inst.Str); | ||
| 958 | name.* = .{ | ||
| 959 | .base = .{ | ||
| 960 | .name = try self.generateName(), | ||
| 961 | .src = src, | ||
| 962 | .tag = Inst.Str.base_tag, | ||
| 963 | }, | ||
| 964 | .positionals = .{ .bytes = ident }, | ||
| 965 | .kw_args = .{}, | ||
| 966 | }; | ||
| 967 | const declref = try self.arena.allocator.create(Inst.DeclRef); | ||
| 968 | declref.* = .{ | ||
| 969 | .base = .{ | ||
| 970 | .name = try self.generateName(), | ||
| 971 | .src = src, | ||
| 972 | .tag = Inst.DeclRef.base_tag, | ||
| 973 | }, | ||
| 974 | .positionals = .{ .name = &name.base }, | ||
| 975 | .kw_args = .{}, | ||
| 976 | }; | ||
| 977 | return &declref.base; | ||
| 978 | } | ||
| 979 | }; | ||
| 980 | if (local_ref) { | ||
| 981 | return body_ctx.?.instructions.items[kv.value]; | ||
| 982 | } else { | ||
| 983 | return self.decls.items[kv.value]; | ||
| 984 | } | ||
| 985 | } | ||
| 986 | |||
| 987 | fn generateName(self: *Parser) ![]u8 { | ||
| 988 | const result = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${}", .{self.unnamed_index}); | ||
| 989 | self.unnamed_index += 1; | ||
| 990 | return result; | ||
| 991 | } | ||
| 992 | }; | ||
| 993 | |||
| 994 | pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { | ||
| 995 | var ctx: EmitZIR = .{ | ||
| 996 | .allocator = allocator, | ||
| 997 | .decls = .{}, | ||
| 998 | .decl_table = std.AutoHashMap(*ir.Inst, *Inst).init(allocator), | ||
| 999 | .arena = std.heap.ArenaAllocator.init(allocator), | ||
| 1000 | .old_module = &old_module, | ||
| 1001 | }; | ||
| 1002 | defer ctx.decls.deinit(allocator); | ||
| 1003 | defer ctx.decl_table.deinit(); | ||
| 1004 | errdefer ctx.arena.deinit(); | ||
| 1005 | |||
| 1006 | try ctx.emit(); | ||
| 1007 | |||
| 1008 | return Module{ | ||
| 1009 | .decls = ctx.decls.toOwnedSlice(allocator), | ||
| 1010 | .arena = ctx.arena, | ||
| 1011 | }; | ||
| 1012 | } | ||
| 1013 | |||
| 1014 | const EmitZIR = struct { | ||
| 1015 | allocator: *Allocator, | ||
| 1016 | arena: std.heap.ArenaAllocator, | ||
| 1017 | old_module: *const IrModule, | ||
| 1018 | decls: std.ArrayListUnmanaged(*Inst), | ||
| 1019 | decl_table: std.AutoHashMap(*ir.Inst, *Inst), | ||
| 1020 | |||
| 1021 | fn emit(self: *EmitZIR) !void { | ||
| 1022 | var it = self.old_module.decl_exports.iterator(); | ||
| 1023 | while (it.next()) |kv| { | ||
| 1024 | const decl = kv.key; | ||
| 1025 | const exports = kv.value; | ||
| 1026 | const export_value = try self.emitTypedValue(decl.src, decl.typed_value.most_recent.typed_value); | ||
| 1027 | for (exports) |module_export| { | ||
| 1028 | const symbol_name = try self.emitStringLiteral(module_export.src, module_export.options.name); | ||
| 1029 | const export_inst = try self.arena.allocator.create(Inst.Export); | ||
| 1030 | export_inst.* = .{ | ||
| 1031 | .base = .{ | ||
| 1032 | .name = try self.autoName(), | ||
| 1033 | .src = module_export.src, | ||
| 1034 | .tag = Inst.Export.base_tag, | ||
| 1035 | }, | ||
| 1036 | .positionals = .{ | ||
| 1037 | .symbol_name = symbol_name, | ||
| 1038 | .value = export_value, | ||
| 1039 | }, | ||
| 1040 | .kw_args = .{}, | ||
| 1041 | }; | ||
| 1042 | try self.decls.append(self.allocator, &export_inst.base); | ||
| 1043 | } | ||
| 1044 | } | ||
| 1045 | } | ||
| 1046 | |||
| 1047 | fn resolveInst(self: *EmitZIR, inst_table: *const std.AutoHashMap(*ir.Inst, *Inst), inst: *ir.Inst) !*Inst { | ||
| 1048 | if (inst.cast(ir.Inst.Constant)) |const_inst| { | ||
| 1049 | if (self.decl_table.getValue(inst)) |decl| { | ||
| 1050 | return decl; | ||
| 1051 | } | ||
| 1052 | const new_decl = try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); | ||
| 1053 | try self.decl_table.putNoClobber(inst, new_decl); | ||
| 1054 | return new_decl; | ||
| 1055 | } else { | ||
| 1056 | return inst_table.getValue(inst).?; | ||
| 1057 | } | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | fn emitComptimeIntVal(self: *EmitZIR, src: usize, val: Value) !*Inst { | ||
| 1061 | const big_int_space = try self.arena.allocator.create(Value.BigIntSpace); | ||
| 1062 | const int_inst = try self.arena.allocator.create(Inst.Int); | ||
| 1063 | int_inst.* = .{ | ||
| 1064 | .base = .{ | ||
| 1065 | .name = try self.autoName(), | ||
| 1066 | .src = src, | ||
| 1067 | .tag = Inst.Int.base_tag, | ||
| 1068 | }, | ||
| 1069 | .positionals = .{ | ||
| 1070 | .int = val.toBigInt(big_int_space), | ||
| 1071 | }, | ||
| 1072 | .kw_args = .{}, | ||
| 1073 | }; | ||
| 1074 | try self.decls.append(self.allocator, &int_inst.base); | ||
| 1075 | return &int_inst.base; | ||
| 1076 | } | ||
| 1077 | |||
| 1078 | fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Inst { | ||
| 1079 | const allocator = &self.arena.allocator; | ||
| 1080 | switch (typed_value.ty.zigTypeTag()) { | ||
| 1081 | .Pointer => { | ||
| 1082 | const ptr_elem_type = typed_value.ty.elemType(); | ||
| 1083 | switch (ptr_elem_type.zigTypeTag()) { | ||
| 1084 | .Array => { | ||
| 1085 | // TODO more checks to make sure this can be emitted as a string literal | ||
| 1086 | //const array_elem_type = ptr_elem_type.elemType(); | ||
| 1087 | //if (array_elem_type.eql(Type.initTag(.u8)) and | ||
| 1088 | // ptr_elem_type.hasSentinel(Value.initTag(.zero))) | ||
| 1089 | //{ | ||
| 1090 | //} | ||
| 1091 | const bytes = typed_value.val.toAllocatedBytes(allocator) catch |err| switch (err) { | ||
| 1092 | error.AnalysisFail => unreachable, | ||
| 1093 | else => |e| return e, | ||
| 1094 | }; | ||
| 1095 | return self.emitStringLiteral(src, bytes); | ||
| 1096 | }, | ||
| 1097 | else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {}", .{@tagName(t)}), | ||
| 1098 | } | ||
| 1099 | }, | ||
| 1100 | .ComptimeInt => return self.emitComptimeIntVal(src, typed_value.val), | ||
| 1101 | .Int => { | ||
| 1102 | const as_inst = try self.arena.allocator.create(Inst.As); | ||
| 1103 | as_inst.* = .{ | ||
| 1104 | .base = .{ | ||
| 1105 | .name = try self.autoName(), | ||
| 1106 | .src = src, | ||
| 1107 | .tag = Inst.As.base_tag, | ||
| 1108 | }, | ||
| 1109 | .positionals = .{ | ||
| 1110 | .dest_type = try self.emitType(src, typed_value.ty), | ||
| 1111 | .value = try self.emitComptimeIntVal(src, typed_value.val), | ||
| 1112 | }, | ||
| 1113 | .kw_args = .{}, | ||
| 1114 | }; | ||
| 1115 | try self.decls.append(self.allocator, &as_inst.base); | ||
| 1116 | |||
| 1117 | return &as_inst.base; | ||
| 1118 | }, | ||
| 1119 | .Type => { | ||
| 1120 | const ty = typed_value.val.toType(); | ||
| 1121 | return self.emitType(src, ty); | ||
| 1122 | }, | ||
| 1123 | .Fn => { | ||
| 1124 | const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; | ||
| 1125 | |||
| 1126 | var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); | ||
| 1127 | defer inst_table.deinit(); | ||
| 1128 | |||
| 1129 | var instructions = std.ArrayList(*Inst).init(self.allocator); | ||
| 1130 | defer instructions.deinit(); | ||
| 1131 | |||
| 1132 | try self.emitBody(module_fn.analysis.success, &inst_table, &instructions); | ||
| 1133 | |||
| 1134 | const fn_type = try self.emitType(src, module_fn.fn_type); | ||
| 1135 | |||
| 1136 | const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len); | ||
| 1137 | mem.copy(*Inst, arena_instrs, instructions.items); | ||
| 1138 | |||
| 1139 | const fn_inst = try self.arena.allocator.create(Inst.Fn); | ||
| 1140 | fn_inst.* = .{ | ||
| 1141 | .base = .{ | ||
| 1142 | .name = try self.autoName(), | ||
| 1143 | .src = src, | ||
| 1144 | .tag = Inst.Fn.base_tag, | ||
| 1145 | }, | ||
| 1146 | .positionals = .{ | ||
| 1147 | .fn_type = fn_type, | ||
| 1148 | .body = .{ .instructions = arena_instrs }, | ||
| 1149 | }, | ||
| 1150 | .kw_args = .{}, | ||
| 1151 | }; | ||
| 1152 | try self.decls.append(self.allocator, &fn_inst.base); | ||
| 1153 | return &fn_inst.base; | ||
| 1154 | }, | ||
| 1155 | else => |t| std.debug.panic("TODO implement emitTypedValue for {}", .{@tagName(t)}), | ||
| 1156 | } | ||
| 1157 | } | ||
| 1158 | |||
| 1159 | fn emitTrivial(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst { | ||
| 1160 | const new_inst = try self.arena.allocator.create(T); | ||
| 1161 | new_inst.* = .{ | ||
| 1162 | .base = .{ | ||
| 1163 | .name = try self.autoName(), | ||
| 1164 | .src = src, | ||
| 1165 | .tag = T.base_tag, | ||
| 1166 | }, | ||
| 1167 | .positionals = .{}, | ||
| 1168 | .kw_args = .{}, | ||
| 1169 | }; | ||
| 1170 | return &new_inst.base; | ||
| 1171 | } | ||
| 1172 | |||
| 1173 | fn emitBody( | ||
| 1174 | self: *EmitZIR, | ||
| 1175 | body: IrModule.Body, | ||
| 1176 | inst_table: *std.AutoHashMap(*ir.Inst, *Inst), | ||
| 1177 | instructions: *std.ArrayList(*Inst), | ||
| 1178 | ) Allocator.Error!void { | ||
| 1179 | for (body.instructions) |inst| { | ||
| 1180 | const new_inst = switch (inst.tag) { | ||
| 1181 | .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint), | ||
| 1182 | .call => blk: { | ||
| 1183 | const old_inst = inst.cast(ir.Inst.Call).?; | ||
| 1184 | const new_inst = try self.arena.allocator.create(Inst.Call); | ||
| 1185 | |||
| 1186 | const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len); | ||
| 1187 | for (args) |*elem, i| { | ||
| 1188 | elem.* = try self.resolveInst(inst_table, old_inst.args.args[i]); | ||
| 1189 | } | ||
| 1190 | new_inst.* = .{ | ||
| 1191 | .base = .{ | ||
| 1192 | .name = try self.autoName(), | ||
| 1193 | .src = inst.src, | ||
| 1194 | .tag = Inst.Call.base_tag, | ||
| 1195 | }, | ||
| 1196 | .positionals = .{ | ||
| 1197 | .func = try self.resolveInst(inst_table, old_inst.args.func), | ||
| 1198 | .args = args, | ||
| 1199 | }, | ||
| 1200 | .kw_args = .{}, | ||
| 1201 | }; | ||
| 1202 | break :blk &new_inst.base; | ||
| 1203 | }, | ||
| 1204 | .unreach => try self.emitTrivial(inst.src, Inst.Unreachable), | ||
| 1205 | .ret => try self.emitTrivial(inst.src, Inst.Return), | ||
| 1206 | .constant => unreachable, // excluded from function bodies | ||
| 1207 | .assembly => blk: { | ||
| 1208 | const old_inst = inst.cast(ir.Inst.Assembly).?; | ||
| 1209 | const new_inst = try self.arena.allocator.create(Inst.Asm); | ||
| 1210 | |||
| 1211 | const inputs = try self.arena.allocator.alloc(*Inst, old_inst.args.inputs.len); | ||
| 1212 | for (inputs) |*elem, i| { | ||
| 1213 | elem.* = try self.emitStringLiteral(inst.src, old_inst.args.inputs[i]); | ||
| 1214 | } | ||
| 1215 | |||
| 1216 | const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.args.clobbers.len); | ||
| 1217 | for (clobbers) |*elem, i| { | ||
| 1218 | elem.* = try self.emitStringLiteral(inst.src, old_inst.args.clobbers[i]); | ||
| 1219 | } | ||
| 1220 | |||
| 1221 | const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len); | ||
| 1222 | for (args) |*elem, i| { | ||
| 1223 | elem.* = try self.resolveInst(inst_table, old_inst.args.args[i]); | ||
| 1224 | } | ||
| 1225 | |||
| 1226 | new_inst.* = .{ | ||
| 1227 | .base = .{ | ||
| 1228 | .name = try self.autoName(), | ||
| 1229 | .src = inst.src, | ||
| 1230 | .tag = Inst.Asm.base_tag, | ||
| 1231 | }, | ||
| 1232 | .positionals = .{ | ||
| 1233 | .asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source), | ||
| 1234 | .return_type = try self.emitType(inst.src, inst.ty), | ||
| 1235 | }, | ||
| 1236 | .kw_args = .{ | ||
| 1237 | .@"volatile" = old_inst.args.is_volatile, | ||
| 1238 | .output = if (old_inst.args.output) |o| | ||
| 1239 | try self.emitStringLiteral(inst.src, o) | ||
| 1240 | else | ||
| 1241 | null, | ||
| 1242 | .inputs = inputs, | ||
| 1243 | .clobbers = clobbers, | ||
| 1244 | .args = args, | ||
| 1245 | }, | ||
| 1246 | }; | ||
| 1247 | break :blk &new_inst.base; | ||
| 1248 | }, | ||
| 1249 | .ptrtoint => blk: { | ||
| 1250 | const old_inst = inst.cast(ir.Inst.PtrToInt).?; | ||
| 1251 | const new_inst = try self.arena.allocator.create(Inst.PtrToInt); | ||
| 1252 | new_inst.* = .{ | ||
| 1253 | .base = .{ | ||
| 1254 | .name = try self.autoName(), | ||
| 1255 | .src = inst.src, | ||
| 1256 | .tag = Inst.PtrToInt.base_tag, | ||
| 1257 | }, | ||
| 1258 | .positionals = .{ | ||
| 1259 | .ptr = try self.resolveInst(inst_table, old_inst.args.ptr), | ||
| 1260 | }, | ||
| 1261 | .kw_args = .{}, | ||
| 1262 | }; | ||
| 1263 | break :blk &new_inst.base; | ||
| 1264 | }, | ||
| 1265 | .bitcast => blk: { | ||
| 1266 | const old_inst = inst.cast(ir.Inst.BitCast).?; | ||
| 1267 | const new_inst = try self.arena.allocator.create(Inst.BitCast); | ||
| 1268 | new_inst.* = .{ | ||
| 1269 | .base = .{ | ||
| 1270 | .name = try self.autoName(), | ||
| 1271 | .src = inst.src, | ||
| 1272 | .tag = Inst.BitCast.base_tag, | ||
| 1273 | }, | ||
| 1274 | .positionals = .{ | ||
| 1275 | .dest_type = try self.emitType(inst.src, inst.ty), | ||
| 1276 | .operand = try self.resolveInst(inst_table, old_inst.args.operand), | ||
| 1277 | }, | ||
| 1278 | .kw_args = .{}, | ||
| 1279 | }; | ||
| 1280 | break :blk &new_inst.base; | ||
| 1281 | }, | ||
| 1282 | .cmp => blk: { | ||
| 1283 | const old_inst = inst.cast(ir.Inst.Cmp).?; | ||
| 1284 | const new_inst = try self.arena.allocator.create(Inst.Cmp); | ||
| 1285 | new_inst.* = .{ | ||
| 1286 | .base = .{ | ||
| 1287 | .name = try self.autoName(), | ||
| 1288 | .src = inst.src, | ||
| 1289 | .tag = Inst.Cmp.base_tag, | ||
| 1290 | }, | ||
| 1291 | .positionals = .{ | ||
| 1292 | .lhs = try self.resolveInst(inst_table, old_inst.args.lhs), | ||
| 1293 | .rhs = try self.resolveInst(inst_table, old_inst.args.rhs), | ||
| 1294 | .op = old_inst.args.op, | ||
| 1295 | }, | ||
| 1296 | .kw_args = .{}, | ||
| 1297 | }; | ||
| 1298 | break :blk &new_inst.base; | ||
| 1299 | }, | ||
| 1300 | .condbr => blk: { | ||
| 1301 | const old_inst = inst.cast(ir.Inst.CondBr).?; | ||
| 1302 | |||
| 1303 | var true_body = std.ArrayList(*Inst).init(self.allocator); | ||
| 1304 | var false_body = std.ArrayList(*Inst).init(self.allocator); | ||
| 1305 | |||
| 1306 | defer true_body.deinit(); | ||
| 1307 | defer false_body.deinit(); | ||
| 1308 | |||
| 1309 | try self.emitBody(old_inst.args.true_body, inst_table, &true_body); | ||
| 1310 | try self.emitBody(old_inst.args.false_body, inst_table, &false_body); | ||
| 1311 | |||
| 1312 | const new_inst = try self.arena.allocator.create(Inst.CondBr); | ||
| 1313 | new_inst.* = .{ | ||
| 1314 | .base = .{ | ||
| 1315 | .name = try self.autoName(), | ||
| 1316 | .src = inst.src, | ||
| 1317 | .tag = Inst.CondBr.base_tag, | ||
| 1318 | }, | ||
| 1319 | .positionals = .{ | ||
| 1320 | .condition = try self.resolveInst(inst_table, old_inst.args.condition), | ||
| 1321 | .true_body = .{ .instructions = true_body.toOwnedSlice() }, | ||
| 1322 | .false_body = .{ .instructions = false_body.toOwnedSlice() }, | ||
| 1323 | }, | ||
| 1324 | .kw_args = .{}, | ||
| 1325 | }; | ||
| 1326 | break :blk &new_inst.base; | ||
| 1327 | }, | ||
| 1328 | .isnull => blk: { | ||
| 1329 | const old_inst = inst.cast(ir.Inst.IsNull).?; | ||
| 1330 | const new_inst = try self.arena.allocator.create(Inst.IsNull); | ||
| 1331 | new_inst.* = .{ | ||
| 1332 | .base = .{ | ||
| 1333 | .name = try self.autoName(), | ||
| 1334 | .src = inst.src, | ||
| 1335 | .tag = Inst.IsNull.base_tag, | ||
| 1336 | }, | ||
| 1337 | .positionals = .{ | ||
| 1338 | .operand = try self.resolveInst(inst_table, old_inst.args.operand), | ||
| 1339 | }, | ||
| 1340 | .kw_args = .{}, | ||
| 1341 | }; | ||
| 1342 | break :blk &new_inst.base; | ||
| 1343 | }, | ||
| 1344 | .isnonnull => blk: { | ||
| 1345 | const old_inst = inst.cast(ir.Inst.IsNonNull).?; | ||
| 1346 | const new_inst = try self.arena.allocator.create(Inst.IsNonNull); | ||
| 1347 | new_inst.* = .{ | ||
| 1348 | .base = .{ | ||
| 1349 | .name = try self.autoName(), | ||
| 1350 | .src = inst.src, | ||
| 1351 | .tag = Inst.IsNonNull.base_tag, | ||
| 1352 | }, | ||
| 1353 | .positionals = .{ | ||
| 1354 | .operand = try self.resolveInst(inst_table, old_inst.args.operand), | ||
| 1355 | }, | ||
| 1356 | .kw_args = .{}, | ||
| 1357 | }; | ||
| 1358 | break :blk &new_inst.base; | ||
| 1359 | }, | ||
| 1360 | }; | ||
| 1361 | try instructions.append(new_inst); | ||
| 1362 | try inst_table.putNoClobber(inst, new_inst); | ||
| 1363 | } | ||
| 1364 | } | ||
| 1365 | |||
| 1366 | fn emitType(self: *EmitZIR, src: usize, ty: Type) Allocator.Error!*Inst { | ||
| 1367 | switch (ty.tag()) { | ||
| 1368 | .isize => return self.emitPrimitiveType(src, .isize), | ||
| 1369 | .usize => return self.emitPrimitiveType(src, .usize), | ||
| 1370 | .c_short => return self.emitPrimitiveType(src, .c_short), | ||
| 1371 | .c_ushort => return self.emitPrimitiveType(src, .c_ushort), | ||
| 1372 | .c_int => return self.emitPrimitiveType(src, .c_int), | ||
| 1373 | .c_uint => return self.emitPrimitiveType(src, .c_uint), | ||
| 1374 | .c_long => return self.emitPrimitiveType(src, .c_long), | ||
| 1375 | .c_ulong => return self.emitPrimitiveType(src, .c_ulong), | ||
| 1376 | .c_longlong => return self.emitPrimitiveType(src, .c_longlong), | ||
| 1377 | .c_ulonglong => return self.emitPrimitiveType(src, .c_ulonglong), | ||
| 1378 | .c_longdouble => return self.emitPrimitiveType(src, .c_longdouble), | ||
| 1379 | .c_void => return self.emitPrimitiveType(src, .c_void), | ||
| 1380 | .f16 => return self.emitPrimitiveType(src, .f16), | ||
| 1381 | .f32 => return self.emitPrimitiveType(src, .f32), | ||
| 1382 | .f64 => return self.emitPrimitiveType(src, .f64), | ||
| 1383 | .f128 => return self.emitPrimitiveType(src, .f128), | ||
| 1384 | .anyerror => return self.emitPrimitiveType(src, .anyerror), | ||
| 1385 | else => switch (ty.zigTypeTag()) { | ||
| 1386 | .Bool => return self.emitPrimitiveType(src, .bool), | ||
| 1387 | .Void => return self.emitPrimitiveType(src, .void), | ||
| 1388 | .NoReturn => return self.emitPrimitiveType(src, .noreturn), | ||
| 1389 | .Type => return self.emitPrimitiveType(src, .type), | ||
| 1390 | .ComptimeInt => return self.emitPrimitiveType(src, .comptime_int), | ||
| 1391 | .ComptimeFloat => return self.emitPrimitiveType(src, .comptime_float), | ||
| 1392 | .Fn => { | ||
| 1393 | const param_types = try self.allocator.alloc(Type, ty.fnParamLen()); | ||
| 1394 | defer self.allocator.free(param_types); | ||
| 1395 | |||
| 1396 | ty.fnParamTypes(param_types); | ||
| 1397 | const emitted_params = try self.arena.allocator.alloc(*Inst, param_types.len); | ||
| 1398 | for (param_types) |param_type, i| { | ||
| 1399 | emitted_params[i] = try self.emitType(src, param_type); | ||
| 1400 | } | ||
| 1401 | |||
| 1402 | const fntype_inst = try self.arena.allocator.create(Inst.FnType); | ||
| 1403 | fntype_inst.* = .{ | ||
| 1404 | .base = .{ | ||
| 1405 | .name = try self.autoName(), | ||
| 1406 | .src = src, | ||
| 1407 | .tag = Inst.FnType.base_tag, | ||
| 1408 | }, | ||
| 1409 | .positionals = .{ | ||
| 1410 | .param_types = emitted_params, | ||
| 1411 | .return_type = try self.emitType(src, ty.fnReturnType()), | ||
| 1412 | }, | ||
| 1413 | .kw_args = .{ | ||
| 1414 | .cc = ty.fnCallingConvention(), | ||
| 1415 | }, | ||
| 1416 | }; | ||
| 1417 | try self.decls.append(self.allocator, &fntype_inst.base); | ||
| 1418 | return &fntype_inst.base; | ||
| 1419 | }, | ||
| 1420 | else => std.debug.panic("TODO implement emitType for {}", .{ty}), | ||
| 1421 | }, | ||
| 1422 | } | ||
| 1423 | } | ||
| 1424 | |||
| 1425 | fn autoName(self: *EmitZIR) ![]u8 { | ||
| 1426 | return std.fmt.allocPrint(&self.arena.allocator, "{}", .{self.decls.items.len}); | ||
| 1427 | } | ||
| 1428 | |||
| 1429 | fn emitPrimitiveType(self: *EmitZIR, src: usize, tag: Inst.Primitive.BuiltinType) !*Inst { | ||
| 1430 | const primitive_inst = try self.arena.allocator.create(Inst.Primitive); | ||
| 1431 | primitive_inst.* = .{ | ||
| 1432 | .base = .{ | ||
| 1433 | .name = try self.autoName(), | ||
| 1434 | .src = src, | ||
| 1435 | .tag = Inst.Primitive.base_tag, | ||
| 1436 | }, | ||
| 1437 | .positionals = .{ | ||
| 1438 | .tag = tag, | ||
| 1439 | }, | ||
| 1440 | .kw_args = .{}, | ||
| 1441 | }; | ||
| 1442 | try self.decls.append(self.allocator, &primitive_inst.base); | ||
| 1443 | return &primitive_inst.base; | ||
| 1444 | } | ||
| 1445 | |||
| 1446 | fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Inst { | ||
| 1447 | const str_inst = try self.arena.allocator.create(Inst.Str); | ||
| 1448 | str_inst.* = .{ | ||
| 1449 | .base = .{ | ||
| 1450 | .name = try self.autoName(), | ||
| 1451 | .src = src, | ||
| 1452 | .tag = Inst.Str.base_tag, | ||
| 1453 | }, | ||
| 1454 | .positionals = .{ | ||
| 1455 | .bytes = str, | ||
| 1456 | }, | ||
| 1457 | .kw_args = .{}, | ||
| 1458 | }; | ||
| 1459 | try self.decls.append(self.allocator, &str_inst.base); | ||
| 1460 | |||
| 1461 | const ref_inst = try self.arena.allocator.create(Inst.Ref); | ||
| 1462 | ref_inst.* = .{ | ||
| 1463 | .base = .{ | ||
| 1464 | .name = try self.autoName(), | ||
| 1465 | .src = src, | ||
| 1466 | .tag = Inst.Ref.base_tag, | ||
| 1467 | }, | ||
| 1468 | .positionals = .{ | ||
| 1469 | .operand = &str_inst.base, | ||
| 1470 | }, | ||
| 1471 | .kw_args = .{}, | ||
| 1472 | }; | ||
| 1473 | try self.decls.append(self.allocator, &ref_inst.base); | ||
| 1474 | |||
| 1475 | return &ref_inst.base; | ||
| 1476 | } | ||
| 1477 | }; | ||
src/codegen.cpp+10| ... | @@ -1794,6 +1794,16 @@ static LLVMValueRef ir_llvm_value(CodeGen *g, IrInstGen *instruction) { | ... | @@ -1794,6 +1794,16 @@ static LLVMValueRef ir_llvm_value(CodeGen *g, IrInstGen *instruction) { |
| 1794 | } | 1794 | } |
| 1795 | 1795 | ||
| 1796 | void codegen_report_errors_and_exit(CodeGen *g) { | 1796 | void codegen_report_errors_and_exit(CodeGen *g) { |
| 1797 | // Clear progress indicator before printing errors | ||
| 1798 | if (g->sub_progress_node != nullptr) { | ||
| 1799 | stage2_progress_end(g->sub_progress_node); | ||
| 1800 | g->sub_progress_node = nullptr; | ||
| 1801 | } | ||
| 1802 | if (g->main_progress_node != nullptr) { | ||
| 1803 | stage2_progress_end(g->main_progress_node); | ||
| 1804 | g->main_progress_node = nullptr; | ||
| 1805 | } | ||
| 1806 | |||
| 1797 | assert(g->errors.length != 0); | 1807 | assert(g->errors.length != 0); |
| 1798 | for (size_t i = 0; i < g->errors.length; i += 1) { | 1808 | for (size_t i = 0; i < g->errors.length; i += 1) { |
| 1799 | ErrorMsg *err = g->errors.at(i); | 1809 | ErrorMsg *err = g->errors.at(i); |
test/stage2/zir.zig+163-58| ... | @@ -1,7 +1,15 @@ | ... | @@ -1,7 +1,15 @@ |
| 1 | const std = @import("std"); | ||
| 1 | const TestContext = @import("../../src-self-hosted/test.zig").TestContext; | 2 | const TestContext = @import("../../src-self-hosted/test.zig").TestContext; |
| 3 | // self-hosted does not yet support PE executable files / COFF object files | ||
| 4 | // or mach-o files. So we do the ZIR transform test cases cross compiling for | ||
| 5 | // x86_64-linux. | ||
| 6 | const linux_x64 = std.zig.CrossTarget{ | ||
| 7 | .cpu_arch = .x86_64, | ||
| 8 | .os_tag = .linux, | ||
| 9 | }; | ||
| 2 | 10 | ||
| 3 | pub fn addCases(ctx: *TestContext) void { | 11 | pub fn addCases(ctx: *TestContext) void { |
| 4 | ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", | 12 | ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, |
| 5 | \\@void = primitive(void) | 13 | \\@void = primitive(void) |
| 6 | \\@usize = primitive(usize) | 14 | \\@usize = primitive(usize) |
| 7 | \\@fnty = fntype([], @void, cc=C) | 15 | \\@fnty = fntype([], @void, cc=C) |
| ... | @@ -12,10 +20,11 @@ pub fn addCases(ctx: *TestContext) void { | ... | @@ -12,10 +20,11 @@ pub fn addCases(ctx: *TestContext) void { |
| 12 | \\ | 20 | \\ |
| 13 | \\@entry = fn(@fnty, { | 21 | \\@entry = fn(@fnty, { |
| 14 | \\ %a = str("\x32\x08\x01\x0a") | 22 | \\ %a = str("\x32\x08\x01\x0a") |
| 15 | \\ %eptr0 = elemptr(%a, @0) | 23 | \\ %aref = ref(%a) |
| 16 | \\ %eptr1 = elemptr(%a, @1) | 24 | \\ %eptr0 = elemptr(%aref, @0) |
| 17 | \\ %eptr2 = elemptr(%a, @2) | 25 | \\ %eptr1 = elemptr(%aref, @1) |
| 18 | \\ %eptr3 = elemptr(%a, @3) | 26 | \\ %eptr2 = elemptr(%aref, @2) |
| 27 | \\ %eptr3 = elemptr(%aref, @3) | ||
| 19 | \\ %v0 = deref(%eptr0) | 28 | \\ %v0 = deref(%eptr0) |
| 20 | \\ %v1 = deref(%eptr1) | 29 | \\ %v1 = deref(%eptr1) |
| 21 | \\ %v2 = deref(%eptr2) | 30 | \\ %v2 = deref(%eptr2) |
| ... | @@ -34,7 +43,8 @@ pub fn addCases(ctx: *TestContext) void { | ... | @@ -34,7 +43,8 @@ pub fn addCases(ctx: *TestContext) void { |
| 34 | \\}) | 43 | \\}) |
| 35 | \\ | 44 | \\ |
| 36 | \\@9 = str("entry") | 45 | \\@9 = str("entry") |
| 37 | \\@10 = export(@9, @entry) | 46 | \\@10 = ref(@9) |
| 47 | \\@11 = export(@10, @entry) | ||
| 38 | , | 48 | , |
| 39 | \\@0 = primitive(void) | 49 | \\@0 = primitive(void) |
| 40 | \\@1 = fntype([], @0, cc=C) | 50 | \\@1 = fntype([], @0, cc=C) |
| ... | @@ -42,66 +52,161 @@ pub fn addCases(ctx: *TestContext) void { | ... | @@ -42,66 +52,161 @@ pub fn addCases(ctx: *TestContext) void { |
| 42 | \\ %0 = return() | 52 | \\ %0 = return() |
| 43 | \\}) | 53 | \\}) |
| 44 | \\@3 = str("entry") | 54 | \\@3 = str("entry") |
| 45 | \\@4 = export(@3, @2) | 55 | \\@4 = ref(@3) |
| 56 | \\@5 = export(@4, @2) | ||
| 46 | \\ | 57 | \\ |
| 47 | ); | 58 | ); |
| 48 | 59 | ||
| 49 | if (@import("std").Target.current.os.tag != .linux or | 60 | if (std.Target.current.os.tag != .linux or |
| 50 | @import("std").Target.current.cpu.arch != .x86_64) | 61 | std.Target.current.cpu.arch != .x86_64) |
| 51 | { | 62 | { |
| 52 | // TODO implement self-hosted PE (.exe file) linking | 63 | // TODO implement self-hosted PE (.exe file) linking |
| 53 | // TODO implement more ZIR so we don't depend on x86_64-linux | 64 | // TODO implement more ZIR so we don't depend on x86_64-linux |
| 54 | return; | 65 | return; |
| 55 | } | 66 | } |
| 56 | 67 | ||
| 57 | ctx.addZIRCompareOutput("hello world ZIR", | 68 | ctx.addZIRCompareOutput( |
| 58 | \\@0 = str("Hello, world!\n") | 69 | "hello world ZIR, update msg", |
| 59 | \\@1 = primitive(noreturn) | 70 | &[_][]const u8{ |
| 60 | \\@2 = primitive(usize) | 71 | \\@noreturn = primitive(noreturn) |
| 61 | \\@3 = fntype([], @1, cc=Naked) | 72 | \\@void = primitive(void) |
| 62 | \\@4 = int(0) | 73 | \\@usize = primitive(usize) |
| 63 | \\@5 = int(1) | 74 | \\@0 = int(0) |
| 64 | \\@6 = int(231) | 75 | \\@1 = int(1) |
| 65 | \\@7 = str("len") | 76 | \\@2 = int(2) |
| 66 | \\ | 77 | \\@3 = int(3) |
| 67 | \\@8 = fn(@3, { | 78 | \\ |
| 68 | \\ %0 = as(@2, @5) ; SYS_write | 79 | \\@syscall_array = str("syscall") |
| 69 | \\ %1 = as(@2, @5) ; STDOUT_FILENO | 80 | \\@sysoutreg_array = str("={rax}") |
| 70 | \\ %2 = ptrtoint(@0) ; msg ptr | 81 | \\@rax_array = str("{rax}") |
| 71 | \\ %3 = fieldptr(@0, @7) ; msg len ptr | 82 | \\@rdi_array = str("{rdi}") |
| 72 | \\ %4 = deref(%3) ; msg len | 83 | \\@rcx_array = str("rcx") |
| 73 | \\ %sysoutreg = str("={rax}") | 84 | \\@r11_array = str("r11") |
| 74 | \\ %rax = str("{rax}") | 85 | \\@rdx_array = str("{rdx}") |
| 75 | \\ %rdi = str("{rdi}") | 86 | \\@rsi_array = str("{rsi}") |
| 76 | \\ %rsi = str("{rsi}") | 87 | \\@memory_array = str("memory") |
| 77 | \\ %rdx = str("{rdx}") | 88 | \\@len_array = str("len") |
| 78 | \\ %rcx = str("rcx") | 89 | \\ |
| 79 | \\ %r11 = str("r11") | 90 | \\@msg = str("Hello, world!\n") |
| 80 | \\ %memory = str("memory") | 91 | \\ |
| 81 | \\ %syscall = str("syscall") | 92 | \\@start_fnty = fntype([], @noreturn, cc=Naked) |
| 82 | \\ %5 = asm(%syscall, @2, | 93 | \\@start = fn(@start_fnty, { |
| 83 | \\ volatile=1, | 94 | \\ %SYS_exit_group = int(231) |
| 84 | \\ output=%sysoutreg, | 95 | \\ %exit_code = as(@usize, @0) |
| 85 | \\ inputs=[%rax, %rdi, %rsi, %rdx], | 96 | \\ |
| 86 | \\ clobbers=[%rcx, %r11, %memory], | 97 | \\ %syscall = ref(@syscall_array) |
| 87 | \\ args=[%0, %1, %2, %4]) | 98 | \\ %sysoutreg = ref(@sysoutreg_array) |
| 88 | \\ | 99 | \\ %rax = ref(@rax_array) |
| 89 | \\ %6 = as(@2, @6) ;SYS_exit_group | 100 | \\ %rdi = ref(@rdi_array) |
| 90 | \\ %7 = as(@2, @4) ;exit code | 101 | \\ %rcx = ref(@rcx_array) |
| 91 | \\ %8 = asm(%syscall, @2, | 102 | \\ %rdx = ref(@rdx_array) |
| 92 | \\ volatile=1, | 103 | \\ %rsi = ref(@rsi_array) |
| 93 | \\ output=%sysoutreg, | 104 | \\ %r11 = ref(@r11_array) |
| 94 | \\ inputs=[%rax, %rdi], | 105 | \\ %memory = ref(@memory_array) |
| 95 | \\ clobbers=[%rcx, %r11, %memory], | 106 | \\ |
| 96 | \\ args=[%6, %7]) | 107 | \\ %SYS_write = as(@usize, @1) |
| 97 | \\ | 108 | \\ %STDOUT_FILENO = as(@usize, @1) |
| 98 | \\ %9 = unreachable() | 109 | \\ |
| 99 | \\}) | 110 | \\ %msg_ptr = ref(@msg) |
| 100 | \\ | 111 | \\ %msg_addr = ptrtoint(%msg_ptr) |
| 101 | \\@9 = str("_start") | 112 | \\ |
| 102 | \\@10 = export(@9, @8) | 113 | \\ %len_name = ref(@len_array) |
| 103 | , | 114 | \\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name) |
| 104 | \\Hello, world! | 115 | \\ %msg_len = deref(%msg_len_ptr) |
| 105 | \\ | 116 | \\ %rc_write = asm(%syscall, @usize, |
| 117 | \\ volatile=1, | ||
| 118 | \\ output=%sysoutreg, | ||
| 119 | \\ inputs=[%rax, %rdi, %rsi, %rdx], | ||
| 120 | \\ clobbers=[%rcx, %r11, %memory], | ||
| 121 | \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len]) | ||
| 122 | \\ | ||
| 123 | \\ %rc_exit = asm(%syscall, @usize, | ||
| 124 | \\ volatile=1, | ||
| 125 | \\ output=%sysoutreg, | ||
| 126 | \\ inputs=[%rax, %rdi], | ||
| 127 | \\ clobbers=[%rcx, %r11, %memory], | ||
| 128 | \\ args=[%SYS_exit_group, %exit_code]) | ||
| 129 | \\ | ||
| 130 | \\ %99 = unreachable() | ||
| 131 | \\}); | ||
| 132 | \\ | ||
| 133 | \\@9 = str("_start") | ||
| 134 | \\@10 = ref(@9) | ||
| 135 | \\@11 = export(@10, @start) | ||
| 136 | , | ||
| 137 | \\@noreturn = primitive(noreturn) | ||
| 138 | \\@void = primitive(void) | ||
| 139 | \\@usize = primitive(usize) | ||
| 140 | \\@0 = int(0) | ||
| 141 | \\@1 = int(1) | ||
| 142 | \\@2 = int(2) | ||
| 143 | \\@3 = int(3) | ||
| 144 | \\ | ||
| 145 | \\@syscall_array = str("syscall") | ||
| 146 | \\@sysoutreg_array = str("={rax}") | ||
| 147 | \\@rax_array = str("{rax}") | ||
| 148 | \\@rdi_array = str("{rdi}") | ||
| 149 | \\@rcx_array = str("rcx") | ||
| 150 | \\@r11_array = str("r11") | ||
| 151 | \\@rdx_array = str("{rdx}") | ||
| 152 | \\@rsi_array = str("{rsi}") | ||
| 153 | \\@memory_array = str("memory") | ||
| 154 | \\@len_array = str("len") | ||
| 155 | \\ | ||
| 156 | \\@msg = str("Hello, world!\n") | ||
| 157 | \\@msg2 = str("HELL WORLD\n") | ||
| 158 | \\ | ||
| 159 | \\@start_fnty = fntype([], @noreturn, cc=Naked) | ||
| 160 | \\@start = fn(@start_fnty, { | ||
| 161 | \\ %SYS_exit_group = int(231) | ||
| 162 | \\ %exit_code = as(@usize, @0) | ||
| 163 | \\ | ||
| 164 | \\ %syscall = ref(@syscall_array) | ||
| 165 | \\ %sysoutreg = ref(@sysoutreg_array) | ||
| 166 | \\ %rax = ref(@rax_array) | ||
| 167 | \\ %rdi = ref(@rdi_array) | ||
| 168 | \\ %rcx = ref(@rcx_array) | ||
| 169 | \\ %rdx = ref(@rdx_array) | ||
| 170 | \\ %rsi = ref(@rsi_array) | ||
| 171 | \\ %r11 = ref(@r11_array) | ||
| 172 | \\ %memory = ref(@memory_array) | ||
| 173 | \\ | ||
| 174 | \\ %SYS_write = as(@usize, @1) | ||
| 175 | \\ %STDOUT_FILENO = as(@usize, @1) | ||
| 176 | \\ | ||
| 177 | \\ %msg_ptr = ref(@msg2) | ||
| 178 | \\ %msg_addr = ptrtoint(%msg_ptr) | ||
| 179 | \\ | ||
| 180 | \\ %len_name = ref(@len_array) | ||
| 181 | \\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name) | ||
| 182 | \\ %msg_len = deref(%msg_len_ptr) | ||
| 183 | \\ %rc_write = asm(%syscall, @usize, | ||
| 184 | \\ volatile=1, | ||
| 185 | \\ output=%sysoutreg, | ||
| 186 | \\ inputs=[%rax, %rdi, %rsi, %rdx], | ||
| 187 | \\ clobbers=[%rcx, %r11, %memory], | ||
| 188 | \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len]) | ||
| 189 | \\ | ||
| 190 | \\ %rc_exit = asm(%syscall, @usize, | ||
| 191 | \\ volatile=1, | ||
| 192 | \\ output=%sysoutreg, | ||
| 193 | \\ inputs=[%rax, %rdi], | ||
| 194 | \\ clobbers=[%rcx, %r11, %memory], | ||
| 195 | \\ args=[%SYS_exit_group, %exit_code]) | ||
| 196 | \\ | ||
| 197 | \\ %99 = unreachable() | ||
| 198 | \\}); | ||
| 199 | \\ | ||
| 200 | \\@9 = str("_start") | ||
| 201 | \\@10 = ref(@9) | ||
| 202 | \\@11 = export(@10, @start) | ||
| 203 | }, | ||
| 204 | &[_][]const u8{ | ||
| 205 | \\Hello, world! | ||
| 206 | \\ | ||
| 207 | , | ||
| 208 | \\HELL WORLD | ||
| 209 | \\ | ||
| 210 | }, | ||
| 106 | ); | 211 | ); |
| 107 | } | 212 | } |