| author | |
| committer | |
| log | 16f100b82e4075a047f008c0de6c44fc418eb58e |
| tree | 93555fbcba29bcd1120349a03ae6904321cb7718 |
| parent | 9a22c8b6ca98fd01795f8cd4f3e9d92311175f13 |
| parent | b0968abccbfb4072528c3b5e039bc03b27af89a1 |
| signature |
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 | 71 | ##### Windows |
| 72 | 72 | |
| 73 | 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 | 51 | |
| 52 | 52 | var exe = b.addExecutable("zig", "src-self-hosted/main.zig"); |
| 53 | 53 | exe.setBuildMode(mode); |
| 54 | test_step.dependOn(&exe.step); | |
| 55 | b.default_step.dependOn(&exe.step); | |
| 54 | 56 | |
| 55 | 57 | const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; |
| 56 | 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 | 60 | const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release; |
| 59 | 61 | const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false; |
| 60 | 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 | 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 | 67 | var ctx = parseConfigH(b, config_h_text); |
| 69 | 68 | ctx.llvm = try findLLVM(b, ctx.llvm_config_exe); |
| 70 | 69 | |
| 71 | 70 | try configureStage2(b, exe, ctx); |
| 72 | ||
| 73 | b.default_step.dependOn(&exe.step); | |
| 71 | } | |
| 72 | if (!only_install_lib_files) { | |
| 74 | 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 | 78 | b.installDirectory(InstallDirectoryOptions{ |
| 78 | 79 | .source_dir = "lib", |
lib/std/array_list.zig+242-9| ... | ... | @@ -8,13 +8,13 @@ const Allocator = mem.Allocator; |
| 8 | 8 | /// A contiguous, growable list of items in memory. |
| 9 | 9 | /// This is a wrapper around an array of T values. Initialize with `init`. |
| 10 | 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 | 15 | if (alignment) |a| { |
| 16 | 16 | if (a == @alignOf(T)) { |
| 17 | return AlignedArrayList(T, null); | |
| 17 | return ArrayListAligned(T, null); | |
| 18 | 18 | } |
| 19 | 19 | } |
| 20 | 20 | return struct { |
| ... | ... | @@ -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 | 83 | /// The caller owns the returned memory. ArrayList becomes empty. |
| 80 | 84 | pub fn toOwnedSlice(self: *Self) Slice { |
| 81 | 85 | const allocator = self.allocator; |
| ... | ... | @@ -84,8 +88,8 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { |
| 84 | 88 | return result; |
| 85 | 89 | } |
| 86 | 90 | |
| 87 | /// Insert `item` at index `n`. Moves `list[n .. list.len]` | |
| 88 | /// to make room. | |
| 91 | /// Insert `item` at index `n` by moving `list[n .. list.len]` to make room. | |
| 92 | /// This operation is O(N). | |
| 89 | 93 | pub fn insert(self: *Self, n: usize, item: T) !void { |
| 90 | 94 | try self.ensureCapacity(self.items.len + 1); |
| 91 | 95 | self.items.len += 1; |
| ... | ... | @@ -94,8 +98,7 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { |
| 94 | 98 | self.items[n] = item; |
| 95 | 99 | } |
| 96 | 100 | |
| 97 | /// Insert slice `items` at index `i`. Moves | |
| 98 | /// `list[i .. list.len]` to make room. | |
| 101 | /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room. | |
| 99 | 102 | /// This operation is O(N). |
| 100 | 103 | pub fn insertSlice(self: *Self, i: usize, items: SliceConst) !void { |
| 101 | 104 | try self.ensureCapacity(self.items.len + items.len); |
| ... | ... | @@ -146,10 +149,15 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type { |
| 146 | 149 | /// Append the slice of items to the list. Allocates more |
| 147 | 150 | /// memory as necessary. |
| 148 | 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 | 159 | const oldlen = self.items.len; |
| 150 | 160 | const newlen = self.items.len + items.len; |
| 151 | ||
| 152 | try self.ensureCapacity(newlen); | |
| 153 | 161 | self.items.len = newlen; |
| 154 | 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 | 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 | 495 | test "std.ArrayList.init" { |
| 263 | 496 | var list = ArrayList(i32).init(testing.allocator); |
| 264 | 497 | defer list.deinit(); |
lib/std/fifo.zig+20-17| ... | ... | @@ -191,8 +191,8 @@ pub fn LinearFifo( |
| 191 | 191 | } |
| 192 | 192 | |
| 193 | 193 | /// Read the next item from the fifo |
| 194 | pub fn readItem(self: *Self) !T { | |
| 195 | if (self.count == 0) return error.EndOfStream; | |
| 194 | pub fn readItem(self: *Self) ?T { | |
| 195 | if (self.count == 0) return null; | |
| 196 | 196 | |
| 197 | 197 | const c = self.buf[self.head]; |
| 198 | 198 | self.discard(1); |
| ... | ... | @@ -282,7 +282,10 @@ pub fn LinearFifo( |
| 282 | 282 | /// Write a single item to the fifo |
| 283 | 283 | pub fn writeItem(self: *Self, item: T) !void { |
| 284 | 284 | try self.ensureUnusedCapacity(1); |
| 285 | return self.writeItemAssumeCapacity(item); | |
| 286 | } | |
| 285 | 287 | |
| 288 | pub fn writeItemAssumeCapacity(self: *Self, item: T) void { | |
| 286 | 289 | var tail = self.head + self.count; |
| 287 | 290 | if (powers_of_two) { |
| 288 | 291 | tail &= self.buf.len - 1; |
| ... | ... | @@ -342,10 +345,10 @@ pub fn LinearFifo( |
| 342 | 345 | } |
| 343 | 346 | } |
| 344 | 347 | |
| 345 | /// Peek at the item at `offset` | |
| 346 | pub fn peekItem(self: Self, offset: usize) error{EndOfStream}!T { | |
| 347 | if (offset >= self.count) | |
| 348 | return error.EndOfStream; | |
| 348 | /// Returns the item at `offset`. | |
| 349 | /// Asserts offset is within bounds. | |
| 350 | pub fn peekItem(self: Self, offset: usize) T { | |
| 351 | assert(offset < self.count); | |
| 349 | 352 | |
| 350 | 353 | var index = self.head + offset; |
| 351 | 354 | if (powers_of_two) { |
| ... | ... | @@ -369,18 +372,18 @@ test "LinearFifo(u8, .Dynamic)" { |
| 369 | 372 | { |
| 370 | 373 | var i: usize = 0; |
| 371 | 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 | 377 | testing.expectEqual(@as(usize, 10), fifo.readableLength()); |
| 375 | 378 | testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0)); |
| 376 | 379 | } |
| 377 | 380 | |
| 378 | 381 | { |
| 379 | testing.expectEqual(@as(u8, 'H'), try fifo.readItem()); | |
| 380 | testing.expectEqual(@as(u8, 'E'), try fifo.readItem()); | |
| 381 | testing.expectEqual(@as(u8, 'L'), try fifo.readItem()); | |
| 382 | testing.expectEqual(@as(u8, 'L'), try fifo.readItem()); | |
| 383 | testing.expectEqual(@as(u8, 'O'), try fifo.readItem()); | |
| 382 | testing.expectEqual(@as(u8, 'H'), fifo.readItem().?); | |
| 383 | testing.expectEqual(@as(u8, 'E'), fifo.readItem().?); | |
| 384 | testing.expectEqual(@as(u8, 'L'), fifo.readItem().?); | |
| 385 | testing.expectEqual(@as(u8, 'L'), fifo.readItem().?); | |
| 386 | testing.expectEqual(@as(u8, 'O'), fifo.readItem().?); | |
| 384 | 387 | } |
| 385 | 388 | testing.expectEqual(@as(usize, 5), fifo.readableLength()); |
| 386 | 389 | |
| ... | ... | @@ -451,11 +454,11 @@ test "LinearFifo" { |
| 451 | 454 | testing.expectEqual(@as(usize, 5), fifo.readableLength()); |
| 452 | 455 | |
| 453 | 456 | { |
| 454 | testing.expectEqual(@as(T, 0), try fifo.readItem()); | |
| 455 | testing.expectEqual(@as(T, 1), try fifo.readItem()); | |
| 456 | testing.expectEqual(@as(T, 1), try fifo.readItem()); | |
| 457 | testing.expectEqual(@as(T, 0), try fifo.readItem()); | |
| 458 | testing.expectEqual(@as(T, 1), try fifo.readItem()); | |
| 457 | testing.expectEqual(@as(T, 0), fifo.readItem().?); | |
| 458 | testing.expectEqual(@as(T, 1), fifo.readItem().?); | |
| 459 | testing.expectEqual(@as(T, 1), fifo.readItem().?); | |
| 460 | testing.expectEqual(@as(T, 0), fifo.readItem().?); | |
| 461 | testing.expectEqual(@as(T, 1), fifo.readItem().?); | |
| 459 | 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 | 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 | 557 | pub const WriteFileOptions = struct { |
| 531 | 558 | in_offset: u64 = 0, |
| 532 | 559 |
lib/std/hash_map.zig+5-1| ... | ... | @@ -10,7 +10,7 @@ const Wyhash = std.hash.Wyhash; |
| 10 | 10 | const Allocator = mem.Allocator; |
| 11 | 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 | 14 | const debug_u32 = if (want_modification_safety) u32 else void; |
| 15 | 15 | |
| 16 | 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 | 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 | 226 | pub fn get(hm: *const Self, key: K) ?*KV { |
| 223 | 227 | if (hm.entries.len == 0) { |
| 224 | 228 | return null; |
lib/std/heap.zig+1-89| ... | ... | @@ -11,6 +11,7 @@ const maxInt = std.math.maxInt; |
| 11 | 11 | |
| 12 | 12 | pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator; |
| 13 | 13 | pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator; |
| 14 | pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator; | |
| 14 | 15 | |
| 15 | 16 | const Allocator = mem.Allocator; |
| 16 | 17 | |
| ... | ... | @@ -510,95 +511,6 @@ pub const HeapAllocator = switch (builtin.os.tag) { |
| 510 | 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 | 514 | pub const FixedBufferAllocator = struct { |
| 603 | 515 | allocator: Allocator, |
| 604 | 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 | 49 | } |
| 50 | 50 | }; |
| 51 | 51 | |
| 52 | first: ?*Node, | |
| 52 | first: ?*Node = null, | |
| 53 | 53 | |
| 54 | 54 | /// Initialize a linked list. |
| 55 | 55 | /// |
lib/std/mem.zig+24-10| ... | ... | @@ -279,6 +279,21 @@ pub const Allocator = struct { |
| 279 | 279 | const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1); |
| 280 | 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 | 299 | var failAllocator = Allocator { |
| ... | ... | @@ -785,19 +800,14 @@ pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool { |
| 785 | 800 | return true; |
| 786 | 801 | } |
| 787 | 802 | |
| 788 | /// Copies `m` to newly allocated memory. Caller owns the memory. | |
| 803 | /// Deprecated, use `Allocator.dupe`. | |
| 789 | 804 | pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T { |
| 790 | const new_buf = try allocator.alloc(T, m.len); | |
| 791 | copy(T, new_buf, m); | |
| 792 | return new_buf; | |
| 805 | return allocator.dupe(T, m); | |
| 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 | 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); | |
| 798 | copy(T, new_buf, m); | |
| 799 | new_buf[m.len] = 0; | |
| 800 | return new_buf[0..m.len :0]; | |
| 810 | return allocator.dupeZ(T, m); | |
| 801 | 811 | } |
| 802 | 812 | |
| 803 | 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 | 2122 | /// Given an address and an alignment, return true if the address is a multiple of the alignment |
| 2113 | 2123 | /// The alignment must be a power of 2 and greater than 0. |
| 2114 | 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 | 2132 | test "isAligned" { |
lib/std/std.zig+3-1| ... | ... | @@ -1,6 +1,8 @@ |
| 1 | pub const AlignedArrayList = @import("array_list.zig").AlignedArrayList; | |
| 2 | 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 | 4 | pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled; |
| 5 | pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged; | |
| 4 | 6 | pub const AutoHashMap = @import("hash_map.zig").AutoHashMap; |
| 5 | 7 | pub const BloomFilter = @import("bloom_filter.zig").BloomFilter; |
| 6 | 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 | 4 | const ir = @import("ir.zig"); |
| 5 | 5 | const Type = @import("type.zig").Type; |
| 6 | 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 | 11 | const Target = std.Target; |
| 8 | ||
| 9 | pub const ErrorMsg = struct { | |
| 10 | byte_offset: usize, | |
| 11 | msg: []const u8, | |
| 12 | }; | |
| 13 | ||
| 14 | pub const Symbol = struct { | |
| 15 | errors: []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 | } | |
| 12 | const Allocator = mem.Allocator; | |
| 13 | ||
| 14 | pub const Result = union(enum) { | |
| 15 | /// The `code` parameter passed to `generateSymbol` has the value appended. | |
| 16 | appended: void, | |
| 17 | /// The value is available externally, `code` is unused. | |
| 18 | externally_managed: []const u8, | |
| 19 | fail: *Module.ErrorMsg, | |
| 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 | 32 | switch (typed_value.ty.zigTypeTag()) { |
| 28 | 33 | .Fn => { |
| 29 | const index = typed_value.val.cast(Value.Payload.Function).?.index; | |
| 30 | const module_fn = module.fns[index]; | |
| 34 | const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; | |
| 31 | 35 | |
| 32 | 36 | var function = Function{ |
| 33 | .module = &module, | |
| 34 | .mod_fn = &module_fn, | |
| 37 | .target = &bin_file.options.target, | |
| 38 | .bin_file = bin_file, | |
| 39 | .mod_fn = module_fn, | |
| 35 | 40 | .code = code, |
| 36 | .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(code.allocator), | |
| 37 | .errors = std.ArrayList(ErrorMsg).init(code.allocator), | |
| 41 | .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(bin_file.allocator), | |
| 42 | .err_msg = null, | |
| 38 | 43 | }; |
| 39 | 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 | 47 | const new_inst = function.genFuncInst(inst) catch |err| switch (err) { |
| 44 | error.CodegenFail => { | |
| 45 | assert(function.errors.items.len != 0); | |
| 46 | break; | |
| 47 | }, | |
| 48 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, | |
| 48 | 49 | else => |e| return e, |
| 49 | 50 | }; |
| 50 | 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 | 149 | const Function = struct { |
| 60 | module: *const ir.Module, | |
| 61 | mod_fn: *const ir.Module.Fn, | |
| 150 | bin_file: *link.ElfFile, | |
| 151 | target: *const std.Target, | |
| 152 | mod_fn: *const Module.Fn, | |
| 62 | 153 | code: *std.ArrayList(u8), |
| 63 | 154 | inst_table: std.AutoHashMap(*ir.Inst, MCValue), |
| 64 | errors: std.ArrayList(ErrorMsg), | |
| 155 | err_msg: ?*ErrorMsg, | |
| 65 | 156 | |
| 66 | 157 | const MCValue = union(enum) { |
| 67 | 158 | none, |
| ... | ... | @@ -73,11 +164,14 @@ const Function = struct { |
| 73 | 164 | /// The value is in a target-specific register. The value can |
| 74 | 165 | /// be @intToEnum casted to the respective Reg enum. |
| 75 | 166 | register: usize, |
| 167 | /// The value is in memory at a hard-coded address. | |
| 168 | memory: u64, | |
| 76 | 169 | }; |
| 77 | 170 | |
| 78 | 171 | fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { |
| 79 | 172 | switch (inst.tag) { |
| 80 | 173 | .breakpoint => return self.genBreakpoint(inst.src), |
| 174 | .call => return self.genCall(inst.cast(ir.Inst.Call).?), | |
| 81 | 175 | .unreach => return MCValue{ .unreach = {} }, |
| 82 | 176 | .constant => unreachable, // excluded from function bodies |
| 83 | 177 | .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), |
| ... | ... | @@ -92,54 +186,76 @@ const Function = struct { |
| 92 | 186 | } |
| 93 | 187 | |
| 94 | 188 | fn genBreakpoint(self: *Function, src: usize) !MCValue { |
| 95 | switch (self.module.target.cpu.arch) { | |
| 189 | switch (self.target.cpu.arch) { | |
| 96 | 190 | .i386, .x86_64 => { |
| 97 | 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 | 219 | fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue { |
| 105 | switch (self.module.target.cpu.arch) { | |
| 220 | switch (self.target.cpu.arch) { | |
| 106 | 221 | .i386, .x86_64 => { |
| 107 | 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 | 226 | return .unreach; |
| 112 | 227 | } |
| 113 | 228 | |
| 114 | 229 | fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue { |
| 115 | switch (self.module.target.cpu.arch) { | |
| 116 | else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.module.target.cpu.arch}), | |
| 230 | switch (self.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 | 235 | fn genCondBr(self: *Function, inst: *ir.Inst.CondBr) !MCValue { |
| 121 | switch (self.module.target.cpu.arch) { | |
| 122 | else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.module.target.cpu.arch}), | |
| 236 | switch (self.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 | 241 | fn genIsNull(self: *Function, inst: *ir.Inst.IsNull) !MCValue { |
| 127 | switch (self.module.target.cpu.arch) { | |
| 128 | else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.module.target.cpu.arch}), | |
| 242 | switch (self.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 | 247 | fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull) !MCValue { |
| 133 | 248 | // Here you can specialize this instruction if it makes sense to, otherwise the default |
| 134 | 249 | // will call genIsNull and invert the result. |
| 135 | switch (self.module.target.cpu.arch) { | |
| 250 | switch (self.target.cpu.arch) { | |
| 136 | 251 | else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}), |
| 137 | 252 | } |
| 138 | 253 | } |
| 139 | 254 | |
| 140 | 255 | fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void { |
| 141 | switch (self.module.target.cpu.arch) { | |
| 256 | switch (self.target.cpu.arch) { | |
| 142 | 257 | .i386, .x86_64 => { |
| 258 | // TODO x86 treats the operands as signed | |
| 143 | 259 | if (amount <= std.math.maxInt(u8)) { |
| 144 | 260 | try self.code.resize(self.code.items.len + 2); |
| 145 | 261 | self.code.items[self.code.items.len - 2] = 0xeb; |
| ... | ... | @@ -151,13 +267,13 @@ const Function = struct { |
| 151 | 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 | 274 | fn genAsm(self: *Function, inst: *ir.Inst.Assembly) !MCValue { |
| 159 | 275 | // TODO convert to inline function |
| 160 | switch (self.module.target.cpu.arch) { | |
| 276 | switch (self.target.cpu.arch) { | |
| 161 | 277 | .arm => return self.genAsmArch(.arm, inst), |
| 162 | 278 | .armeb => return self.genAsmArch(.armeb, inst), |
| 163 | 279 | .aarch64 => return self.genAsmArch(.aarch64, inst), |
| ... | ... | @@ -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 | 366 | switch (arch) { |
| 251 | .x86_64 => switch (reg) { | |
| 252 | .rax => switch (mcv) { | |
| 253 | .none, .unreach => unreachable, | |
| 254 | .immediate => |x| { | |
| 255 | // Setting the eax register zeroes the upper part of rax, so if the number is small | |
| 256 | // enough, that is preferable. | |
| 257 | // Best case: zero | |
| 258 | // 31 c0 xor eax,eax | |
| 259 | if (x == 0) { | |
| 260 | return self.code.appendSlice(&[_]u8{ 0x31, 0xc0 }); | |
| 367 | .x86_64 => switch (mcv) { | |
| 368 | .none, .unreach => unreachable, | |
| 369 | .immediate => |x| { | |
| 370 | if (reg.size() != 64) { | |
| 371 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | |
| 372 | } | |
| 373 | // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit | |
| 374 | // register is the fastest way to zero a register. | |
| 375 | if (x == 0) { | |
| 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 | |
| 263 | // b8 04 03 02 01 mov eax,0x01020304 | |
| 264 | if (x <= std.math.maxInt(u32)) { | |
| 402 | } | |
| 403 | 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 | 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 | |
| 272 | // 48 b8 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708 | |
| 273 | try self.code.resize(self.code.items.len + 10); | |
| 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); | |
| 418 | self.code.items[self.code.items.len - 5] = 0xB8 | @as(u8, reg.id() & 0b111); | |
| 419 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | |
| 420 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); | |
| 278 | 421 | return; |
| 279 | }, | |
| 280 | .embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rax = embedded_in_code", .{}), | |
| 281 | .register => return self.fail(src, "TODO implement x86_64 genSetReg %rax = register", .{}), | |
| 422 | } | |
| 423 | // Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls | |
| 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) { | |
| 284 | .none, .unreach => unreachable, | |
| 285 | .immediate => |x| { | |
| 286 | // Setting the edx register zeroes the upper part of rdx, so if the number is small | |
| 287 | // enough, that is preferable. | |
| 288 | // Best case: zero | |
| 289 | // 31 d2 xor edx,edx | |
| 290 | if (x == 0) { | |
| 291 | return self.code.appendSlice(&[_]u8{ 0x31, 0xd2 }); | |
| 292 | } | |
| 293 | // Next best case: set edx with 4 bytes | |
| 294 | // ba 04 03 02 01 mov edx,0x1020304 | |
| 295 | if (x <= std.math.maxInt(u32)) { | |
| 296 | try self.code.resize(self.code.items.len + 5); | |
| 297 | self.code.items[self.code.items.len - 5] = 0xba; | |
| 298 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | |
| 299 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); | |
| 300 | return; | |
| 301 | } | |
| 302 | // Worst case: set rdx with 8 bytes | |
| 303 | // 48 ba 08 07 06 05 04 03 02 01 movabs rdx,0x0102030405060708 | |
| 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", .{}), | |
| 441 | .embedded_in_code => |code_offset| { | |
| 442 | if (reg.size() != 64) { | |
| 443 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | |
| 444 | } | |
| 445 | // We need the offset from RIP in a signed i32 twos complement. | |
| 446 | // The instruction is 7 bytes long and RIP points to the next instruction. | |
| 447 | // | |
| 448 | // 64-bit LEA is encoded as REX.W 8D /r. If the register is extended, the REX byte is modified, | |
| 449 | // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three | |
| 450 | // bits as five. | |
| 451 | // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id. | |
| 452 | try self.code.resize(self.code.items.len + 7); | |
| 453 | const REX = 0x48 | if (reg.isExtended()) @as(u8, 1) else 0; | |
| 454 | const rip = self.code.items.len; | |
| 455 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); | |
| 456 | const offset = @intCast(i32, big_offset); | |
| 457 | self.code.items[self.code.items.len - 7] = REX; | |
| 458 | self.code.items[self.code.items.len - 6] = 0x8D; | |
| 459 | self.code.items[self.code.items.len - 5] = 0b101 | (@as(u8, reg.id() & 0b111) << 3); | |
| 460 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | |
| 461 | mem.writeIntLittle(i32, imm_ptr, offset); | |
| 313 | 462 | }, |
| 314 | .rdi => switch (mcv) { | |
| 315 | .none, .unreach => unreachable, | |
| 316 | .immediate => |x| { | |
| 317 | // Setting the edi register zeroes the upper part of rdi, so if the number is small | |
| 318 | // enough, that is preferable. | |
| 319 | // Best case: zero | |
| 320 | // 31 ff xor edi,edi | |
| 321 | if (x == 0) { | |
| 322 | return self.code.appendSlice(&[_]u8{ 0x31, 0xff }); | |
| 323 | } | |
| 324 | // Next best case: set edi with 4 bytes | |
| 325 | // bf 04 03 02 01 mov edi,0x1020304 | |
| 326 | if (x <= std.math.maxInt(u32)) { | |
| 327 | try self.code.resize(self.code.items.len + 5); | |
| 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", .{}), | |
| 463 | .register => |r| { | |
| 464 | if (reg.size() != 64) { | |
| 465 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | |
| 466 | } | |
| 467 | const src_reg = @intToEnum(Reg(arch), @intCast(u8, r)); | |
| 468 | // This is a varient of 8B /r. Since we're using 64-bit moves, we require a REX. | |
| 469 | // This is thus three bytes: REX 0x8B R/M. | |
| 470 | // If the destination is extended, the R field must be 1. | |
| 471 | // If the *source* is extended, the B field must be 1. | |
| 472 | // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle | |
| 473 | // three bits) contain the destination, and the R/M field (the lower three bits) contain the source. | |
| 474 | const REX = 0x48 | (if (reg.isExtended()) @as(u8, 4) else 0) | (if (src_reg.isExtended()) @as(u8, 1) else 0); | |
| 475 | const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, src_reg.id()); | |
| 476 | try self.code.appendSlice(&[_]u8{ REX, 0x8B, R }); | |
| 344 | 477 | }, |
| 345 | .rsi => switch (mcv) { | |
| 346 | .none, .unreach => unreachable, | |
| 347 | .immediate => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = immediate", .{}), | |
| 348 | .embedded_in_code => |code_offset| { | |
| 349 | // Examples: | |
| 350 | // lea rsi, [rip + 0x01020304] | |
| 351 | // lea rsi, [rip - 7] | |
| 352 | // f: 48 8d 35 04 03 02 01 lea rsi,[rip+0x1020304] # 102031a <_start+0x102031a> | |
| 353 | // 16: 48 8d 35 f9 ff ff ff lea rsi,[rip+0xfffffffffffffff9] # 16 <_start+0x16> | |
| 354 | // | |
| 355 | // We need the offset from RIP in a signed i32 twos complement. | |
| 356 | // The instruction is 7 bytes long and RIP points to the next instruction. | |
| 357 | try self.code.resize(self.code.items.len + 7); | |
| 358 | const rip = self.code.items.len; | |
| 359 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); | |
| 360 | const offset = @intCast(i32, big_offset); | |
| 361 | self.code.items[self.code.items.len - 7] = 0x48; | |
| 362 | self.code.items[self.code.items.len - 6] = 0x8d; | |
| 363 | self.code.items[self.code.items.len - 5] = 0x35; | |
| 478 | .memory => |x| { | |
| 479 | if (reg.size() != 64) { | |
| 480 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); | |
| 481 | } | |
| 482 | if (x <= std.math.maxInt(u32)) { | |
| 483 | // Moving from memory to a register is a variant of `8B /r`. | |
| 484 | // Since we're using 64-bit moves, we require a REX. | |
| 485 | // This variant also requires a SIB, as it would otherwise be RIP-relative. | |
| 486 | // We want mode zero with the lower three bits set to four to indicate an SIB with no other displacement. | |
| 487 | // The SIB must be 0x25, to indicate a disp32 with no scaled index. | |
| 488 | // 0b00RRR100, where RRR is the lower three bits of the register ID. | |
| 489 | // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32. | |
| 490 | try self.code.resize(self.code.items.len + 8); | |
| 491 | const REX = 0x48 | if (reg.isExtended()) @as(u8, 1) else 0; | |
| 492 | const r = 0x04 | (@as(u8, reg.id() & 0b111) << 3); | |
| 493 | self.code.items[self.code.items.len - 8] = REX; | |
| 494 | self.code.items[self.code.items.len - 7] = 0x8B; | |
| 495 | self.code.items[self.code.items.len - 6] = r; | |
| 496 | self.code.items[self.code.items.len - 5] = 0x25; | |
| 364 | 497 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; |
| 365 | mem.writeIntLittle(i32, imm_ptr, offset); | |
| 366 | return; | |
| 367 | }, | |
| 368 | .register => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = register", .{}), | |
| 498 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); | |
| 499 | } else { | |
| 500 | // If this is RAX, we can use a direct load; otherwise, we need to load the address, then indirectly load | |
| 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 | 542 | else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}), |
| 373 | 543 | } |
| ... | ... | @@ -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 | 573 | switch (typed_value.ty.zigTypeTag()) { |
| 401 | 574 | .Pointer => { |
| 402 | const ptr_elem_type = typed_value.ty.elemType(); | |
| 403 | switch (ptr_elem_type.zigTypeTag()) { | |
| 404 | .Array => { | |
| 405 | // TODO more checks to make sure this can be emitted as a string literal | |
| 406 | const bytes = try typed_value.val.toAllocatedBytes(self.code.allocator); | |
| 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)}), | |
| 575 | if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| { | |
| 576 | const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?]; | |
| 577 | const decl = payload.decl; | |
| 578 | const got_addr = got.p_vaddr + decl.link.offset_table_index * ptr_bytes; | |
| 579 | return MCValue{ .memory = got_addr }; | |
| 418 | 580 | } |
| 581 | return self.fail(src, "TODO codegen more kinds of const pointers", .{}); | |
| 419 | 582 | }, |
| 420 | 583 | .Int => { |
| 421 | const info = typed_value.ty.intInfo(self.module.target); | |
| 422 | const ptr_bits = self.module.target.cpu.arch.ptrBitWidth(); | |
| 584 | const info = typed_value.ty.intInfo(self.target.*); | |
| 423 | 585 | if (info.bits > ptr_bits or info.signed) { |
| 424 | 586 | return self.fail(src, "TODO const int bigger than ptr and signed int", .{}); |
| 425 | 587 | } |
| ... | ... | @@ -433,127 +595,19 @@ const Function = struct { |
| 433 | 595 | |
| 434 | 596 | fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } { |
| 435 | 597 | @setCold(true); |
| 436 | const msg = try std.fmt.allocPrint(self.errors.allocator, format, args); | |
| 437 | { | |
| 438 | errdefer self.errors.allocator.free(msg); | |
| 439 | (try self.errors.addOne()).* = .{ | |
| 440 | .byte_offset = src, | |
| 441 | .msg = msg, | |
| 442 | }; | |
| 443 | } | |
| 598 | assert(self.err_msg == null); | |
| 599 | self.err_msg = try ErrorMsg.create(self.code.allocator, src, format, args); | |
| 444 | 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 | 607 | fn Reg(comptime arch: Target.Cpu.Arch) type { |
| 449 | 608 | return switch (arch) { |
| 450 | .i386 => enum { | |
| 451 | eax, | |
| 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 | }, | |
| 609 | .i386 => x86.Register, | |
| 610 | .x86_64 => x86_64.Register, | |
| 557 | 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 | 1 | const std = @import("std"); |
| 2 | const mem = std.mem; | |
| 3 | const Allocator = std.mem.Allocator; | |
| 4 | 2 | const Value = @import("value.zig").Value; |
| 5 | 3 | const Type = @import("type.zig").Type; |
| 6 | const assert = std.debug.assert; | |
| 7 | const BigIntConst = std.math.big.int.Const; | |
| 8 | const BigIntMutable = std.math.big.int.Mutable; | |
| 9 | const Target = std.Target; | |
| 4 | const Module = @import("Module.zig"); | |
| 10 | 5 | |
| 11 | pub const text = @import("ir/text.zig"); | |
| 12 | ||
| 13 | /// These are in-memory, analyzed instructions. See `text.Inst` for the representation | |
| 6 | /// These are in-memory, analyzed instructions. See `zir.Inst` for the representation | |
| 14 | 7 | /// of instructions that correspond to the ZIR text format. |
| 15 | 8 | /// This struct owns the `Value` and `Type` memory. When the struct is deallocated, |
| 16 | 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 | 18 | assembly, |
| 26 | 19 | bitcast, |
| 27 | 20 | breakpoint, |
| 21 | call, | |
| 28 | 22 | cmp, |
| 29 | 23 | condbr, |
| 30 | 24 | constant, |
| ... | ... | @@ -84,6 +78,15 @@ pub const Inst = struct { |
| 84 | 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 | 90 | pub const Cmp = struct { |
| 88 | 91 | pub const base_tag = Tag.cmp; |
| 89 | 92 | |
| ... | ... | @@ -152,1200 +155,3 @@ pub const Inst = struct { |
| 152 | 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 | 1 | const std = @import("std"); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | const util = @import("util.zig"); | |
| 4 | 3 | const Target = std.Target; |
| 5 | 4 | const fs = std.fs; |
| 6 | 5 | const Allocator = std.mem.Allocator; |
src-self-hosted/link.zig+749-291| ... | ... | @@ -3,56 +3,74 @@ const mem = std.mem; |
| 3 | 3 | const assert = std.debug.assert; |
| 4 | 4 | const Allocator = std.mem.Allocator; |
| 5 | 5 | const ir = @import("ir.zig"); |
| 6 | const Module = @import("Module.zig"); | |
| 6 | 7 | const fs = std.fs; |
| 7 | 8 | const elf = std.elf; |
| 8 | 9 | const codegen = @import("codegen.zig"); |
| 9 | 10 | |
| 10 | 11 | const default_entry_addr = 0x8000000; |
| 11 | 12 | |
| 12 | pub const ErrorMsg = struct { | |
| 13 | byte_offset: usize, | |
| 14 | msg: []const u8, | |
| 15 | }; | |
| 16 | ||
| 17 | pub const Result = struct { | |
| 18 | errors: []ErrorMsg, | |
| 19 | ||
| 20 | pub fn deinit(self: *Result, allocator: *mem.Allocator) void { | |
| 21 | for (self.errors) |err| { | |
| 22 | allocator.free(err.msg); | |
| 23 | } | |
| 24 | allocator.free(self.errors); | |
| 25 | self.* = undefined; | |
| 26 | } | |
| 13 | pub const Options = struct { | |
| 14 | target: std.Target, | |
| 15 | output_mode: std.builtin.OutputMode, | |
| 16 | link_mode: std.builtin.LinkMode, | |
| 17 | object_format: std.builtin.ObjectFormat, | |
| 18 | /// Used for calculating how much space to reserve for symbols in case the binary file | |
| 19 | /// does not already have a symbol table. | |
| 20 | symbol_count_hint: u64 = 32, | |
| 21 | /// Used for calculating how much space to reserve for executable program code in case | |
| 22 | /// the binary file deos not already have such a section. | |
| 23 | program_code_size_hint: u64 = 256 * 1024, | |
| 27 | 24 | }; |
| 28 | 25 | |
| 29 | 26 | /// Attempts incremental linking, if the file already exists. |
| 30 | 27 | /// If incremental linking fails, falls back to truncating the file and rewriting it. |
| 31 | 28 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. |
| 32 | 29 | /// This operation is not atomic. |
| 33 | pub fn updateFilePath( | |
| 30 | pub fn openBinFilePath( | |
| 34 | 31 | allocator: *Allocator, |
| 35 | module: ir.Module, | |
| 36 | 32 | dir: fs.Dir, |
| 37 | 33 | sub_path: []const u8, |
| 38 | ) !Result { | |
| 39 | const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(module) }); | |
| 40 | defer file.close(); | |
| 41 | ||
| 42 | return updateFile(allocator, module, file); | |
| 34 | options: Options, | |
| 35 | ) !ElfFile { | |
| 36 | const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) }); | |
| 37 | errdefer file.close(); | |
| 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 | 44 | /// Atomically overwrites the old file, if present. |
| 46 | 45 | pub fn writeFilePath( |
| 47 | 46 | allocator: *Allocator, |
| 48 | module: ir.Module, | |
| 49 | 47 | dir: fs.Dir, |
| 50 | 48 | sub_path: []const u8, |
| 51 | ) !Result { | |
| 52 | const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(module) }); | |
| 49 | module: 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 | 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 | 74 | try af.finish(); |
| 57 | 75 | return result; |
| 58 | 76 | } |
| ... | ... | @@ -62,58 +80,126 @@ pub fn writeFilePath( |
| 62 | 80 | /// Returns an error if `file` is not already open with +read +write +seek abilities. |
| 63 | 81 | /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. |
| 64 | 82 | /// This operation is not atomic. |
| 65 | pub fn updateFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | |
| 66 | return updateFileInner(allocator, module, file) catch |err| switch (err) { | |
| 83 | pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !ElfFile { | |
| 84 | return openBinFileInner(allocator, file, options) catch |err| switch (err) { | |
| 67 | 85 | error.IncrFailed => { |
| 68 | return writeFile(allocator, module, file); | |
| 86 | return createElfFile(allocator, file, options); | |
| 69 | 87 | }, |
| 70 | 88 | else => |e| return e, |
| 71 | 89 | }; |
| 72 | 90 | } |
| 73 | 91 | |
| 74 | const Update = struct { | |
| 75 | file: fs.File, | |
| 76 | module: *const ir.Module, | |
| 92 | pub const ElfFile = struct { | |
| 93 | allocator: *Allocator, | |
| 94 | file: ?fs.File, | |
| 95 | owns_file_handle: bool, | |
| 96 | options: Options, | |
| 97 | ptr_width: enum { p32, p64 }, | |
| 77 | 98 | |
| 78 | 99 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. |
| 79 | 100 | /// Same order as in the file. |
| 80 | sections: std.ArrayList(elf.Elf64_Shdr), | |
| 81 | shdr_table_offset: ?u64, | |
| 101 | sections: std.ArrayListUnmanaged(elf.Elf64_Shdr) = std.ArrayListUnmanaged(elf.Elf64_Shdr){}, | |
| 102 | shdr_table_offset: ?u64 = null, | |
| 82 | 103 | |
| 83 | 104 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. |
| 84 | 105 | /// Same order as in the file. |
| 85 | program_headers: std.ArrayList(elf.Elf64_Phdr), | |
| 86 | phdr_table_offset: ?u64, | |
| 106 | program_headers: std.ArrayListUnmanaged(elf.Elf64_Phdr) = std.ArrayListUnmanaged(elf.Elf64_Phdr){}, | |
| 107 | phdr_table_offset: ?u64 = null, | |
| 87 | 108 | /// The index into the program headers of a PT_LOAD program header with Read and Execute flags |
| 88 | phdr_load_re_index: ?u16, | |
| 89 | entry_addr: ?u64, | |
| 109 | phdr_load_re_index: ?u16 = null, | |
| 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), | |
| 92 | shstrtab_index: ?u16, | |
| 145 | pub const Decl = struct { | |
| 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, | |
| 95 | symtab_section_index: ?u16, | |
| 162 | pub const Export = struct { | |
| 163 | sym_index: ?u32 = null, | |
| 164 | }; | |
| 96 | 165 | |
| 97 | /// The same order as in the file | |
| 98 | symbols: std.ArrayList(elf.Elf64_Sym), | |
| 166 | pub fn deinit(self: *ElfFile) void { | |
| 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 { | |
| 103 | self.sections.deinit(); | |
| 104 | self.program_headers.deinit(); | |
| 105 | self.shstrtab.deinit(); | |
| 106 | self.symbols.deinit(); | |
| 107 | self.errors.deinit(); | |
| 186 | pub fn makeWritable(self: *ElfFile, dir: fs.Dir, sub_path: []const u8) !void { | |
| 187 | assert(self.owns_file_handle); | |
| 188 | if (self.file != null) return; | |
| 189 | self.file = try dir.createFile(sub_path, .{ | |
| 190 | .truncate = false, | |
| 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 | 197 | const alloc_num = 4; |
| 112 | 198 | const alloc_den = 3; |
| 113 | 199 | |
| 114 | 200 | /// Returns end pos of collision, if any. |
| 115 | fn detectAllocCollision(self: *Update, start: u64, size: u64) ?u64 { | |
| 116 | const small_ptr = self.module.target.cpu.arch.ptrBitWidth() == 32; | |
| 201 | fn detectAllocCollision(self: *ElfFile, start: u64, size: u64) ?u64 { | |
| 202 | const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32; | |
| 117 | 203 | const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); |
| 118 | 204 | if (start < ehdr_size) |
| 119 | 205 | return ehdr_size; |
| ... | ... | @@ -157,7 +243,7 @@ const Update = struct { |
| 157 | 243 | return null; |
| 158 | 244 | } |
| 159 | 245 | |
| 160 | fn allocatedSize(self: *Update, start: u64) u64 { | |
| 246 | fn allocatedSize(self: *ElfFile, start: u64) u64 { | |
| 161 | 247 | var min_pos: u64 = std.math.maxInt(u64); |
| 162 | 248 | if (self.shdr_table_offset) |off| { |
| 163 | 249 | if (off > start and off < min_pos) min_pos = off; |
| ... | ... | @@ -176,7 +262,7 @@ const Update = struct { |
| 176 | 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 | 266 | var start: u64 = 0; |
| 181 | 267 | while (self.detectAllocCollision(start, object_size)) |item_end| { |
| 182 | 268 | start = mem.alignForwardGeneric(u64, item_end, min_alignment); |
| ... | ... | @@ -184,73 +270,86 @@ const Update = struct { |
| 184 | 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 | 275 | const result = self.shstrtab.items.len; |
| 189 | try self.shstrtab.appendSlice(bytes); | |
| 190 | try self.shstrtab.append(0); | |
| 276 | self.shstrtab.appendSliceAssumeCapacity(bytes); | |
| 277 | self.shstrtab.appendAssumeCapacity(0); | |
| 191 | 278 | return @intCast(u32, result); |
| 192 | 279 | } |
| 193 | 280 | |
| 194 | fn perform(self: *Update) !void { | |
| 195 | const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) { | |
| 196 | 32 => .p32, | |
| 197 | 64 => .p64, | |
| 198 | else => return error.UnsupportedArchitecture, | |
| 199 | }; | |
| 200 | const small_ptr = switch (ptr_width) { | |
| 281 | fn getString(self: *ElfFile, str_off: u32) []const u8 { | |
| 282 | assert(str_off < self.shstrtab.items.len); | |
| 283 | return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off)); | |
| 284 | } | |
| 285 | ||
| 286 | fn updateString(self: *ElfFile, old_str_off: u32, new_name: []const u8) !u32 { | |
| 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 | 296 | .p32 => true, |
| 202 | 297 | .p64 => false, |
| 203 | 298 | }; |
| 204 | // This means the entire read-only executable program code needs to be rewritten. | |
| 205 | var phdr_load_re_dirty = false; | |
| 206 | var phdr_table_dirty = false; | |
| 207 | var shdr_table_dirty = false; | |
| 208 | var shstrtab_dirty = false; | |
| 209 | var symtab_dirty = false; | |
| 210 | ||
| 299 | const ptr_size: u8 = switch (self.ptr_width) { | |
| 300 | .p32 => 4, | |
| 301 | .p64 => 8, | |
| 302 | }; | |
| 211 | 303 | if (self.phdr_load_re_index == null) { |
| 212 | 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 | 306 | const p_align = 0x1000; |
| 215 | 307 | const off = self.findFreeSpace(file_size, p_align); |
| 216 | 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 | 310 | .p_type = elf.PT_LOAD, |
| 219 | 311 | .p_offset = off, |
| 220 | 312 | .p_filesz = file_size, |
| 221 | 313 | .p_vaddr = default_entry_addr, |
| 222 | 314 | .p_paddr = default_entry_addr, |
| 223 | .p_memsz = 0, | |
| 315 | .p_memsz = file_size, | |
| 224 | 316 | .p_align = p_align, |
| 225 | 317 | .p_flags = elf.PF_X | elf.PF_R, |
| 226 | 318 | }); |
| 227 | 319 | self.entry_addr = null; |
| 228 | phdr_load_re_dirty = true; | |
| 229 | phdr_table_dirty = true; | |
| 230 | } | |
| 231 | if (self.sections.items.len == 0) { | |
| 232 | // There must always be a null section in index 0 | |
| 233 | try self.sections.append(.{ | |
| 234 | .sh_name = 0, | |
| 235 | .sh_type = elf.SHT_NULL, | |
| 236 | .sh_flags = 0, | |
| 237 | .sh_addr = 0, | |
| 238 | .sh_offset = 0, | |
| 239 | .sh_size = 0, | |
| 240 | .sh_link = 0, | |
| 241 | .sh_info = 0, | |
| 242 | .sh_addralign = 0, | |
| 243 | .sh_entsize = 0, | |
| 320 | self.phdr_table_dirty = true; | |
| 321 | } | |
| 322 | if (self.phdr_got_index == null) { | |
| 323 | self.phdr_got_index = @intCast(u16, self.program_headers.items.len); | |
| 324 | const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint; | |
| 325 | // We really only need ptr alignment but since we are using PROGBITS, linux requires | |
| 326 | // page align. | |
| 327 | const p_align = 0x1000; | |
| 328 | const off = self.findFreeSpace(file_size, p_align); | |
| 329 | //std.debug.warn("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 330 | // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. | |
| 331 | // we'll need to re-use that function anyway, in case the GOT grows and overlaps something | |
| 332 | // else in virtual memory. | |
| 333 | const default_got_addr = 0x4000000; | |
| 334 | try self.program_headers.append(self.allocator, .{ | |
| 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 | 346 | if (self.shstrtab_index == null) { |
| 248 | 347 | self.shstrtab_index = @intCast(u16, self.sections.items.len); |
| 249 | 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 | 350 | const off = self.findFreeSpace(self.shstrtab.items.len, 1); |
| 252 | 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 | 353 | .sh_name = try self.makeString(".shstrtab"), |
| 255 | 354 | .sh_type = elf.SHT_STRTAB, |
| 256 | 355 | .sh_flags = 0, |
| ... | ... | @@ -262,14 +361,14 @@ const Update = struct { |
| 262 | 361 | .sh_addralign = 1, |
| 263 | 362 | .sh_entsize = 0, |
| 264 | 363 | }); |
| 265 | shstrtab_dirty = true; | |
| 266 | shdr_table_dirty = true; | |
| 364 | self.shstrtab_dirty = true; | |
| 365 | self.shdr_table_dirty = true; | |
| 267 | 366 | } |
| 268 | 367 | if (self.text_section_index == null) { |
| 269 | 368 | self.text_section_index = @intCast(u16, self.sections.items.len); |
| 270 | 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 | 372 | .sh_name = try self.makeString(".text"), |
| 274 | 373 | .sh_type = elf.SHT_PROGBITS, |
| 275 | 374 | .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, |
| ... | ... | @@ -281,17 +380,35 @@ const Update = struct { |
| 281 | 380 | .sh_addralign = phdr.p_align, |
| 282 | 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 | 403 | if (self.symtab_section_index == null) { |
| 287 | 404 | self.symtab_section_index = @intCast(u16, self.sections.items.len); |
| 288 | 405 | const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); |
| 289 | 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 | 408 | const off = self.findFreeSpace(file_size, min_align); |
| 292 | 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 | 412 | .sh_name = try self.makeString(".symtab"), |
| 296 | 413 | .sh_type = elf.SHT_SYMTAB, |
| 297 | 414 | .sh_flags = 0, |
| ... | ... | @@ -300,42 +417,56 @@ const Update = struct { |
| 300 | 417 | .sh_size = file_size, |
| 301 | 418 | // The section header index of the associated string table. |
| 302 | 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 | 421 | .sh_addralign = min_align, |
| 305 | 422 | .sh_entsize = each_size, |
| 306 | 423 | }); |
| 307 | symtab_dirty = true; | |
| 308 | shdr_table_dirty = true; | |
| 424 | self.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 | 428 | .p32 => @sizeOf(elf.Elf32_Shdr), |
| 312 | 429 | .p64 => @sizeOf(elf.Elf64_Shdr), |
| 313 | 430 | }; |
| 314 | const shalign: u16 = switch (ptr_width) { | |
| 431 | const shalign: u16 = switch (self.ptr_width) { | |
| 315 | 432 | .p32 => @alignOf(elf.Elf32_Shdr), |
| 316 | 433 | .p64 => @alignOf(elf.Elf64_Shdr), |
| 317 | 434 | }; |
| 318 | 435 | if (self.shdr_table_offset == null) { |
| 319 | 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 | 440 | .p32 => @sizeOf(elf.Elf32_Phdr), |
| 324 | 441 | .p64 => @sizeOf(elf.Elf64_Phdr), |
| 325 | 442 | }; |
| 326 | const phalign: u16 = switch (ptr_width) { | |
| 443 | const phalign: u16 = switch (self.ptr_width) { | |
| 327 | 444 | .p32 => @alignOf(elf.Elf32_Phdr), |
| 328 | 445 | .p64 => @alignOf(elf.Elf64_Phdr), |
| 329 | 446 | }; |
| 330 | 447 | if (self.phdr_table_offset == null) { |
| 331 | 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 | 470 | const allocated_size = self.allocatedSize(self.phdr_table_offset.?); |
| 340 | 471 | const needed_size = self.program_headers.items.len * phsize; |
| 341 | 472 | |
| ... | ... | @@ -344,11 +475,10 @@ const Update = struct { |
| 344 | 475 | self.phdr_table_offset = self.findFreeSpace(needed_size, phalign); |
| 345 | 476 | } |
| 346 | 477 | |
| 347 | const allocator = self.program_headers.allocator; | |
| 348 | switch (ptr_width) { | |
| 478 | switch (self.ptr_width) { | |
| 349 | 479 | .p32 => { |
| 350 | const buf = try allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len); | |
| 351 | defer allocator.free(buf); | |
| 480 | const buf = try self.allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len); | |
| 481 | defer self.allocator.free(buf); | |
| 352 | 482 | |
| 353 | 483 | for (buf) |*phdr, i| { |
| 354 | 484 | phdr.* = progHeaderTo32(self.program_headers.items[i]); |
| ... | ... | @@ -356,11 +486,11 @@ const Update = struct { |
| 356 | 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 | 491 | .p64 => { |
| 362 | const buf = try allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len); | |
| 363 | defer allocator.free(buf); | |
| 492 | const buf = try self.allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len); | |
| 493 | defer self.allocator.free(buf); | |
| 364 | 494 | |
| 365 | 495 | for (buf) |*phdr, i| { |
| 366 | 496 | phdr.* = self.program_headers.items[i]; |
| ... | ... | @@ -368,14 +498,15 @@ const Update = struct { |
| 368 | 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 | 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 | 510 | const allocated_size = self.allocatedSize(shstrtab_sect.sh_offset); |
| 380 | 511 | const needed_size = self.shstrtab.items.len; |
| 381 | 512 | |
| ... | ... | @@ -386,27 +517,35 @@ const Update = struct { |
| 386 | 517 | shstrtab_sect.sh_size = needed_size; |
| 387 | 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); | |
| 390 | if (!shdr_table_dirty) { | |
| 520 | try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); | |
| 521 | if (!self.shdr_table_dirty) { | |
| 391 | 522 | // Then it won't get written with the others and we need to do it. |
| 392 | 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 | 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 | 540 | if (needed_size > allocated_size) { |
| 401 | 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; | |
| 406 | switch (ptr_width) { | |
| 545 | switch (self.ptr_width) { | |
| 407 | 546 | .p32 => { |
| 408 | const buf = try allocator.alloc(elf.Elf32_Shdr, self.sections.items.len); | |
| 409 | defer allocator.free(buf); | |
| 547 | const buf = try self.allocator.alloc(elf.Elf32_Shdr, self.sections.items.len); | |
| 548 | defer self.allocator.free(buf); | |
| 410 | 549 | |
| 411 | 550 | for (buf) |*shdr, i| { |
| 412 | 551 | shdr.* = sectHeaderTo32(self.sections.items[i]); |
| ... | ... | @@ -414,11 +553,11 @@ const Update = struct { |
| 414 | 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 | 558 | .p64 => { |
| 420 | const buf = try allocator.alloc(elf.Elf64_Shdr, self.sections.items.len); | |
| 421 | defer allocator.free(buf); | |
| 559 | const buf = try self.allocator.alloc(elf.Elf64_Shdr, self.sections.items.len); | |
| 560 | defer self.allocator.free(buf); | |
| 422 | 561 | |
| 423 | 562 | for (buf) |*shdr, i| { |
| 424 | 563 | shdr.* = self.sections.items[i]; |
| ... | ... | @@ -427,42 +566,42 @@ const Update = struct { |
| 427 | 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) { | |
| 435 | const msg = try std.fmt.allocPrint(self.errors.allocator, "no entry point found", .{}); | |
| 436 | errdefer self.errors.allocator.free(msg); | |
| 437 | try self.errors.append(.{ | |
| 438 | .byte_offset = 0, | |
| 439 | .msg = msg, | |
| 440 | }); | |
| 574 | if (self.entry_addr == null and self.options.output_mode == .Exe) { | |
| 575 | self.error_flags.no_entry_point_found = true; | |
| 441 | 576 | } else { |
| 577 | self.error_flags.no_entry_point_found = false; | |
| 442 | 578 | try self.writeElfHeader(); |
| 443 | 579 | } |
| 444 | 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 | 592 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; |
| 449 | 593 | |
| 450 | 594 | var index: usize = 0; |
| 451 | 595 | hdr_buf[0..4].* = "\x7fELF".*; |
| 452 | 596 | index += 4; |
| 453 | 597 | |
| 454 | const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) { | |
| 455 | 32 => .p32, | |
| 456 | 64 => .p64, | |
| 457 | else => return error.UnsupportedArchitecture, | |
| 458 | }; | |
| 459 | hdr_buf[index] = switch (ptr_width) { | |
| 598 | hdr_buf[index] = switch (self.ptr_width) { | |
| 460 | 599 | .p32 => elf.ELFCLASS32, |
| 461 | 600 | .p64 => elf.ELFCLASS64, |
| 462 | 601 | }; |
| 463 | 602 | index += 1; |
| 464 | 603 | |
| 465 | const endian = self.module.target.cpu.arch.endian(); | |
| 604 | const endian = self.options.target.cpu.arch.endian(); | |
| 466 | 605 | hdr_buf[index] = switch (endian) { |
| 467 | 606 | .Little => elf.ELFDATA2LSB, |
| 468 | 607 | .Big => elf.ELFDATA2MSB, |
| ... | ... | @@ -480,10 +619,10 @@ const Update = struct { |
| 480 | 619 | |
| 481 | 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 | 623 | .Exe => elf.ET.EXEC, |
| 485 | 624 | .Obj => elf.ET.REL, |
| 486 | .Lib => switch (self.module.link_mode) { | |
| 625 | .Lib => switch (self.options.link_mode) { | |
| 487 | 626 | .Static => elf.ET.REL, |
| 488 | 627 | .Dynamic => elf.ET.DYN, |
| 489 | 628 | }, |
| ... | ... | @@ -491,7 +630,7 @@ const Update = struct { |
| 491 | 630 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian); |
| 492 | 631 | index += 2; |
| 493 | 632 | |
| 494 | const machine = self.module.target.cpu.arch.toElfMachine(); | |
| 633 | const machine = self.options.target.cpu.arch.toElfMachine(); | |
| 495 | 634 | mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian); |
| 496 | 635 | index += 2; |
| 497 | 636 | |
| ... | ... | @@ -501,7 +640,7 @@ const Update = struct { |
| 501 | 640 | |
| 502 | 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 | 644 | .p32 => { |
| 506 | 645 | mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian); |
| 507 | 646 | index += 4; |
| ... | ... | @@ -533,14 +672,14 @@ const Update = struct { |
| 533 | 672 | mem.writeInt(u32, hdr_buf[index..][0..4], e_flags, endian); |
| 534 | 673 | index += 4; |
| 535 | 674 | |
| 536 | const e_ehsize: u16 = switch (ptr_width) { | |
| 675 | const e_ehsize: u16 = switch (self.ptr_width) { | |
| 537 | 676 | .p32 => @sizeOf(elf.Elf32_Ehdr), |
| 538 | 677 | .p64 => @sizeOf(elf.Elf64_Ehdr), |
| 539 | 678 | }; |
| 540 | 679 | mem.writeInt(u16, hdr_buf[index..][0..2], e_ehsize, endian); |
| 541 | 680 | index += 2; |
| 542 | 681 | |
| 543 | const e_phentsize: u16 = switch (ptr_width) { | |
| 682 | const e_phentsize: u16 = switch (self.ptr_width) { | |
| 544 | 683 | .p32 => @sizeOf(elf.Elf32_Phdr), |
| 545 | 684 | .p64 => @sizeOf(elf.Elf64_Phdr), |
| 546 | 685 | }; |
| ... | ... | @@ -551,7 +690,7 @@ const Update = struct { |
| 551 | 690 | mem.writeInt(u16, hdr_buf[index..][0..2], e_phnum, endian); |
| 552 | 691 | index += 2; |
| 553 | 692 | |
| 554 | const e_shentsize: u16 = switch (ptr_width) { | |
| 693 | const e_shentsize: u16 = switch (self.ptr_width) { | |
| 555 | 694 | .p32 => @sizeOf(elf.Elf32_Shdr), |
| 556 | 695 | .p64 => @sizeOf(elf.Elf64_Shdr), |
| 557 | 696 | }; |
| ... | ... | @@ -567,186 +706,463 @@ const Update = struct { |
| 567 | 706 | |
| 568 | 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 { | |
| 574 | // index 0 is always a null symbol | |
| 575 | try self.symbols.resize(1); | |
| 576 | self.symbols.items[0] = .{ | |
| 712 | const AllocatedBlock = struct { | |
| 713 | vaddr: u64, | |
| 714 | file_offset: u64, | |
| 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 | 788 | .st_name = 0, |
| 578 | 789 | .st_info = 0, |
| 579 | 790 | .st_other = 0, |
| 580 | 791 | .st_shndx = 0, |
| 581 | .st_value = 0, | |
| 792 | .st_value = phdr.p_vaddr, | |
| 582 | 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.?]; | |
| 586 | var vaddr: u64 = phdr.p_vaddr; | |
| 587 | var file_off: u64 = phdr.p_offset; | |
| 588 | ||
| 589 | var code = std.ArrayList(u8).init(self.sections.allocator); | |
| 590 | defer code.deinit(); | |
| 591 | ||
| 592 | for (self.module.exports) |exp| { | |
| 593 | code.shrink(0); | |
| 594 | var symbol = try codegen.generateSymbol(exp.typed_value, self.module.*, &code); | |
| 595 | defer symbol.deinit(code.allocator); | |
| 596 | if (symbol.errors.len != 0) { | |
| 597 | for (symbol.errors) |err| { | |
| 598 | const msg = try mem.dupe(self.errors.allocator, u8, err.msg); | |
| 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); | |
| 807 | pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void { | |
| 808 | var code_buffer = std.ArrayList(u8).init(self.allocator); | |
| 809 | defer code_buffer.deinit(); | |
| 810 | ||
| 811 | const typed_value = decl.typed_value.most_recent.typed_value; | |
| 812 | const code = switch (try codegen.generateSymbol(self, decl.src, typed_value, &code_buffer)) { | |
| 813 | .externally_managed => |x| x, | |
| 814 | .appended => code_buffer.items, | |
| 815 | .fail => |em| { | |
| 816 | decl.analysis = .codegen_failure; | |
| 817 | _ = try module.failed_decls.put(decl, em); | |
| 818 | return; | |
| 819 | }, | |
| 820 | }; | |
| 608 | 821 | |
| 609 | if (mem.eql(u8, exp.name, "_start")) { | |
| 610 | self.entry_addr = vaddr; | |
| 611 | } | |
| 612 | (try self.symbols.addOne()).* = .{ | |
| 613 | .st_name = try self.makeString(exp.name), | |
| 614 | .st_info = (elf.STB_LOCAL << 4) | elf.STT_FUNC, | |
| 615 | .st_other = 0, | |
| 616 | .st_shndx = self.text_section_index.?, | |
| 617 | .st_value = vaddr, | |
| 618 | .st_size = code.items.len, | |
| 822 | const required_alignment = typed_value.ty.abiAlignment(self.options.target); | |
| 823 | ||
| 824 | const file_offset = blk: { | |
| 825 | const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) { | |
| 826 | .Fn => elf.STT_FUNC, | |
| 827 | else => elf.STT_OBJECT, | |
| 619 | 828 | }; |
| 620 | vaddr += code.items.len; | |
| 621 | } | |
| 622 | 829 | |
| 623 | { | |
| 624 | // Now that we know the code size, we need to update the program header for executable code | |
| 625 | phdr.p_memsz = vaddr - phdr.p_vaddr; | |
| 626 | phdr.p_filesz = phdr.p_memsz; | |
| 830 | if (decl.link.local_sym_index != 0) { | |
| 831 | const local_sym = &self.local_symbols.items[decl.link.local_sym_index]; | |
| 832 | const existing_block = self.findAllocatedTextBlock(local_sym.*); | |
| 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.?]; | |
| 629 | shdr.sh_size = phdr.p_filesz; | |
| 900 | // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. | |
| 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) { | |
| 632 | // Then it won't get written with the others and we need to do it. | |
| 633 | try self.writeProgHeader(self.phdr_load_re_index.?); | |
| 905 | /// Must be called only after a successful call to `updateDecl`. | |
| 906 | pub fn updateDeclExports( | |
| 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) { | |
| 636 | // Then it won't get written with the others and we need to do it. | |
| 637 | try self.writeSectHeader(self.text_section_index.?); | |
| 928 | const stb_bits: u8 = switch (exp.options.linkage) { | |
| 929 | .Internal => elf.STB_LOCAL, | |
| 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 { | |
| 645 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 975 | fn writeProgHeader(self: *ElfFile, index: usize) !void { | |
| 976 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 646 | 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 | 979 | 32 => { |
| 649 | 980 | var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; |
| 650 | 981 | if (foreign_endian) { |
| 651 | 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 | 986 | 64 => { |
| 656 | 987 | var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; |
| 657 | 988 | if (foreign_endian) { |
| 658 | 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 | 993 | else => return error.UnsupportedArchitecture, |
| 663 | 994 | } |
| 664 | 995 | } |
| 665 | 996 | |
| 666 | fn writeSectHeader(self: *Update, index: usize) !void { | |
| 667 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 997 | fn writeSectHeader(self: *ElfFile, index: usize) !void { | |
| 998 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 668 | 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 | 1001 | 32 => { |
| 671 | 1002 | var shdr: [1]elf.Elf32_Shdr = undefined; |
| 672 | 1003 | shdr[0] = sectHeaderTo32(self.sections.items[index]); |
| 673 | 1004 | if (foreign_endian) { |
| 674 | 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 | 1009 | 64 => { |
| 679 | 1010 | var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; |
| 680 | 1011 | if (foreign_endian) { |
| 681 | 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 | 1016 | else => return error.UnsupportedArchitecture, |
| 686 | 1017 | } |
| 687 | 1018 | } |
| 688 | 1019 | |
| 689 | fn writeSymbols(self: *Update) !void { | |
| 690 | const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) { | |
| 691 | 32 => .p32, | |
| 692 | 64 => .p64, | |
| 693 | else => return error.UnsupportedArchitecture, | |
| 1020 | fn writeOffsetTableEntry(self: *ElfFile, index: usize) !void { | |
| 1021 | const shdr = &self.sections.items[self.got_section_index.?]; | |
| 1022 | const phdr = &self.program_headers.items[self.phdr_got_index.?]; | |
| 1023 | const entry_size: u16 = switch (self.ptr_width) { | |
| 1024 | .p32 => 4, | |
| 1025 | .p64 => 8, | |
| 694 | 1026 | }; |
| 695 | const small_ptr = ptr_width == .p32; | |
| 696 | const syms_sect = &self.sections.items[self.symtab_section_index.?]; | |
| 697 | const sym_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); | |
| 698 | const sym_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); | |
| 1027 | if (self.offset_table_count_dirty) { | |
| 1028 | // TODO Also detect virtual address collisions. | |
| 1029 | const allocated_size = self.allocatedSize(shdr.sh_offset); | |
| 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); | |
| 701 | const needed_size = self.symbols.items.len * sym_size; | |
| 702 | if (needed_size > allocated_size) { | |
| 703 | syms_sect.sh_size = 0; // free the space | |
| 704 | syms_sect.sh_offset = self.findFreeSpace(needed_size, sym_align); | |
| 705 | //std.debug.warn("moved symtab to 0x{x} to 0x{x}\n", .{ syms_sect.sh_offset, syms_sect.sh_offset + needed_size }); | |
| 1063 | fn writeSymbol(self: *ElfFile, index: usize) !void { | |
| 1064 | const syms_sect = &self.sections.items[self.symtab_section_index.?]; | |
| 1065 | // Make sure we are not pointlessly writing symbol data that will have to get relocated | |
| 1066 | // due to running out of space. | |
| 1067 | if (self.local_symbols.items.len != syms_sect.sh_info) { | |
| 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 | 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; | |
| 709 | syms_sect.sh_info = @intCast(u32, self.symbols.items.len); | |
| 710 | const allocator = self.symbols.allocator; | |
| 711 | const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 712 | switch (ptr_width) { | |
| 1126 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | |
| 1127 | const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size; | |
| 1128 | switch (self.ptr_width) { | |
| 713 | 1129 | .p32 => { |
| 714 | const buf = try allocator.alloc(elf.Elf32_Sym, self.symbols.items.len); | |
| 715 | defer allocator.free(buf); | |
| 1130 | const buf = try self.allocator.alloc(elf.Elf32_Sym, self.global_symbols.items.len); | |
| 1131 | defer self.allocator.free(buf); | |
| 716 | 1132 | |
| 717 | 1133 | for (buf) |*sym, i| { |
| 718 | 1134 | sym.* = .{ |
| 719 | .st_name = self.symbols.items[i].st_name, | |
| 720 | .st_value = @intCast(u32, self.symbols.items[i].st_value), | |
| 721 | .st_size = @intCast(u32, self.symbols.items[i].st_size), | |
| 722 | .st_info = self.symbols.items[i].st_info, | |
| 723 | .st_other = self.symbols.items[i].st_other, | |
| 724 | .st_shndx = self.symbols.items[i].st_shndx, | |
| 1135 | .st_name = self.global_symbols.items[i].st_name, | |
| 1136 | .st_value = @intCast(u32, self.global_symbols.items[i].st_value), | |
| 1137 | .st_size = @intCast(u32, self.global_symbols.items[i].st_size), | |
| 1138 | .st_info = self.global_symbols.items[i].st_info, | |
| 1139 | .st_other = self.global_symbols.items[i].st_other, | |
| 1140 | .st_shndx = self.global_symbols.items[i].st_shndx, | |
| 725 | 1141 | }; |
| 726 | 1142 | if (foreign_endian) { |
| 727 | 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 | 1148 | .p64 => { |
| 733 | const buf = try allocator.alloc(elf.Elf64_Sym, self.symbols.items.len); | |
| 734 | defer allocator.free(buf); | |
| 1149 | const buf = try self.allocator.alloc(elf.Elf64_Sym, self.global_symbols.items.len); | |
| 1150 | defer self.allocator.free(buf); | |
| 735 | 1151 | |
| 736 | 1152 | for (buf) |*sym, i| { |
| 737 | 1153 | sym.* = .{ |
| 738 | .st_name = self.symbols.items[i].st_name, | |
| 739 | .st_value = self.symbols.items[i].st_value, | |
| 740 | .st_size = self.symbols.items[i].st_size, | |
| 741 | .st_info = self.symbols.items[i].st_info, | |
| 742 | .st_other = self.symbols.items[i].st_other, | |
| 743 | .st_shndx = self.symbols.items[i].st_shndx, | |
| 1154 | .st_name = self.global_symbols.items[i].st_name, | |
| 1155 | .st_value = self.global_symbols.items[i].st_value, | |
| 1156 | .st_size = self.global_symbols.items[i].st_size, | |
| 1157 | .st_info = self.global_symbols.items[i].st_info, | |
| 1158 | .st_other = self.global_symbols.items[i].st_other, | |
| 1159 | .st_shndx = self.global_symbols.items[i].st_shndx, | |
| 744 | 1160 | }; |
| 745 | 1161 | if (foreign_endian) { |
| 746 | 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 | 1170 | |
| 755 | 1171 | /// Truncates the existing file contents and overwrites the contents. |
| 756 | 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 { | |
| 758 | switch (module.output_mode) { | |
| 1173 | pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !ElfFile { | |
| 1174 | switch (options.output_mode) { | |
| 759 | 1175 | .Exe => {}, |
| 760 | 1176 | .Obj => {}, |
| 761 | 1177 | .Lib => return error.TODOImplementWritingLibFiles, |
| 762 | 1178 | } |
| 763 | switch (module.object_format) { | |
| 1179 | switch (options.object_format) { | |
| 764 | 1180 | .unknown => unreachable, // TODO remove this tag from the enum |
| 765 | 1181 | .coff => return error.TODOImplementWritingCOFF, |
| 766 | 1182 | .elf => {}, |
| ... | ... | @@ -768,38 +1184,80 @@ pub fn writeFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Resul |
| 768 | 1184 | .wasm => return error.TODOImplementWritingWasmObjects, |
| 769 | 1185 | } |
| 770 | 1186 | |
| 771 | var update = Update{ | |
| 1187 | var self: ElfFile = .{ | |
| 1188 | .allocator = allocator, | |
| 772 | 1189 | .file = file, |
| 773 | .module = &module, | |
| 774 | .sections = std.ArrayList(elf.Elf64_Shdr).init(allocator), | |
| 775 | .shdr_table_offset = null, | |
| 776 | .program_headers = std.ArrayList(elf.Elf64_Phdr).init(allocator), | |
| 777 | .phdr_table_offset = null, | |
| 778 | .phdr_load_re_index = null, | |
| 779 | .entry_addr = null, | |
| 780 | .shstrtab = std.ArrayList(u8).init(allocator), | |
| 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(), | |
| 1190 | .options = options, | |
| 1191 | .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { | |
| 1192 | 32 => .p32, | |
| 1193 | 64 => .p64, | |
| 1194 | else => return error.UnsupportedELFArchitecture, | |
| 1195 | }, | |
| 1196 | .shdr_table_dirty = true, | |
| 1197 | .owns_file_handle = false, | |
| 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 | 1230 | /// Returns error.IncrFailed if incremental update could not be performed. |
| 798 | fn updateFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { | |
| 799 | //var ehdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; | |
| 1231 | fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !ElfFile { | |
| 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 | 1258 | return error.IncrFailed; |
| 1259 | //try self.populateMissingMetadata(); | |
| 1260 | //return self; | |
| 803 | 1261 | } |
| 804 | 1262 | |
| 805 | 1263 | /// Saturating multiplication |
| ... | ... | @@ -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 | 1302 | // On common systems with a 0o022 umask, 0o777 will still result in a file created |
| 845 | 1303 | // with 0o755 permissions, but it works appropriately if the system is configured |
| 846 | 1304 | // more leniently. As another data point, C's fopen seems to open files with the |
| 847 | 1305 | // 666 mode. |
| 848 | 1306 | const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777; |
| 849 | switch (module.output_mode) { | |
| 850 | .Lib => return switch (module.link_mode) { | |
| 1307 | switch (options.output_mode) { | |
| 1308 | .Lib => return switch (options.link_mode) { | |
| 851 | 1309 | .Dynamic => executable_mode, |
| 852 | 1310 | .Static => fs.File.default_mode, |
| 853 | 1311 | }, |
src-self-hosted/main.zig+413-525| ... | ... | @@ -1,29 +1,29 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); | |
| 3 | ||
| 4 | const event = std.event; | |
| 5 | const os = std.os; | |
| 6 | 2 | const io = std.io; |
| 7 | 3 | const fs = std.fs; |
| 8 | 4 | const mem = std.mem; |
| 9 | 5 | const process = std.process; |
| 10 | 6 | const Allocator = mem.Allocator; |
| 11 | 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 | const introspect = @import("introspect.zig"); | |
| 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; | |
| 14 | // TODO Improve async I/O enough that we feel comfortable doing this. | |
| 15 | //pub const io_mode = .evented; | |
| 22 | 16 | |
| 23 | 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 | 25 | const usage = |
| 26 | \\usage: zig [command] [options] | |
| 26 | \\Usage: zig [command] [options] | |
| 27 | 27 | \\ |
| 28 | 28 | \\Commands: |
| 29 | 29 | \\ |
| ... | ... | @@ -31,7 +31,6 @@ const usage = |
| 31 | 31 | \\ build-lib [source] Create library from source or object files |
| 32 | 32 | \\ build-obj [source] Create object from source or assembly |
| 33 | 33 | \\ fmt [source] Parse file and render in canonical zig format |
| 34 | \\ libc [paths_file] Display native libc paths file or validate one | |
| 35 | 34 | \\ targets List available compilation targets |
| 36 | 35 | \\ version Print version number and exit |
| 37 | 36 | \\ zen Print zen of zig and exit |
| ... | ... | @@ -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 | 41 | pub fn main() !void { |
| 48 | const allocator = std.heap.c_allocator; | |
| 49 | ||
| 50 | const stderr = io.getStdErr().outStream(); | |
| 42 | // TODO general purpose allocator in the zig std lib | |
| 43 | const gpa = if (std.builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator; | |
| 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); | |
| 53 | defer process.argsFree(allocator, args); | |
| 48 | const args = try process.argsAlloc(arena); | |
| 54 | 49 | |
| 55 | 50 | if (args.len <= 1) { |
| 56 | try stderr.writeAll("expected command argument\n\n"); | |
| 57 | try stderr.writeAll(usage); | |
| 51 | std.debug.warn("expected command argument\n\n{}", .{usage}); | |
| 58 | 52 | process.exit(1); |
| 59 | 53 | } |
| 60 | 54 | |
| 61 | 55 | const cmd = args[1]; |
| 62 | 56 | const cmd_args = args[2..]; |
| 63 | 57 | if (mem.eql(u8, cmd, "build-exe")) { |
| 64 | return buildOutputType(allocator, cmd_args, .Exe); | |
| 58 | return buildOutputType(gpa, arena, cmd_args, .Exe); | |
| 65 | 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 | 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 | 63 | } else if (mem.eql(u8, cmd, "fmt")) { |
| 70 | return cmdFmt(allocator, cmd_args); | |
| 71 | } else if (mem.eql(u8, cmd, "libc")) { | |
| 72 | return cmdLibC(allocator, cmd_args); | |
| 64 | return cmdFmt(gpa, cmd_args); | |
| 73 | 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 | 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 | 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 | 73 | } else if (mem.eql(u8, cmd, "zen")) { |
| 80 | return cmdZen(allocator, cmd_args); | |
| 74 | try io.getStdOut().writeAll(info_zen); | |
| 81 | 75 | } else if (mem.eql(u8, cmd, "help")) { |
| 82 | return cmdHelp(allocator, cmd_args); | |
| 83 | } else if (mem.eql(u8, cmd, "internal")) { | |
| 84 | return cmdInternal(allocator, cmd_args); | |
| 76 | try io.getStdOut().writeAll(usage); | |
| 85 | 77 | } else { |
| 86 | try stderr.print("unknown command: {}\n\n", .{args[1]}); | |
| 87 | try stderr.writeAll(usage); | |
| 78 | std.debug.warn("unknown command: {}\n\n{}", .{ args[1], usage }); | |
| 88 | 79 | process.exit(1); |
| 89 | 80 | } |
| 90 | 81 | } |
| 91 | 82 | |
| 92 | 83 | const usage_build_generic = |
| 93 | \\usage: zig build-exe <options> [file] | |
| 94 | \\ zig build-lib <options> [file] | |
| 95 | \\ zig build-obj <options> [file] | |
| 84 | \\Usage: zig build-exe <options> [files] | |
| 85 | \\ zig build-lib <options> [files] | |
| 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 | 105 | \\General Options: |
| 98 | \\ --help Print this help and exit | |
| 99 | \\ --color [auto|off|on] Enable or disable colored error messages | |
| 106 | \\ -h, --help Print this help and exit | |
| 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 | 112 | \\Compile Options: |
| 102 | \\ --libc [file] Provide a file which specifies libc paths | |
| 103 | \\ --assembly [source] Add assembly file to build | |
| 104 | \\ --emit [filetype] Emit a specific file format as compilation output | |
| 105 | \\ --enable-timing-info Print timing diagnostics | |
| 106 | \\ --name [name] Override output name | |
| 107 | \\ --output [file] Override destination path | |
| 108 | \\ --output-h [file] Override generated header file path | |
| 109 | \\ --pkg-begin [name] [path] Make package available to import and push current pkg | |
| 110 | \\ --pkg-end Pop current pkg | |
| 111 | \\ --mode [mode] Set the build mode | |
| 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 | |
| 113 | \\ -target [name] <arch><sub>-<os>-<abi> see the targets command | |
| 114 | \\ -mcpu [cpu] Specify target CPU and feature set | |
| 115 | \\ --name [name] Override output name | |
| 116 | \\ --mode [mode] Set the build mode | |
| 117 | \\ Debug (default) optimizations off, safety on | |
| 118 | \\ ReleaseFast optimizations on, safety off | |
| 119 | \\ ReleaseSafe optimizations on, safety on | |
| 120 | \\ ReleaseSmall optimize for small binary, safety off | |
| 121 | \\ --dynamic Force output to be dynamically linked | |
| 122 | \\ --strip Exclude debug symbols | |
| 130 | 123 | \\ |
| 131 | 124 | \\Link Options: |
| 132 | \\ --ar-path [path] Set the path to ar | |
| 133 | \\ --each-lib-rpath Add rpath for each used dynamic library | |
| 134 | \\ --library [lib] Link against lib | |
| 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 | |
| 125 | \\ -l[lib], --library [lib] Link against system library | |
| 126 | \\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so) | |
| 127 | \\ --version [ver] Dynamic library semver | |
| 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 { | |
| 152 | const stderr = io.getStdErr().outStream(); | |
| 140 | const Emit = union(enum) { | |
| 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 | 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 | 154 | var provided_name: ?[]const u8 = null; |
| 161 | var is_dynamic = false; | |
| 155 | var link_mode: ?std.builtin.LinkMode = null; | |
| 162 | 156 | var root_src_file: ?[]const u8 = null; |
| 163 | var libc_arg: ?[]const u8 = null; | |
| 164 | 157 | var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 }; |
| 165 | var linker_script: ?[]const u8 = null; | |
| 166 | 158 | var strip = false; |
| 167 | var verbose_tokenize = false; | |
| 168 | var verbose_ast_tree = false; | |
| 169 | var verbose_ast_fmt = false; | |
| 170 | var verbose_link = false; | |
| 171 | var verbose_ir = false; | |
| 172 | var verbose_llvm_ir = false; | |
| 173 | var verbose_cimport = false; | |
| 174 | var linker_rdynamic = false; | |
| 175 | var link_eh_frame_hdr = false; | |
| 176 | var macosx_version_min: ?[]const u8 = null; | |
| 177 | var ios_version_min: ?[]const u8 = null; | |
| 178 | ||
| 179 | var assembly_files = ArrayList([]const u8).init(allocator); | |
| 180 | defer assembly_files.deinit(); | |
| 181 | ||
| 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); | |
| 159 | var watch = false; | |
| 160 | var debug_tokenize = false; | |
| 161 | var debug_ast_tree = false; | |
| 162 | var debug_ast_fmt = false; | |
| 163 | var debug_link = false; | |
| 164 | var debug_ir = false; | |
| 165 | var debug_codegen = false; | |
| 166 | var time_report = false; | |
| 167 | var emit_bin: Emit = .yes_default_path; | |
| 168 | var emit_zir: Emit = .no; | |
| 169 | var target_arch_os_abi: []const u8 = "native"; | |
| 170 | var target_mcpu: ?[]const u8 = null; | |
| 171 | var target_dynamic_linker: ?[]const u8 = null; | |
| 172 | ||
| 173 | var system_libs = std.ArrayList([]const u8).init(gpa); | |
| 195 | 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 | 177 | var i: usize = 0; |
| 202 | 178 | while (i < args.len) : (i += 1) { |
| 203 | 179 | const arg = args[i]; |
| 204 | 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 | 182 | try io.getStdOut().writeAll(usage_build_generic); |
| 207 | 183 | process.exit(0); |
| 208 | 184 | } else if (mem.eql(u8, arg, "--color")) { |
| 209 | 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 | 187 | process.exit(1); |
| 212 | 188 | } |
| 213 | 189 | i += 1; |
| ... | ... | @@ -219,12 +195,12 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co |
| 219 | 195 | } else if (mem.eql(u8, next_arg, "off")) { |
| 220 | 196 | color = .Off; |
| 221 | 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 | 199 | process.exit(1); |
| 224 | 200 | } |
| 225 | 201 | } else if (mem.eql(u8, arg, "--mode")) { |
| 226 | 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 | 204 | process.exit(1); |
| 229 | 205 | } |
| 230 | 206 | i += 1; |
| ... | ... | @@ -238,289 +214,302 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co |
| 238 | 214 | } else if (mem.eql(u8, next_arg, "ReleaseSmall")) { |
| 239 | 215 | build_mode = .ReleaseSmall; |
| 240 | 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 | 218 | process.exit(1); |
| 243 | 219 | } |
| 244 | 220 | } else if (mem.eql(u8, arg, "--name")) { |
| 245 | 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 | 223 | process.exit(1); |
| 248 | 224 | } |
| 249 | 225 | i += 1; |
| 250 | 226 | provided_name = args[i]; |
| 251 | } else if (mem.eql(u8, arg, "--ver-major")) { | |
| 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")) { | |
| 227 | } else if (mem.eql(u8, arg, "--library")) { | |
| 266 | 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 | 230 | process.exit(1); |
| 269 | 231 | } |
| 270 | 232 | i += 1; |
| 271 | version.patch = try std.fmt.parseInt(u32, args[i], 10); | |
| 272 | } else if (mem.eql(u8, arg, "--linker-script")) { | |
| 233 | try system_libs.append(args[i]); | |
| 234 | } else if (mem.eql(u8, arg, "--version")) { | |
| 273 | 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 | 237 | process.exit(1); |
| 276 | 238 | } |
| 277 | 239 | i += 1; |
| 278 | linker_script = args[i]; | |
| 279 | } else if (mem.eql(u8, arg, "--libc")) { | |
| 280 | if (i + 1 >= args.len) { | |
| 281 | try stderr.writeAll("expected parameter after --libc\n"); | |
| 240 | version = std.builtin.Version.parse(args[i]) catch |err| { | |
| 241 | std.debug.warn("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) }); | |
| 282 | 242 | process.exit(1); |
| 283 | } | |
| 284 | i += 1; | |
| 285 | libc_arg = args[i]; | |
| 286 | } else if (mem.eql(u8, arg, "-mllvm")) { | |
| 243 | }; | |
| 244 | } else if (mem.eql(u8, arg, "-target")) { | |
| 287 | 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 | 247 | process.exit(1); |
| 290 | 248 | } |
| 291 | 249 | i += 1; |
| 292 | try clang_argv_buf.append("-mllvm"); | |
| 293 | try clang_argv_buf.append(args[i]); | |
| 294 | ||
| 295 | try mllvm_flags.append(args[i]); | |
| 296 | } else if (mem.eql(u8, arg, "-mmacosx-version-min")) { | |
| 250 | target_arch_os_abi = args[i]; | |
| 251 | } else if (mem.eql(u8, arg, "-mcpu")) { | |
| 297 | 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 | 254 | process.exit(1); |
| 300 | 255 | } |
| 301 | 256 | i += 1; |
| 302 | macosx_version_min = args[i]; | |
| 303 | } else if (mem.eql(u8, arg, "-mios-version-min")) { | |
| 257 | target_mcpu = args[i]; | |
| 258 | } else if (mem.startsWith(u8, arg, "-mcpu=")) { | |
| 259 | target_mcpu = arg["-mcpu=".len..]; | |
| 260 | } else if (mem.eql(u8, arg, "--dynamic-linker")) { | |
| 304 | 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 | 263 | process.exit(1); |
| 307 | 264 | } |
| 308 | 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 | 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 | 275 | } else if (mem.eql(u8, arg, "-fno-emit-bin")) { |
| 313 | emit_bin = false; | |
| 314 | } else if (mem.eql(u8, arg, "-femit-asm")) { | |
| 315 | emit_asm = true; | |
| 316 | } else if (mem.eql(u8, arg, "-fno-emit-asm")) { | |
| 317 | emit_asm = false; | |
| 318 | } else if (mem.eql(u8, arg, "-femit-llvm-ir")) { | |
| 319 | emit_llvm_ir = true; | |
| 320 | } else if (mem.eql(u8, arg, "-fno-emit-llvm-ir")) { | |
| 321 | emit_llvm_ir = false; | |
| 276 | emit_bin = .no; | |
| 277 | } else if (mem.eql(u8, arg, "-femit-zir")) { | |
| 278 | emit_zir = .yes_default_path; | |
| 279 | } else if (mem.startsWith(u8, arg, "-femit-zir=")) { | |
| 280 | emit_zir = .{ .yes = arg["-femit-zir=".len..] }; | |
| 281 | } else if (mem.eql(u8, arg, "-fno-emit-zir")) { | |
| 282 | emit_zir = .no; | |
| 322 | 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 | 287 | } else if (mem.eql(u8, arg, "--strip")) { |
| 325 | 288 | strip = true; |
| 326 | } else if (mem.eql(u8, arg, "--verbose-tokenize")) { | |
| 327 | verbose_tokenize = true; | |
| 328 | } else if (mem.eql(u8, arg, "--verbose-ast-tree")) { | |
| 329 | verbose_ast_tree = true; | |
| 330 | } else if (mem.eql(u8, arg, "--verbose-ast-fmt")) { | |
| 331 | verbose_ast_fmt = true; | |
| 332 | } else if (mem.eql(u8, arg, "--verbose-link")) { | |
| 333 | verbose_link = true; | |
| 334 | } else if (mem.eql(u8, arg, "--verbose-ir")) { | |
| 335 | verbose_ir = true; | |
| 336 | } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) { | |
| 337 | verbose_llvm_ir = 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 | } | |
| 289 | } else if (mem.eql(u8, arg, "--debug-tokenize")) { | |
| 290 | debug_tokenize = true; | |
| 291 | } else if (mem.eql(u8, arg, "--debug-ast-tree")) { | |
| 292 | debug_ast_tree = true; | |
| 293 | } else if (mem.eql(u8, arg, "--debug-ast-fmt")) { | |
| 294 | debug_ast_fmt = true; | |
| 295 | } else if (mem.eql(u8, arg, "--debug-link")) { | |
| 296 | debug_link = true; | |
| 297 | } else if (mem.eql(u8, arg, "--debug-ir")) { | |
| 298 | debug_ir = true; | |
| 299 | } else if (mem.eql(u8, arg, "--debug-codegen")) { | |
| 300 | debug_codegen = true; | |
| 364 | 301 | } else if (mem.startsWith(u8, arg, "-l")) { |
| 365 | 302 | try system_libs.append(arg[2..]); |
| 366 | 303 | } else { |
| 367 | try stderr.print("unrecognized parameter: '{}'", .{arg}); | |
| 304 | std.debug.warn("unrecognized parameter: '{}'", .{arg}); | |
| 368 | 305 | process.exit(1); |
| 369 | 306 | } |
| 370 | } else if (mem.endsWith(u8, arg, ".s")) { | |
| 371 | try assembly_files.append(arg); | |
| 307 | } else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) { | |
| 308 | std.debug.warn("assembly files not supported yet", .{}); | |
| 309 | process.exit(1); | |
| 372 | 310 | } else if (mem.endsWith(u8, arg, ".o") or |
| 373 | 311 | mem.endsWith(u8, arg, ".obj") or |
| 374 | 312 | mem.endsWith(u8, arg, ".a") or |
| 375 | 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 | 317 | } else if (mem.endsWith(u8, arg, ".c") or |
| 379 | 318 | mem.endsWith(u8, arg, ".cpp")) |
| 380 | 319 | { |
| 381 | try c_src_files.append(arg); | |
| 382 | } else if (mem.endsWith(u8, arg, ".zig")) { | |
| 320 | std.debug.warn("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{}); | |
| 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 | 329 | if (root_src_file) |other| { |
| 384 | try stderr.print("found another zig file '{}' after root source file '{}'", .{ | |
| 385 | arg, | |
| 386 | other, | |
| 387 | }); | |
| 330 | std.debug.warn("found another zig file '{}' after root source file '{}'", .{ arg, other }); | |
| 388 | 331 | process.exit(1); |
| 389 | 332 | } else { |
| 390 | 333 | root_src_file = arg; |
| 391 | 334 | } |
| 392 | 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 | 341 | const root_name = if (provided_name) |n| n else blk: { |
| 404 | 342 | if (root_src_file) |file| { |
| 405 | 343 | const basename = fs.path.basename(file); |
| 406 | 344 | var it = mem.split(basename, "."); |
| 407 | 345 | break :blk it.next() orelse basename; |
| 408 | 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 | 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) { | |
| 415 | try stderr.writeAll("Expected source file argument or at least one --object or --assembly argument\n"); | |
| 352 | if (system_libs.items.len != 0) { | |
| 353 | std.debug.warn("linking against system libraries not yet supported", .{}); | |
| 416 | 354 | process.exit(1); |
| 417 | 355 | } |
| 418 | 356 | |
| 419 | if (out_type == Compilation.Kind.Obj and link_objects.len != 0) { | |
| 420 | try stderr.writeAll("When building an object file, --object arguments are invalid\n"); | |
| 357 | var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{}; | |
| 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 | 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); | |
| 427 | defer allocator.free(zig_lib_dir); | |
| 405 | const bin_path = switch (emit_bin) { | |
| 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); | |
| 432 | defer zig_compiler.deinit(); | |
| 427 | const root_pkg = try Package.create(gpa, fs.cwd(), ".", src_path); | |
| 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( | |
| 435 | &zig_compiler, | |
| 436 | root_name, | |
| 437 | root_src_file, | |
| 438 | .{}, | |
| 439 | out_type, | |
| 440 | build_mode, | |
| 441 | !is_dynamic, | |
| 442 | zig_lib_dir, | |
| 443 | ); | |
| 444 | defer comp.destroy(); | |
| 442 | const stdin = std.io.getStdIn().inStream(); | |
| 443 | const stderr = std.io.getStdErr().outStream(); | |
| 444 | var repl_buf: [1024]u8 = undefined; | |
| 445 | 445 | |
| 446 | if (libc_arg) |libc_path| { | |
| 447 | parseLibcPaths(allocator, &override_libc, libc_path); | |
| 448 | comp.override_libc = &override_libc; | |
| 449 | } | |
| 446 | try updateModule(gpa, &module, zir_out_path); | |
| 450 | 447 | |
| 451 | for (system_libs.span()) |lib| { | |
| 452 | _ = try comp.addLinkLib(lib, true); | |
| 448 | while (watch) { | |
| 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; | |
| 456 | comp.is_test = false; | |
| 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; | |
| 475 | fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !void { | |
| 476 | try module.update(); | |
| 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) { | |
| 476 | try stderr.writeAll("-mmacosx-version-min and -mios-version-min options not allowed together\n"); | |
| 477 | process.exit(1); | |
| 481 | if (errors.list.len != 0) { | |
| 482 | for (errors.list) |full_err_msg| { | |
| 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| { | |
| 481 | comp.darwin_version_min = Compilation.DarwinVersionMin{ .MacOS = ver }; | |
| 482 | } | |
| 483 | if (ios_version_min) |ver| { | |
| 484 | comp.darwin_version_min = Compilation.DarwinVersionMin{ .Ios = ver }; | |
| 485 | } | |
| 492 | if (zir_out_path) |zop| { | |
| 493 | var new_zir_module = try zir.emit(gpa, module.*); | |
| 494 | defer new_zir_module.deinit(gpa); | |
| 486 | 495 | |
| 487 | comp.emit_bin = emit_bin; | |
| 488 | comp.emit_asm = emit_asm; | |
| 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(); | |
| 496 | const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{}); | |
| 497 | defer baf.destroy(); | |
| 493 | 498 | |
| 494 | comp.start(); | |
| 495 | processBuildEvents(comp, color); | |
| 496 | } | |
| 499 | try new_zir_module.writeToStream(gpa, baf.stream()); | |
| 497 | 500 | |
| 498 | fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void { | |
| 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 | } | |
| 501 | try baf.finish(); | |
| 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 | 513 | pub const usage_fmt = |
| 525 | 514 | \\usage: zig fmt [file]... |
| 526 | 515 | \\ |
| ... | ... | @@ -539,58 +528,20 @@ pub const usage_fmt = |
| 539 | 528 | ; |
| 540 | 529 | |
| 541 | 530 | const Fmt = struct { |
| 542 | seen: event.Locked(SeenMap), | |
| 531 | seen: SeenMap, | |
| 543 | 532 | any_error: bool, |
| 544 | color: errmsg.Color, | |
| 545 | allocator: *Allocator, | |
| 533 | color: Color, | |
| 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 { | |
| 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 { | |
| 539 | pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { | |
| 588 | 540 | const stderr_file = io.getStdErr(); |
| 589 | const stderr = stderr_file.outStream(); | |
| 590 | var color: errmsg.Color = .Auto; | |
| 541 | var color: Color = .Auto; | |
| 591 | 542 | var stdin_flag: bool = false; |
| 592 | 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 | 547 | var i: usize = 0; |
| ... | ... | @@ -603,7 +554,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { |
| 603 | 554 | process.exit(0); |
| 604 | 555 | } else if (mem.eql(u8, arg, "--color")) { |
| 605 | 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 | 558 | process.exit(1); |
| 608 | 559 | } |
| 609 | 560 | i += 1; |
| ... | ... | @@ -615,7 +566,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { |
| 615 | 566 | } else if (mem.eql(u8, next_arg, "off")) { |
| 616 | 567 | color = .Off; |
| 617 | 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 | 570 | process.exit(1); |
| 620 | 571 | } |
| 621 | 572 | } else if (mem.eql(u8, arg, "--stdin")) { |
| ... | ... | @@ -623,7 +574,7 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { |
| 623 | 574 | } else if (mem.eql(u8, arg, "--check")) { |
| 624 | 575 | check_flag = true; |
| 625 | 576 | } else { |
| 626 | try stderr.print("unrecognized parameter: '{}'", .{arg}); | |
| 577 | std.debug.warn("unrecognized parameter: '{}'", .{arg}); | |
| 627 | 578 | process.exit(1); |
| 628 | 579 | } |
| 629 | 580 | } else { |
| ... | ... | @@ -633,60 +584,55 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void { |
| 633 | 584 | } |
| 634 | 585 | |
| 635 | 586 | if (stdin_flag) { |
| 636 | if (input_files.len != 0) { | |
| 637 | try stderr.writeAll("cannot use --stdin with positional arguments\n"); | |
| 587 | if (input_files.items.len != 0) { | |
| 588 | std.debug.warn("cannot use --stdin with positional arguments\n", .{}); | |
| 638 | 589 | process.exit(1); |
| 639 | 590 | } |
| 640 | 591 | |
| 641 | 592 | const stdin = io.getStdIn().inStream(); |
| 642 | 593 | |
| 643 | const source_code = try stdin.readAllAlloc(allocator, max_src_size); | |
| 644 | defer allocator.free(source_code); | |
| 594 | const source_code = try stdin.readAllAlloc(gpa, max_src_size); | |
| 595 | defer gpa.free(source_code); | |
| 645 | 596 | |
| 646 | const tree = std.zig.parse(allocator, source_code) catch |err| { | |
| 647 | try stderr.print("error parsing stdin: {}\n", .{err}); | |
| 597 | const tree = std.zig.parse(gpa, source_code) catch |err| { | |
| 598 | std.debug.warn("error parsing stdin: {}\n", .{err}); | |
| 648 | 599 | process.exit(1); |
| 649 | 600 | }; |
| 650 | 601 | defer tree.deinit(); |
| 651 | 602 | |
| 652 | 603 | var error_it = tree.errors.iterator(0); |
| 653 | 604 | while (error_it.next()) |parse_error| { |
| 654 | const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, tree, "<stdin>"); | |
| 655 | defer msg.destroy(); | |
| 656 | ||
| 657 | try msg.printToFile(io.getStdErr(), color); | |
| 605 | try printErrMsgToFile(gpa, parse_error, tree, "<stdin>", stderr_file, color); | |
| 658 | 606 | } |
| 659 | 607 | if (tree.errors.len != 0) { |
| 660 | 608 | process.exit(1); |
| 661 | 609 | } |
| 662 | 610 | if (check_flag) { |
| 663 | const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree); | |
| 664 | const code: u8 = if (anything_changed) 1 else 0; | |
| 611 | const anything_changed = try std.zig.render(gpa, io.null_out_stream, tree); | |
| 612 | const code = if (anything_changed) @as(u8, 1) else @as(u8, 0); | |
| 665 | 613 | process.exit(code); |
| 666 | 614 | } |
| 667 | 615 | |
| 668 | 616 | const stdout = io.getStdOut().outStream(); |
| 669 | _ = try std.zig.render(allocator, stdout, tree); | |
| 617 | _ = try std.zig.render(gpa, stdout, tree); | |
| 670 | 618 | return; |
| 671 | 619 | } |
| 672 | 620 | |
| 673 | if (input_files.len == 0) { | |
| 674 | try stderr.writeAll("expected at least one source file argument\n"); | |
| 621 | if (input_files.items.len == 0) { | |
| 622 | std.debug.warn("expected at least one source file argument\n", .{}); | |
| 675 | 623 | process.exit(1); |
| 676 | 624 | } |
| 677 | 625 | |
| 678 | 626 | var fmt = Fmt{ |
| 679 | .allocator = allocator, | |
| 680 | .seen = event.Locked(Fmt.SeenMap).init(Fmt.SeenMap.init(allocator)), | |
| 627 | .gpa = gpa, | |
| 628 | .seen = Fmt.SeenMap.init(gpa), | |
| 681 | 629 | .any_error = false, |
| 682 | 630 | .color = color, |
| 683 | 631 | }; |
| 684 | 632 | |
| 685 | var group = event.Group(FmtError!void).init(allocator); | |
| 686 | 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 | 636 | if (fmt.any_error) { |
| 691 | 637 | process.exit(1); |
| 692 | 638 | } |
| ... | ... | @@ -711,53 +657,45 @@ const FmtError = error{ |
| 711 | 657 | ReadOnlyFileSystem, |
| 712 | 658 | LinkQuotaExceeded, |
| 713 | 659 | FileBusy, |
| 714 | CurrentWorkingDirectoryUnlinked, | |
| 715 | 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 | { | |
| 724 | const held = fmt.seen.acquire(); | |
| 725 | defer held.release(); | |
| 662 | fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { | |
| 663 | // get the real path here to avoid Windows failing on relative file paths with . or .. in them | |
| 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; | |
| 728 | } | |
| 671 | if (fmt.seen.exists(real_path)) return; | |
| 672 | try fmt.seen.put(real_path); | |
| 729 | 673 | |
| 730 | const source_code = fs.cwd().readFileAlloc( | |
| 731 | fmt.allocator, | |
| 732 | file_path, | |
| 733 | max_src_size, | |
| 734 | ) catch |err| switch (err) { | |
| 674 | const source_code = fs.cwd().readFileAlloc(fmt.gpa, real_path, max_src_size) catch |err| switch (err) { | |
| 735 | 675 | error.IsDir, error.AccessDenied => { |
| 736 | 676 | var dir = try fs.cwd().openDir(file_path, .{ .iterate = true }); |
| 737 | 677 | defer dir.close(); |
| 738 | 678 | |
| 739 | var group = event.Group(FmtError!void).init(fmt.allocator); | |
| 740 | var it = dir.iterate(); | |
| 741 | while (try it.next()) |entry| { | |
| 679 | var dir_it = dir.iterate(); | |
| 680 | ||
| 681 | while (try dir_it.next()) |entry| { | |
| 742 | 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 }); | |
| 744 | @panic("TODO https://github.com/ziglang/zig/issues/3777"); | |
| 745 | // try group.call(fmtPath, .{fmt, full_path, check_mode}); | |
| 683 | const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name }); | |
| 684 | try fmtPath(fmt, full_path, check_mode); | |
| 746 | 685 | } |
| 747 | 686 | } |
| 748 | return group.wait(); | |
| 687 | return; | |
| 749 | 688 | }, |
| 750 | 689 | else => { |
| 751 | // TODO lock stderr printing | |
| 752 | try stderr.print("unable to open '{}': {}\n", .{ file_path, err }); | |
| 690 | std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); | |
| 753 | 691 | fmt.any_error = true; |
| 754 | 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| { | |
| 760 | try stderr.print("error parsing file '{}': {}\n", .{ file_path, err }); | |
| 697 | const tree = std.zig.parse(fmt.gpa, source_code) catch |err| { | |
| 698 | std.debug.warn("error parsing file '{}': {}\n", .{ file_path, err }); | |
| 761 | 699 | fmt.any_error = true; |
| 762 | 700 | return; |
| 763 | 701 | }; |
| ... | ... | @@ -765,10 +703,7 @@ fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) callconv(.Asy |
| 765 | 703 | |
| 766 | 704 | var error_it = tree.errors.iterator(0); |
| 767 | 705 | while (error_it.next()) |parse_error| { |
| 768 | const msg = try errmsg.Msg.createFromParseError(fmt.allocator, parse_error, tree, file_path); | |
| 769 | defer fmt.allocator.destroy(msg); | |
| 770 | ||
| 771 | try msg.printToFile(stderr_file, fmt.color); | |
| 706 | try printErrMsgToFile(fmt.gpa, parse_error, tree, file_path, std.io.getStdErr(), fmt.color); | |
| 772 | 707 | } |
| 773 | 708 | if (tree.errors.len != 0) { |
| 774 | 709 | fmt.any_error = true; |
| ... | ... | @@ -776,32 +711,67 @@ fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) callconv(.Asy |
| 776 | 711 | } |
| 777 | 712 | |
| 778 | 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 | 715 | if (anything_changed) { |
| 781 | try stderr.print("{}\n", .{file_path}); | |
| 716 | std.debug.warn("{}\n", .{file_path}); | |
| 782 | 717 | fmt.any_error = true; |
| 783 | 718 | } |
| 784 | 719 | } else { |
| 785 | // TODO make this evented | |
| 786 | const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path); | |
| 720 | const baf = try io.BufferedAtomicFile.create(fmt.gpa, fs.cwd(), real_path, .{}); | |
| 787 | 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 | 724 | if (anything_changed) { |
| 791 | try stderr.print("{}\n", .{file_path}); | |
| 725 | std.debug.warn("{}\n", .{file_path}); | |
| 792 | 726 | try baf.finish(); |
| 793 | 727 | } |
| 794 | 728 | } |
| 795 | 729 | } |
| 796 | 730 | |
| 797 | fn cmdVersion(allocator: *Allocator, args: []const []const u8) !void { | |
| 798 | const stdout = io.getStdOut().outStream(); | |
| 799 | try stdout.print("{}\n", .{c.ZIG_VERSION_STRING}); | |
| 800 | } | |
| 801 | ||
| 802 | fn cmdHelp(allocator: *Allocator, args: []const []const u8) !void { | |
| 803 | const stdout = io.getStdOut(); | |
| 804 | try stdout.writeAll(usage); | |
| 731 | fn printErrMsgToFile( | |
| 732 | gpa: *mem.Allocator, | |
| 733 | parse_error: *const ast.Error, | |
| 734 | tree: *ast.Tree, | |
| 735 | path: []const u8, | |
| 736 | file: fs.File, | |
| 737 | color: Color, | |
| 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 | 777 | pub const info_zen = |
| ... | ... | @@ -816,90 +786,8 @@ pub const info_zen = |
| 816 | 786 | \\ * Avoid local maximums. |
| 817 | 787 | \\ * Reduce the amount one must remember. |
| 818 | 788 | \\ * Minimize energy spent on coding style. |
| 789 | \\ * Resource deallocation must succeed. | |
| 819 | 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 | 12 | const Target = std.Target; |
| 13 | 13 | const CrossTarget = std.zig.CrossTarget; |
| 14 | 14 | const self_hosted_main = @import("main.zig"); |
| 15 | const errmsg = @import("errmsg.zig"); | |
| 16 | 15 | const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer; |
| 17 | 16 | const assert = std.debug.assert; |
| 18 | 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 | 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 | 170 | export fn stage2_fmt(argc: c_int, argv: [*]const [*:0]const u8) c_int { |
| 174 | 171 | if (std.debug.runtime_safety) { |
| 175 | 172 | fmtMain(argc, argv) catch unreachable; |
| ... | ... | @@ -191,258 +188,9 @@ fn fmtMain(argc: c_int, argv: [*]const [*:0]const u8) !void { |
| 191 | 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 | 191 | const args = args_list.span()[2..]; |
| 199 | 192 | |
| 200 | var color: errmsg.Color = .Auto; | |
| 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'); | |
| 193 | return self_hosted_main.cmdFmt(allocator, args); | |
| 446 | 194 | } |
| 447 | 195 | |
| 448 | 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 | 1 | const std = @import("std"); |
| 2 | 2 | const link = @import("link.zig"); |
| 3 | const ir = @import("ir.zig"); | |
| 3 | const Module = @import("Module.zig"); | |
| 4 | 4 | const Allocator = std.mem.Allocator; |
| 5 | ||
| 6 | var global_ctx: TestContext = undefined; | |
| 5 | const zir = @import("zir.zig"); | |
| 6 | const Package = @import("Package.zig"); | |
| 7 | 7 | |
| 8 | 8 | test "self-hosted" { |
| 9 | try global_ctx.init(); | |
| 10 | defer global_ctx.deinit(); | |
| 9 | var ctx: TestContext = undefined; | |
| 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 | 18 | pub const TestContext = struct { |
| ... | ... | @@ -20,32 +21,34 @@ pub const TestContext = struct { |
| 20 | 21 | |
| 21 | 22 | pub const ZIRCompareOutputCase = struct { |
| 22 | 23 | name: []const u8, |
| 23 | src: [:0]const u8, | |
| 24 | expected_stdout: []const u8, | |
| 24 | src_list: []const []const u8, | |
| 25 | expected_stdout_list: []const []const u8, | |
| 25 | 26 | }; |
| 26 | 27 | |
| 27 | 28 | pub const ZIRTransformCase = struct { |
| 28 | 29 | name: []const u8, |
| 29 | 30 | src: [:0]const u8, |
| 30 | 31 | expected_zir: []const u8, |
| 32 | cross_target: std.zig.CrossTarget, | |
| 31 | 33 | }; |
| 32 | 34 | |
| 33 | 35 | pub fn addZIRCompareOutput( |
| 34 | 36 | ctx: *TestContext, |
| 35 | 37 | name: []const u8, |
| 36 | src: [:0]const u8, | |
| 37 | expected_stdout: []const u8, | |
| 38 | src_list: []const []const u8, | |
| 39 | expected_stdout_list: []const []const u8, | |
| 38 | 40 | ) void { |
| 39 | 41 | ctx.zir_cmp_output_cases.append(.{ |
| 40 | 42 | .name = name, |
| 41 | .src = src, | |
| 42 | .expected_stdout = expected_stdout, | |
| 43 | .src_list = src_list, | |
| 44 | .expected_stdout_list = expected_stdout_list, | |
| 43 | 45 | }) catch unreachable; |
| 44 | 46 | } |
| 45 | 47 | |
| 46 | 48 | pub fn addZIRTransform( |
| 47 | 49 | ctx: *TestContext, |
| 48 | 50 | name: []const u8, |
| 51 | cross_target: std.zig.CrossTarget, | |
| 49 | 52 | src: [:0]const u8, |
| 50 | 53 | expected_zir: []const u8, |
| 51 | 54 | ) void { |
| ... | ... | @@ -53,6 +56,7 @@ pub const TestContext = struct { |
| 53 | 56 | .name = name, |
| 54 | 57 | .src = src, |
| 55 | 58 | .expected_zir = expected_zir, |
| 59 | .cross_target = cross_target, | |
| 56 | 60 | }) catch unreachable; |
| 57 | 61 | } |
| 58 | 62 | |
| ... | ... | @@ -84,7 +88,8 @@ pub const TestContext = struct { |
| 84 | 88 | } |
| 85 | 89 | for (self.zir_transform_cases.items) |case| { |
| 86 | 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 | 93 | try std.testing.allocator_instance.validate(); |
| 89 | 94 | } |
| 90 | 95 | } |
| ... | ... | @@ -99,77 +104,68 @@ pub const TestContext = struct { |
| 99 | 104 | var tmp = std.testing.tmpDir(.{}); |
| 100 | 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 | 112 | prg_node.activate(); |
| 104 | 113 | defer prg_node.end(); |
| 105 | 114 | |
| 106 | var zir_module = x: { | |
| 107 | var parse_node = prg_node.start("parse", null); | |
| 108 | parse_node.activate(); | |
| 109 | defer parse_node.end(); | |
| 110 | ||
| 111 | break :x try ir.text.parse(allocator, case.src); | |
| 112 | }; | |
| 113 | defer zir_module.deinit(allocator); | |
| 114 | if (zir_module.errors.len != 0) { | |
| 115 | debugPrintErrors(case.src, zir_module.errors); | |
| 116 | return error.ParseFailure; | |
| 117 | } | |
| 118 | ||
| 119 | var analyzed_module = x: { | |
| 120 | var analyze_node = prg_node.start("analyze", null); | |
| 121 | analyze_node.activate(); | |
| 122 | defer analyze_node.end(); | |
| 123 | ||
| 124 | break :x try ir.analyze(allocator, zir_module, .{ | |
| 125 | .target = target, | |
| 126 | .output_mode = .Exe, | |
| 127 | .link_mode = .Static, | |
| 128 | .optimize_mode = .Debug, | |
| 129 | }); | |
| 130 | }; | |
| 131 | defer analyzed_module.deinit(allocator); | |
| 132 | if (analyzed_module.errors.len != 0) { | |
| 133 | debugPrintErrors(case.src, analyzed_module.errors); | |
| 134 | return error.ParseFailure; | |
| 135 | } | |
| 136 | ||
| 137 | var link_result = x: { | |
| 138 | var link_node = prg_node.start("link", null); | |
| 139 | link_node.activate(); | |
| 140 | defer link_node.end(); | |
| 141 | ||
| 142 | break :x try link.updateFilePath(allocator, analyzed_module, tmp.dir, "a.out"); | |
| 143 | }; | |
| 144 | defer link_result.deinit(allocator); | |
| 145 | if (link_result.errors.len != 0) { | |
| 146 | debugPrintErrors(case.src, link_result.errors); | |
| 147 | return error.LinkFailure; | |
| 148 | } | |
| 149 | ||
| 150 | var exec_result = x: { | |
| 151 | var exec_node = prg_node.start("execute", null); | |
| 152 | exec_node.activate(); | |
| 153 | defer exec_node.end(); | |
| 154 | ||
| 155 | break :x try std.ChildProcess.exec(.{ | |
| 156 | .allocator = allocator, | |
| 157 | .argv = &[_][]const u8{"./a.out"}, | |
| 158 | .cwd_dir = tmp.dir, | |
| 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, | |
| 115 | var module = try Module.init(allocator, .{ | |
| 116 | .target = target, | |
| 117 | .output_mode = .Exe, | |
| 118 | .optimize_mode = .Debug, | |
| 119 | .bin_file_dir = tmp.dir, | |
| 120 | .bin_file_path = "a.out", | |
| 121 | .root_pkg = root_pkg, | |
| 122 | }); | |
| 123 | defer module.deinit(); | |
| 124 | ||
| 125 | for (case.src_list) |source, i| { | |
| 126 | var src_node = prg_node.start("update", 2); | |
| 127 | src_node.activate(); | |
| 128 | defer src_node.end(); | |
| 129 | ||
| 130 | try tmp.dir.writeFile(tmp_src_path, source); | |
| 131 | ||
| 132 | var update_node = src_node.start("parse,analysis,codegen", null); | |
| 133 | update_node.activate(); | |
| 134 | try module.makeBinFileWritable(); | |
| 135 | try module.update(); | |
| 136 | update_node.end(); | |
| 137 | ||
| 138 | var exec_result = x: { | |
| 139 | var exec_node = src_node.start("execute", null); | |
| 140 | exec_node.activate(); | |
| 141 | defer exec_node.end(); | |
| 142 | ||
| 143 | try module.makeBinFileExecutable(); | |
| 144 | break :x try std.ChildProcess.exec(.{ | |
| 145 | .allocator = allocator, | |
| 146 | .argv = &[_][]const u8{"./a.out"}, | |
| 147 | .cwd_dir = tmp.dir, | |
| 148 | }); | |
| 149 | }; | |
| 150 | defer allocator.free(exec_result.stdout); | |
| 151 | defer allocator.free(exec_result.stderr); | |
| 152 | switch (exec_result.term) { | |
| 153 | .Exited => |code| { | |
| 154 | if (code != 0) { | |
| 155 | std.debug.warn("elf file exited with code {}\n", .{code}); | |
| 156 | return error.BinaryBadExitCode; | |
| 157 | } | |
| 158 | }, | |
| 159 | else => return error.BinaryCrashed, | |
| 160 | } | |
| 161 | const expected_stdout = case.expected_stdout_list[i]; | |
| 162 | if (!std.mem.eql(u8, expected_stdout, exec_result.stdout)) { | |
| 163 | std.debug.panic( | |
| 164 | "update index {}, mismatched stdout\n====Expected (len={}):====\n{}\n====Actual (len={}):====\n{}\n========\n", | |
| 165 | .{ i, expected_stdout.len, expected_stdout, exec_result.stdout.len, exec_result.stdout }, | |
| 166 | ); | |
| 167 | } | |
| 171 | 168 | } |
| 172 | std.testing.expectEqualSlices(u8, case.expected_stdout, exec_result.stdout); | |
| 173 | 169 | } |
| 174 | 170 | |
| 175 | 171 | fn runOneZIRTransformCase( |
| ... | ... | @@ -179,38 +175,37 @@ pub const TestContext = struct { |
| 179 | 175 | case: ZIRTransformCase, |
| 180 | 176 | target: std.Target, |
| 181 | 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 | 182 | prg_node.activate(); |
| 184 | 183 | defer prg_node.end(); |
| 185 | 184 | |
| 186 | var parse_node = prg_node.start("parse", null); | |
| 187 | parse_node.activate(); | |
| 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(); | |
| 185 | const tmp_src_path = "test-case.zir"; | |
| 186 | try tmp.dir.writeFile(tmp_src_path, case.src); | |
| 195 | 187 | |
| 196 | var analyze_node = prg_node.start("analyze", null); | |
| 197 | analyze_node.activate(); | |
| 198 | var analyzed_module = try ir.analyze(allocator, zir_module, .{ | |
| 188 | const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); | |
| 189 | defer root_pkg.destroy(); | |
| 190 | ||
| 191 | var module = try Module.init(allocator, .{ | |
| 199 | 192 | .target = target, |
| 200 | 193 | .output_mode = .Obj, |
| 201 | .link_mode = .Static, | |
| 202 | 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); | |
| 205 | if (analyzed_module.errors.len != 0) { | |
| 206 | debugPrintErrors(case.src, analyzed_module.errors); | |
| 207 | return error.ParseFailure; | |
| 208 | } | |
| 209 | analyze_node.end(); | |
| 199 | defer module.deinit(); | |
| 200 | ||
| 201 | var module_node = prg_node.start("parse/analysis/codegen", null); | |
| 202 | module_node.activate(); | |
| 203 | try module.update(); | |
| 204 | module_node.end(); | |
| 210 | 205 | |
| 211 | 206 | var emit_node = prg_node.start("emit", null); |
| 212 | 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 | 209 | defer new_zir_module.deinit(allocator); |
| 215 | 210 | emit_node.end(); |
| 216 | 211 |
src-self-hosted/type.zig+231-3| ... | ... | @@ -5,8 +5,7 @@ const Allocator = std.mem.Allocator; |
| 5 | 5 | const Target = std.Target; |
| 6 | 6 | |
| 7 | 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. | |
| 9 | /// It is not copyable since it may contain references to its inner data. | |
| 8 | /// It's important for this type to be small. | |
| 10 | 9 | /// Types are not de-duplicated, which helps with multi-threading since it obviates the requirement |
| 11 | 10 | /// of obtaining a lock on a global type table, as well as making the |
| 12 | 11 | /// garbage collection bookkeeping simpler. |
| ... | ... | @@ -51,7 +50,9 @@ pub const Type = extern union { |
| 51 | 50 | .comptime_int => return .ComptimeInt, |
| 52 | 51 | .comptime_float => return .ComptimeFloat, |
| 53 | 52 | .noreturn => return .NoReturn, |
| 53 | .@"null" => return .Null, | |
| 54 | 54 | |
| 55 | .fn_noreturn_no_args => return .Fn, | |
| 55 | 56 | .fn_naked_noreturn_no_args => return .Fn, |
| 56 | 57 | .fn_ccc_void_no_args => return .Fn, |
| 57 | 58 | |
| ... | ... | @@ -183,7 +184,10 @@ pub const Type = extern union { |
| 183 | 184 | .noreturn, |
| 184 | 185 | => return out_stream.writeAll(@tagName(t)), |
| 185 | 186 | |
| 187 | .@"null" => return out_stream.writeAll("@TypeOf(null)"), | |
| 188 | ||
| 186 | 189 | .const_slice_u8 => return out_stream.writeAll("[]const u8"), |
| 190 | .fn_noreturn_no_args => return out_stream.writeAll("fn() noreturn"), | |
| 187 | 191 | .fn_naked_noreturn_no_args => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), |
| 188 | 192 | .fn_ccc_void_no_args => return out_stream.writeAll("fn() callconv(.C) void"), |
| 189 | 193 | .single_const_pointer_to_comptime_int => return out_stream.writeAll("*const comptime_int"), |
| ... | ... | @@ -244,6 +248,8 @@ pub const Type = extern union { |
| 244 | 248 | .comptime_int => return Value.initTag(.comptime_int_type), |
| 245 | 249 | .comptime_float => return Value.initTag(.comptime_float_type), |
| 246 | 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 | 253 | .fn_naked_noreturn_no_args => return Value.initTag(.fn_naked_noreturn_no_args_type), |
| 248 | 254 | .fn_ccc_void_no_args => return Value.initTag(.fn_ccc_void_no_args_type), |
| 249 | 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 | 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 | 369 | pub fn isSinglePointer(self: Type) bool { |
| 260 | 370 | return switch (self.tag()) { |
| 261 | 371 | .u8, |
| ... | ... | @@ -283,9 +393,11 @@ pub const Type = extern union { |
| 283 | 393 | .comptime_int, |
| 284 | 394 | .comptime_float, |
| 285 | 395 | .noreturn, |
| 396 | .@"null", | |
| 286 | 397 | .array, |
| 287 | 398 | .array_u8_sentinel_0, |
| 288 | 399 | .const_slice_u8, |
| 400 | .fn_noreturn_no_args, | |
| 289 | 401 | .fn_naked_noreturn_no_args, |
| 290 | 402 | .fn_ccc_void_no_args, |
| 291 | 403 | .int_unsigned, |
| ... | ... | @@ -325,10 +437,12 @@ pub const Type = extern union { |
| 325 | 437 | .comptime_int, |
| 326 | 438 | .comptime_float, |
| 327 | 439 | .noreturn, |
| 440 | .@"null", | |
| 328 | 441 | .array, |
| 329 | 442 | .array_u8_sentinel_0, |
| 330 | 443 | .single_const_pointer, |
| 331 | 444 | .single_const_pointer_to_comptime_int, |
| 445 | .fn_noreturn_no_args, | |
| 332 | 446 | .fn_naked_noreturn_no_args, |
| 333 | 447 | .fn_ccc_void_no_args, |
| 334 | 448 | .int_unsigned, |
| ... | ... | @@ -367,8 +481,10 @@ pub const Type = extern union { |
| 367 | 481 | .comptime_int, |
| 368 | 482 | .comptime_float, |
| 369 | 483 | .noreturn, |
| 484 | .@"null", | |
| 370 | 485 | .array, |
| 371 | 486 | .array_u8_sentinel_0, |
| 487 | .fn_noreturn_no_args, | |
| 372 | 488 | .fn_naked_noreturn_no_args, |
| 373 | 489 | .fn_ccc_void_no_args, |
| 374 | 490 | .int_unsigned, |
| ... | ... | @@ -410,6 +526,8 @@ pub const Type = extern union { |
| 410 | 526 | .comptime_int, |
| 411 | 527 | .comptime_float, |
| 412 | 528 | .noreturn, |
| 529 | .@"null", | |
| 530 | .fn_noreturn_no_args, | |
| 413 | 531 | .fn_naked_noreturn_no_args, |
| 414 | 532 | .fn_ccc_void_no_args, |
| 415 | 533 | .int_unsigned, |
| ... | ... | @@ -451,6 +569,8 @@ pub const Type = extern union { |
| 451 | 569 | .comptime_int, |
| 452 | 570 | .comptime_float, |
| 453 | 571 | .noreturn, |
| 572 | .@"null", | |
| 573 | .fn_noreturn_no_args, | |
| 454 | 574 | .fn_naked_noreturn_no_args, |
| 455 | 575 | .fn_ccc_void_no_args, |
| 456 | 576 | .single_const_pointer, |
| ... | ... | @@ -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 | 632 | /// Returns true if and only if the type is a fixed-width, signed integer. |
| 469 | 633 | pub fn isSignedInt(self: Type) bool { |
| 470 | 634 | return switch (self.tag()) { |
| ... | ... | @@ -481,6 +645,8 @@ pub const Type = extern union { |
| 481 | 645 | .comptime_int, |
| 482 | 646 | .comptime_float, |
| 483 | 647 | .noreturn, |
| 648 | .@"null", | |
| 649 | .fn_noreturn_no_args, | |
| 484 | 650 | .fn_naked_noreturn_no_args, |
| 485 | 651 | .fn_ccc_void_no_args, |
| 486 | 652 | .array, |
| ... | ... | @@ -524,6 +690,8 @@ pub const Type = extern union { |
| 524 | 690 | .comptime_int, |
| 525 | 691 | .comptime_float, |
| 526 | 692 | .noreturn, |
| 693 | .@"null", | |
| 694 | .fn_noreturn_no_args, | |
| 527 | 695 | .fn_naked_noreturn_no_args, |
| 528 | 696 | .fn_ccc_void_no_args, |
| 529 | 697 | .array, |
| ... | ... | @@ -579,6 +747,7 @@ pub const Type = extern union { |
| 579 | 747 | /// Asserts the type is a function. |
| 580 | 748 | pub fn fnParamLen(self: Type) usize { |
| 581 | 749 | return switch (self.tag()) { |
| 750 | .fn_noreturn_no_args => 0, | |
| 582 | 751 | .fn_naked_noreturn_no_args => 0, |
| 583 | 752 | .fn_ccc_void_no_args => 0, |
| 584 | 753 | |
| ... | ... | @@ -595,6 +764,7 @@ pub const Type = extern union { |
| 595 | 764 | .comptime_int, |
| 596 | 765 | .comptime_float, |
| 597 | 766 | .noreturn, |
| 767 | .@"null", | |
| 598 | 768 | .array, |
| 599 | 769 | .single_const_pointer, |
| 600 | 770 | .single_const_pointer_to_comptime_int, |
| ... | ... | @@ -622,6 +792,7 @@ pub const Type = extern union { |
| 622 | 792 | /// given by `fnParamLen`. |
| 623 | 793 | pub fn fnParamTypes(self: Type, types: []Type) void { |
| 624 | 794 | switch (self.tag()) { |
| 795 | .fn_noreturn_no_args => return, | |
| 625 | 796 | .fn_naked_noreturn_no_args => return, |
| 626 | 797 | .fn_ccc_void_no_args => return, |
| 627 | 798 | |
| ... | ... | @@ -638,6 +809,7 @@ pub const Type = extern union { |
| 638 | 809 | .comptime_int, |
| 639 | 810 | .comptime_float, |
| 640 | 811 | .noreturn, |
| 812 | .@"null", | |
| 641 | 813 | .array, |
| 642 | 814 | .single_const_pointer, |
| 643 | 815 | .single_const_pointer_to_comptime_int, |
| ... | ... | @@ -664,6 +836,7 @@ pub const Type = extern union { |
| 664 | 836 | /// Asserts the type is a function. |
| 665 | 837 | pub fn fnReturnType(self: Type) Type { |
| 666 | 838 | return switch (self.tag()) { |
| 839 | .fn_noreturn_no_args => Type.initTag(.noreturn), | |
| 667 | 840 | .fn_naked_noreturn_no_args => Type.initTag(.noreturn), |
| 668 | 841 | .fn_ccc_void_no_args => Type.initTag(.void), |
| 669 | 842 | |
| ... | ... | @@ -680,6 +853,7 @@ pub const Type = extern union { |
| 680 | 853 | .comptime_int, |
| 681 | 854 | .comptime_float, |
| 682 | 855 | .noreturn, |
| 856 | .@"null", | |
| 683 | 857 | .array, |
| 684 | 858 | .single_const_pointer, |
| 685 | 859 | .single_const_pointer_to_comptime_int, |
| ... | ... | @@ -706,6 +880,7 @@ pub const Type = extern union { |
| 706 | 880 | /// Asserts the type is a function. |
| 707 | 881 | pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention { |
| 708 | 882 | return switch (self.tag()) { |
| 883 | .fn_noreturn_no_args => .Unspecified, | |
| 709 | 884 | .fn_naked_noreturn_no_args => .Naked, |
| 710 | 885 | .fn_ccc_void_no_args => .C, |
| 711 | 886 | |
| ... | ... | @@ -722,6 +897,51 @@ pub const Type = extern union { |
| 722 | 897 | .comptime_int, |
| 723 | 898 | .comptime_float, |
| 724 | 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 | 945 | .array, |
| 726 | 946 | .single_const_pointer, |
| 727 | 947 | .single_const_pointer_to_comptime_int, |
| ... | ... | @@ -776,6 +996,8 @@ pub const Type = extern union { |
| 776 | 996 | .type, |
| 777 | 997 | .anyerror, |
| 778 | 998 | .noreturn, |
| 999 | .@"null", | |
| 1000 | .fn_noreturn_no_args, | |
| 779 | 1001 | .fn_naked_noreturn_no_args, |
| 780 | 1002 | .fn_ccc_void_no_args, |
| 781 | 1003 | .array, |
| ... | ... | @@ -812,6 +1034,7 @@ pub const Type = extern union { |
| 812 | 1034 | .bool, |
| 813 | 1035 | .type, |
| 814 | 1036 | .anyerror, |
| 1037 | .fn_noreturn_no_args, | |
| 815 | 1038 | .fn_naked_noreturn_no_args, |
| 816 | 1039 | .fn_ccc_void_no_args, |
| 817 | 1040 | .single_const_pointer_to_comptime_int, |
| ... | ... | @@ -822,6 +1045,7 @@ pub const Type = extern union { |
| 822 | 1045 | .c_void, |
| 823 | 1046 | .void, |
| 824 | 1047 | .noreturn, |
| 1048 | .@"null", | |
| 825 | 1049 | => return true, |
| 826 | 1050 | |
| 827 | 1051 | .int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0, |
| ... | ... | @@ -865,6 +1089,7 @@ pub const Type = extern union { |
| 865 | 1089 | .bool, |
| 866 | 1090 | .type, |
| 867 | 1091 | .anyerror, |
| 1092 | .fn_noreturn_no_args, | |
| 868 | 1093 | .fn_naked_noreturn_no_args, |
| 869 | 1094 | .fn_ccc_void_no_args, |
| 870 | 1095 | .single_const_pointer_to_comptime_int, |
| ... | ... | @@ -873,6 +1098,7 @@ pub const Type = extern union { |
| 873 | 1098 | .c_void, |
| 874 | 1099 | .void, |
| 875 | 1100 | .noreturn, |
| 1101 | .@"null", | |
| 876 | 1102 | .int_unsigned, |
| 877 | 1103 | .int_signed, |
| 878 | 1104 | .array, |
| ... | ... | @@ -902,11 +1128,11 @@ pub const Type = extern union { |
| 902 | 1128 | c_longlong, |
| 903 | 1129 | c_ulonglong, |
| 904 | 1130 | c_longdouble, |
| 905 | c_void, | |
| 906 | 1131 | f16, |
| 907 | 1132 | f32, |
| 908 | 1133 | f64, |
| 909 | 1134 | f128, |
| 1135 | c_void, | |
| 910 | 1136 | bool, |
| 911 | 1137 | void, |
| 912 | 1138 | type, |
| ... | ... | @@ -914,6 +1140,8 @@ pub const Type = extern union { |
| 914 | 1140 | comptime_int, |
| 915 | 1141 | comptime_float, |
| 916 | 1142 | noreturn, |
| 1143 | @"null", | |
| 1144 | fn_noreturn_no_args, | |
| 917 | 1145 | fn_naked_noreturn_no_args, |
| 918 | 1146 | fn_ccc_void_no_args, |
| 919 | 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 | 6 | const BigIntMutable = std.math.big.int.Mutable; |
| 7 | 7 | const Target = std.Target; |
| 8 | 8 | const Allocator = std.mem.Allocator; |
| 9 | const Module = @import("Module.zig"); | |
| 9 | 10 | |
| 10 | 11 | /// This is the raw data, with no bookkeeping, no memory awareness, |
| 11 | 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 | 14 | /// This union takes advantage of the fact that the first page of memory |
| 14 | 15 | /// is unmapped, giving us 4096 possible enum tags that have no payload. |
| 15 | 16 | pub const Value = extern union { |
| ... | ... | @@ -45,6 +46,8 @@ pub const Value = extern union { |
| 45 | 46 | comptime_int_type, |
| 46 | 47 | comptime_float_type, |
| 47 | 48 | noreturn_type, |
| 49 | null_type, | |
| 50 | fn_noreturn_no_args_type, | |
| 48 | 51 | fn_naked_noreturn_no_args_type, |
| 49 | 52 | fn_ccc_void_no_args_type, |
| 50 | 53 | single_const_pointer_to_comptime_int_type, |
| ... | ... | @@ -64,8 +67,9 @@ pub const Value = extern union { |
| 64 | 67 | int_big_positive, |
| 65 | 68 | int_big_negative, |
| 66 | 69 | function, |
| 67 | ref, | |
| 68 | 70 | ref_val, |
| 71 | decl_ref, | |
| 72 | elem_ptr, | |
| 69 | 73 | bytes, |
| 70 | 74 | repeated, // the value is a value repeated some number of times |
| 71 | 75 | |
| ... | ... | @@ -136,6 +140,8 @@ pub const Value = extern union { |
| 136 | 140 | .comptime_int_type => return out_stream.writeAll("comptime_int"), |
| 137 | 141 | .comptime_float_type => return out_stream.writeAll("comptime_float"), |
| 138 | 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 | 145 | .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), |
| 140 | 146 | .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"), |
| 141 | 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 | 159 | .int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}), |
| 154 | 160 | .int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}), |
| 155 | 161 | .function => return out_stream.writeAll("(function)"), |
| 156 | .ref => return out_stream.writeAll("(ref)"), | |
| 157 | 162 | .ref_val => { |
| 158 | try out_stream.writeAll("*const "); | |
| 159 | val = val.cast(Payload.RefVal).?.val; | |
| 160 | continue; | |
| 163 | const ref_val = val.cast(Payload.RefVal).?; | |
| 164 | try out_stream.writeAll("&const "); | |
| 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 | 173 | .bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream), |
| 163 | 174 | .repeated => { |
| ... | ... | @@ -169,10 +180,17 @@ pub const Value = extern union { |
| 169 | 180 | |
| 170 | 181 | /// Asserts that the value is representable as an array of bytes. |
| 171 | 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 | 184 | if (self.cast(Payload.Bytes)) |bytes| { |
| 174 | 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 | 194 | unreachable; |
| 177 | 195 | } |
| 178 | 196 | |
| ... | ... | @@ -181,31 +199,33 @@ pub const Value = extern union { |
| 181 | 199 | return switch (self.tag()) { |
| 182 | 200 | .ty => self.cast(Payload.Ty).?.ty, |
| 183 | 201 | |
| 184 | .u8_type => Type.initTag(.@"u8"), | |
| 185 | .i8_type => Type.initTag(.@"i8"), | |
| 186 | .isize_type => Type.initTag(.@"isize"), | |
| 187 | .usize_type => Type.initTag(.@"usize"), | |
| 188 | .c_short_type => Type.initTag(.@"c_short"), | |
| 189 | .c_ushort_type => Type.initTag(.@"c_ushort"), | |
| 190 | .c_int_type => Type.initTag(.@"c_int"), | |
| 191 | .c_uint_type => Type.initTag(.@"c_uint"), | |
| 192 | .c_long_type => Type.initTag(.@"c_long"), | |
| 193 | .c_ulong_type => Type.initTag(.@"c_ulong"), | |
| 194 | .c_longlong_type => Type.initTag(.@"c_longlong"), | |
| 195 | .c_ulonglong_type => Type.initTag(.@"c_ulonglong"), | |
| 196 | .c_longdouble_type => Type.initTag(.@"c_longdouble"), | |
| 197 | .f16_type => Type.initTag(.@"f16"), | |
| 198 | .f32_type => Type.initTag(.@"f32"), | |
| 199 | .f64_type => Type.initTag(.@"f64"), | |
| 200 | .f128_type => Type.initTag(.@"f128"), | |
| 201 | .c_void_type => Type.initTag(.@"c_void"), | |
| 202 | .bool_type => Type.initTag(.@"bool"), | |
| 203 | .void_type => Type.initTag(.@"void"), | |
| 204 | .type_type => Type.initTag(.@"type"), | |
| 205 | .anyerror_type => Type.initTag(.@"anyerror"), | |
| 206 | .comptime_int_type => Type.initTag(.@"comptime_int"), | |
| 207 | .comptime_float_type => Type.initTag(.@"comptime_float"), | |
| 208 | .noreturn_type => Type.initTag(.@"noreturn"), | |
| 202 | .u8_type => Type.initTag(.u8), | |
| 203 | .i8_type => Type.initTag(.i8), | |
| 204 | .isize_type => Type.initTag(.isize), | |
| 205 | .usize_type => Type.initTag(.usize), | |
| 206 | .c_short_type => Type.initTag(.c_short), | |
| 207 | .c_ushort_type => Type.initTag(.c_ushort), | |
| 208 | .c_int_type => Type.initTag(.c_int), | |
| 209 | .c_uint_type => Type.initTag(.c_uint), | |
| 210 | .c_long_type => Type.initTag(.c_long), | |
| 211 | .c_ulong_type => Type.initTag(.c_ulong), | |
| 212 | .c_longlong_type => Type.initTag(.c_longlong), | |
| 213 | .c_ulonglong_type => Type.initTag(.c_ulonglong), | |
| 214 | .c_longdouble_type => Type.initTag(.c_longdouble), | |
| 215 | .f16_type => Type.initTag(.f16), | |
| 216 | .f32_type => Type.initTag(.f32), | |
| 217 | .f64_type => Type.initTag(.f64), | |
| 218 | .f128_type => Type.initTag(.f128), | |
| 219 | .c_void_type => Type.initTag(.c_void), | |
| 220 | .bool_type => Type.initTag(.bool), | |
| 221 | .void_type => Type.initTag(.void), | |
| 222 | .type_type => Type.initTag(.type), | |
| 223 | .anyerror_type => Type.initTag(.anyerror), | |
| 224 | .comptime_int_type => Type.initTag(.comptime_int), | |
| 225 | .comptime_float_type => Type.initTag(.comptime_float), | |
| 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 | 229 | .fn_naked_noreturn_no_args_type => Type.initTag(.fn_naked_noreturn_no_args), |
| 210 | 230 | .fn_ccc_void_no_args_type => Type.initTag(.fn_ccc_void_no_args), |
| 211 | 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 | 242 | .int_big_positive, |
| 223 | 243 | .int_big_negative, |
| 224 | 244 | .function, |
| 225 | .ref, | |
| 226 | 245 | .ref_val, |
| 246 | .decl_ref, | |
| 247 | .elem_ptr, | |
| 227 | 248 | .bytes, |
| 228 | 249 | .repeated, |
| 229 | 250 | => unreachable, |
| ... | ... | @@ -259,6 +280,8 @@ pub const Value = extern union { |
| 259 | 280 | .comptime_int_type, |
| 260 | 281 | .comptime_float_type, |
| 261 | 282 | .noreturn_type, |
| 283 | .null_type, | |
| 284 | .fn_noreturn_no_args_type, | |
| 262 | 285 | .fn_naked_noreturn_no_args_type, |
| 263 | 286 | .fn_ccc_void_no_args_type, |
| 264 | 287 | .single_const_pointer_to_comptime_int_type, |
| ... | ... | @@ -267,8 +290,9 @@ pub const Value = extern union { |
| 267 | 290 | .bool_false, |
| 268 | 291 | .null_value, |
| 269 | 292 | .function, |
| 270 | .ref, | |
| 271 | 293 | .ref_val, |
| 294 | .decl_ref, | |
| 295 | .elem_ptr, | |
| 272 | 296 | .bytes, |
| 273 | 297 | .undef, |
| 274 | 298 | .repeated, |
| ... | ... | @@ -314,6 +338,8 @@ pub const Value = extern union { |
| 314 | 338 | .comptime_int_type, |
| 315 | 339 | .comptime_float_type, |
| 316 | 340 | .noreturn_type, |
| 341 | .null_type, | |
| 342 | .fn_noreturn_no_args_type, | |
| 317 | 343 | .fn_naked_noreturn_no_args_type, |
| 318 | 344 | .fn_ccc_void_no_args_type, |
| 319 | 345 | .single_const_pointer_to_comptime_int_type, |
| ... | ... | @@ -322,8 +348,9 @@ pub const Value = extern union { |
| 322 | 348 | .bool_false, |
| 323 | 349 | .null_value, |
| 324 | 350 | .function, |
| 325 | .ref, | |
| 326 | 351 | .ref_val, |
| 352 | .decl_ref, | |
| 353 | .elem_ptr, | |
| 327 | 354 | .bytes, |
| 328 | 355 | .undef, |
| 329 | 356 | .repeated, |
| ... | ... | @@ -370,6 +397,8 @@ pub const Value = extern union { |
| 370 | 397 | .comptime_int_type, |
| 371 | 398 | .comptime_float_type, |
| 372 | 399 | .noreturn_type, |
| 400 | .null_type, | |
| 401 | .fn_noreturn_no_args_type, | |
| 373 | 402 | .fn_naked_noreturn_no_args_type, |
| 374 | 403 | .fn_ccc_void_no_args_type, |
| 375 | 404 | .single_const_pointer_to_comptime_int_type, |
| ... | ... | @@ -378,8 +407,9 @@ pub const Value = extern union { |
| 378 | 407 | .bool_false, |
| 379 | 408 | .null_value, |
| 380 | 409 | .function, |
| 381 | .ref, | |
| 382 | 410 | .ref_val, |
| 411 | .decl_ref, | |
| 412 | .elem_ptr, | |
| 383 | 413 | .bytes, |
| 384 | 414 | .undef, |
| 385 | 415 | .repeated, |
| ... | ... | @@ -431,6 +461,8 @@ pub const Value = extern union { |
| 431 | 461 | .comptime_int_type, |
| 432 | 462 | .comptime_float_type, |
| 433 | 463 | .noreturn_type, |
| 464 | .null_type, | |
| 465 | .fn_noreturn_no_args_type, | |
| 434 | 466 | .fn_naked_noreturn_no_args_type, |
| 435 | 467 | .fn_ccc_void_no_args_type, |
| 436 | 468 | .single_const_pointer_to_comptime_int_type, |
| ... | ... | @@ -439,8 +471,9 @@ pub const Value = extern union { |
| 439 | 471 | .bool_false, |
| 440 | 472 | .null_value, |
| 441 | 473 | .function, |
| 442 | .ref, | |
| 443 | 474 | .ref_val, |
| 475 | .decl_ref, | |
| 476 | .elem_ptr, | |
| 444 | 477 | .bytes, |
| 445 | 478 | .repeated, |
| 446 | 479 | => unreachable, |
| ... | ... | @@ -521,6 +554,8 @@ pub const Value = extern union { |
| 521 | 554 | .comptime_int_type, |
| 522 | 555 | .comptime_float_type, |
| 523 | 556 | .noreturn_type, |
| 557 | .null_type, | |
| 558 | .fn_noreturn_no_args_type, | |
| 524 | 559 | .fn_naked_noreturn_no_args_type, |
| 525 | 560 | .fn_ccc_void_no_args_type, |
| 526 | 561 | .single_const_pointer_to_comptime_int_type, |
| ... | ... | @@ -529,8 +564,9 @@ pub const Value = extern union { |
| 529 | 564 | .bool_false, |
| 530 | 565 | .null_value, |
| 531 | 566 | .function, |
| 532 | .ref, | |
| 533 | 567 | .ref_val, |
| 568 | .decl_ref, | |
| 569 | .elem_ptr, | |
| 534 | 570 | .bytes, |
| 535 | 571 | .repeated, |
| 536 | 572 | .undef, |
| ... | ... | @@ -573,6 +609,8 @@ pub const Value = extern union { |
| 573 | 609 | .comptime_int_type, |
| 574 | 610 | .comptime_float_type, |
| 575 | 611 | .noreturn_type, |
| 612 | .null_type, | |
| 613 | .fn_noreturn_no_args_type, | |
| 576 | 614 | .fn_naked_noreturn_no_args_type, |
| 577 | 615 | .fn_ccc_void_no_args_type, |
| 578 | 616 | .single_const_pointer_to_comptime_int_type, |
| ... | ... | @@ -581,8 +619,9 @@ pub const Value = extern union { |
| 581 | 619 | .bool_false, |
| 582 | 620 | .null_value, |
| 583 | 621 | .function, |
| 584 | .ref, | |
| 585 | 622 | .ref_val, |
| 623 | .decl_ref, | |
| 624 | .elem_ptr, | |
| 586 | 625 | .bytes, |
| 587 | 626 | .repeated, |
| 588 | 627 | .undef, |
| ... | ... | @@ -636,7 +675,8 @@ pub const Value = extern union { |
| 636 | 675 | } |
| 637 | 676 | |
| 638 | 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 | 680 | return switch (self.tag()) { |
| 641 | 681 | .ty, |
| 642 | 682 | .u8_type, |
| ... | ... | @@ -664,6 +704,8 @@ pub const Value = extern union { |
| 664 | 704 | .comptime_int_type, |
| 665 | 705 | .comptime_float_type, |
| 666 | 706 | .noreturn_type, |
| 707 | .null_type, | |
| 708 | .fn_noreturn_no_args_type, | |
| 667 | 709 | .fn_naked_noreturn_no_args_type, |
| 668 | 710 | .fn_ccc_void_no_args_type, |
| 669 | 711 | .single_const_pointer_to_comptime_int_type, |
| ... | ... | @@ -683,14 +725,19 @@ pub const Value = extern union { |
| 683 | 725 | => unreachable, |
| 684 | 726 | |
| 685 | 727 | .the_one_possible_value => Value.initTag(.the_one_possible_value), |
| 686 | .ref => self.cast(Payload.Ref).?.cell.contents, | |
| 687 | 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 | 738 | /// Asserts the value is a single-item pointer to an array, or an array, |
| 692 | 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 | 741 | switch (self.tag()) { |
| 695 | 742 | .ty, |
| 696 | 743 | .u8_type, |
| ... | ... | @@ -718,6 +765,8 @@ pub const Value = extern union { |
| 718 | 765 | .comptime_int_type, |
| 719 | 766 | .comptime_float_type, |
| 720 | 767 | .noreturn_type, |
| 768 | .null_type, | |
| 769 | .fn_noreturn_no_args_type, | |
| 721 | 770 | .fn_naked_noreturn_no_args_type, |
| 722 | 771 | .fn_ccc_void_no_args_type, |
| 723 | 772 | .single_const_pointer_to_comptime_int_type, |
| ... | ... | @@ -733,13 +782,13 @@ pub const Value = extern union { |
| 733 | 782 | .int_big_positive, |
| 734 | 783 | .int_big_negative, |
| 735 | 784 | .undef, |
| 785 | .elem_ptr, | |
| 786 | .ref_val, | |
| 787 | .decl_ref, | |
| 736 | 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 | 790 | .bytes => { |
| 742 | const int_payload = try allocator.create(Value.Payload.Int_u64); | |
| 791 | const int_payload = try allocator.create(Payload.Int_u64); | |
| 743 | 792 | int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] }; |
| 744 | 793 | return Value.initPayload(&int_payload.base); |
| 745 | 794 | }, |
| ... | ... | @@ -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 | 812 | pub fn isUndef(self: Value) bool { |
| 753 | 813 | return self.tag() == .undef; |
| 754 | 814 | } |
| ... | ... | @@ -783,6 +843,8 @@ pub const Value = extern union { |
| 783 | 843 | .comptime_int_type, |
| 784 | 844 | .comptime_float_type, |
| 785 | 845 | .noreturn_type, |
| 846 | .null_type, | |
| 847 | .fn_noreturn_no_args_type, | |
| 786 | 848 | .fn_naked_noreturn_no_args_type, |
| 787 | 849 | .fn_ccc_void_no_args_type, |
| 788 | 850 | .single_const_pointer_to_comptime_int_type, |
| ... | ... | @@ -796,8 +858,9 @@ pub const Value = extern union { |
| 796 | 858 | .int_i64, |
| 797 | 859 | .int_big_positive, |
| 798 | 860 | .int_big_negative, |
| 799 | .ref, | |
| 800 | 861 | .ref_val, |
| 862 | .decl_ref, | |
| 863 | .elem_ptr, | |
| 801 | 864 | .bytes, |
| 802 | 865 | .repeated, |
| 803 | 866 | => false, |
| ... | ... | @@ -841,8 +904,7 @@ pub const Value = extern union { |
| 841 | 904 | |
| 842 | 905 | pub const Function = struct { |
| 843 | 906 | base: Payload = Payload{ .tag = .function }, |
| 844 | /// Index into the `fns` array of the `ir.Module` | |
| 845 | index: usize, | |
| 907 | func: *Module.Fn, | |
| 846 | 908 | }; |
| 847 | 909 | |
| 848 | 910 | pub const ArraySentinel0_u8_Type = struct { |
| ... | ... | @@ -855,16 +917,24 @@ pub const Value = extern union { |
| 855 | 917 | elem_type: *Type, |
| 856 | 918 | }; |
| 857 | 919 | |
| 858 | pub const Ref = struct { | |
| 859 | base: Payload = Payload{ .tag = .ref }, | |
| 860 | cell: *MemoryCell, | |
| 861 | }; | |
| 862 | ||
| 920 | /// Represents a pointer to another immutable value. | |
| 863 | 921 | pub const RefVal = struct { |
| 864 | 922 | base: Payload = Payload{ .tag = .ref_val }, |
| 865 | 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 | 938 | pub const Bytes = struct { |
| 869 | 939 | base: Payload = Payload{ .tag = .bytes }, |
| 870 | 940 | data: []const u8, |
| ... | ... | @@ -890,29 +960,3 @@ pub const Value = extern union { |
| 890 | 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 | 1794 | } |
| 1795 | 1795 | |
| 1796 | 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 | 1807 | assert(g->errors.length != 0); |
| 1798 | 1808 | for (size_t i = 0; i < g->errors.length; i += 1) { |
| 1799 | 1809 | ErrorMsg *err = g->errors.at(i); |
test/stage2/zir.zig+163-58| ... | ... | @@ -1,7 +1,15 @@ |
| 1 | const std = @import("std"); | |
| 1 | 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 | 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 | 13 | \\@void = primitive(void) |
| 6 | 14 | \\@usize = primitive(usize) |
| 7 | 15 | \\@fnty = fntype([], @void, cc=C) |
| ... | ... | @@ -12,10 +20,11 @@ pub fn addCases(ctx: *TestContext) void { |
| 12 | 20 | \\ |
| 13 | 21 | \\@entry = fn(@fnty, { |
| 14 | 22 | \\ %a = str("\x32\x08\x01\x0a") |
| 15 | \\ %eptr0 = elemptr(%a, @0) | |
| 16 | \\ %eptr1 = elemptr(%a, @1) | |
| 17 | \\ %eptr2 = elemptr(%a, @2) | |
| 18 | \\ %eptr3 = elemptr(%a, @3) | |
| 23 | \\ %aref = ref(%a) | |
| 24 | \\ %eptr0 = elemptr(%aref, @0) | |
| 25 | \\ %eptr1 = elemptr(%aref, @1) | |
| 26 | \\ %eptr2 = elemptr(%aref, @2) | |
| 27 | \\ %eptr3 = elemptr(%aref, @3) | |
| 19 | 28 | \\ %v0 = deref(%eptr0) |
| 20 | 29 | \\ %v1 = deref(%eptr1) |
| 21 | 30 | \\ %v2 = deref(%eptr2) |
| ... | ... | @@ -34,7 +43,8 @@ pub fn addCases(ctx: *TestContext) void { |
| 34 | 43 | \\}) |
| 35 | 44 | \\ |
| 36 | 45 | \\@9 = str("entry") |
| 37 | \\@10 = export(@9, @entry) | |
| 46 | \\@10 = ref(@9) | |
| 47 | \\@11 = export(@10, @entry) | |
| 38 | 48 | , |
| 39 | 49 | \\@0 = primitive(void) |
| 40 | 50 | \\@1 = fntype([], @0, cc=C) |
| ... | ... | @@ -42,66 +52,161 @@ pub fn addCases(ctx: *TestContext) void { |
| 42 | 52 | \\ %0 = return() |
| 43 | 53 | \\}) |
| 44 | 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 | |
| 50 | @import("std").Target.current.cpu.arch != .x86_64) | |
| 60 | if (std.Target.current.os.tag != .linux or | |
| 61 | std.Target.current.cpu.arch != .x86_64) | |
| 51 | 62 | { |
| 52 | 63 | // TODO implement self-hosted PE (.exe file) linking |
| 53 | 64 | // TODO implement more ZIR so we don't depend on x86_64-linux |
| 54 | 65 | return; |
| 55 | 66 | } |
| 56 | 67 | |
| 57 | ctx.addZIRCompareOutput("hello world ZIR", | |
| 58 | \\@0 = str("Hello, world!\n") | |
| 59 | \\@1 = primitive(noreturn) | |
| 60 | \\@2 = primitive(usize) | |
| 61 | \\@3 = fntype([], @1, cc=Naked) | |
| 62 | \\@4 = int(0) | |
| 63 | \\@5 = int(1) | |
| 64 | \\@6 = int(231) | |
| 65 | \\@7 = str("len") | |
| 66 | \\ | |
| 67 | \\@8 = fn(@3, { | |
| 68 | \\ %0 = as(@2, @5) ; SYS_write | |
| 69 | \\ %1 = as(@2, @5) ; STDOUT_FILENO | |
| 70 | \\ %2 = ptrtoint(@0) ; msg ptr | |
| 71 | \\ %3 = fieldptr(@0, @7) ; msg len ptr | |
| 72 | \\ %4 = deref(%3) ; msg len | |
| 73 | \\ %sysoutreg = str("={rax}") | |
| 74 | \\ %rax = str("{rax}") | |
| 75 | \\ %rdi = str("{rdi}") | |
| 76 | \\ %rsi = str("{rsi}") | |
| 77 | \\ %rdx = str("{rdx}") | |
| 78 | \\ %rcx = str("rcx") | |
| 79 | \\ %r11 = str("r11") | |
| 80 | \\ %memory = str("memory") | |
| 81 | \\ %syscall = str("syscall") | |
| 82 | \\ %5 = asm(%syscall, @2, | |
| 83 | \\ volatile=1, | |
| 84 | \\ output=%sysoutreg, | |
| 85 | \\ inputs=[%rax, %rdi, %rsi, %rdx], | |
| 86 | \\ clobbers=[%rcx, %r11, %memory], | |
| 87 | \\ args=[%0, %1, %2, %4]) | |
| 88 | \\ | |
| 89 | \\ %6 = as(@2, @6) ;SYS_exit_group | |
| 90 | \\ %7 = as(@2, @4) ;exit code | |
| 91 | \\ %8 = asm(%syscall, @2, | |
| 92 | \\ volatile=1, | |
| 93 | \\ output=%sysoutreg, | |
| 94 | \\ inputs=[%rax, %rdi], | |
| 95 | \\ clobbers=[%rcx, %r11, %memory], | |
| 96 | \\ args=[%6, %7]) | |
| 97 | \\ | |
| 98 | \\ %9 = unreachable() | |
| 99 | \\}) | |
| 100 | \\ | |
| 101 | \\@9 = str("_start") | |
| 102 | \\@10 = export(@9, @8) | |
| 103 | , | |
| 104 | \\Hello, world! | |
| 105 | \\ | |
| 68 | ctx.addZIRCompareOutput( | |
| 69 | "hello world ZIR, update msg", | |
| 70 | &[_][]const u8{ | |
| 71 | \\@noreturn = primitive(noreturn) | |
| 72 | \\@void = primitive(void) | |
| 73 | \\@usize = primitive(usize) | |
| 74 | \\@0 = int(0) | |
| 75 | \\@1 = int(1) | |
| 76 | \\@2 = int(2) | |
| 77 | \\@3 = int(3) | |
| 78 | \\ | |
| 79 | \\@syscall_array = str("syscall") | |
| 80 | \\@sysoutreg_array = str("={rax}") | |
| 81 | \\@rax_array = str("{rax}") | |
| 82 | \\@rdi_array = str("{rdi}") | |
| 83 | \\@rcx_array = str("rcx") | |
| 84 | \\@r11_array = str("r11") | |
| 85 | \\@rdx_array = str("{rdx}") | |
| 86 | \\@rsi_array = str("{rsi}") | |
| 87 | \\@memory_array = str("memory") | |
| 88 | \\@len_array = str("len") | |
| 89 | \\ | |
| 90 | \\@msg = str("Hello, world!\n") | |
| 91 | \\ | |
| 92 | \\@start_fnty = fntype([], @noreturn, cc=Naked) | |
| 93 | \\@start = fn(@start_fnty, { | |
| 94 | \\ %SYS_exit_group = int(231) | |
| 95 | \\ %exit_code = as(@usize, @0) | |
| 96 | \\ | |
| 97 | \\ %syscall = ref(@syscall_array) | |
| 98 | \\ %sysoutreg = ref(@sysoutreg_array) | |
| 99 | \\ %rax = ref(@rax_array) | |
| 100 | \\ %rdi = ref(@rdi_array) | |
| 101 | \\ %rcx = ref(@rcx_array) | |
| 102 | \\ %rdx = ref(@rdx_array) | |
| 103 | \\ %rsi = ref(@rsi_array) | |
| 104 | \\ %r11 = ref(@r11_array) | |
| 105 | \\ %memory = ref(@memory_array) | |
| 106 | \\ | |
| 107 | \\ %SYS_write = as(@usize, @1) | |
| 108 | \\ %STDOUT_FILENO = as(@usize, @1) | |
| 109 | \\ | |
| 110 | \\ %msg_ptr = ref(@msg) | |
| 111 | \\ %msg_addr = ptrtoint(%msg_ptr) | |
| 112 | \\ | |
| 113 | \\ %len_name = ref(@len_array) | |
| 114 | \\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name) | |
| 115 | \\ %msg_len = deref(%msg_len_ptr) | |
| 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 | } |