| author | |
| committer | |
| log | a489ea0b2f38c67025c2b2424749a9a7320cdd5a |
| tree | b27dffca9c3c26ef8cb33776dfbdf1941e7f6e55 |
| parent | 0e1c7209e8632ebf398e60de9053e2e0fe8b5661 |
| parent | bf56cdd9edffd5b97d2084b46cda6e6a89a391c1 |
14 files changed, 1395 insertions(+), 438 deletions(-)
lib/std/array_list.zig+60| ... | ... | @@ -257,6 +257,24 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { |
| 257 | 257 | return &self.items[self.items.len - 1]; |
| 258 | 258 | } |
| 259 | 259 | |
| 260 | /// Resize the array, adding `n` new elements, which have `undefined` values. | |
| 261 | /// The return value is an array pointing to the newly allocated elements. | |
| 262 | pub fn addManyAsArray(self: *Self, comptime n: usize) !*[n]T { | |
| 263 | const prev_len = self.items.len; | |
| 264 | try self.resize(self.items.len + n); | |
| 265 | return self.items[prev_len..][0..n]; | |
| 266 | } | |
| 267 | ||
| 268 | /// Resize the array, adding `n` new elements, which have `undefined` values. | |
| 269 | /// The return value is an array pointing to the newly allocated elements. | |
| 270 | /// Asserts that there is already space for the new item without allocating more. | |
| 271 | pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T { | |
| 272 | assert(self.items.len + n <= self.capacity); | |
| 273 | const prev_len = self.items.len; | |
| 274 | self.items.len += n; | |
| 275 | return self.items[prev_len..][0..n]; | |
| 276 | } | |
| 277 | ||
| 260 | 278 | /// Remove and return the last element from the list. |
| 261 | 279 | /// Asserts the list has at least one item. |
| 262 | 280 | pub fn pop(self: *Self) T { |
| ... | ... | @@ -488,6 +506,24 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ |
| 488 | 506 | return &self.items[self.items.len - 1]; |
| 489 | 507 | } |
| 490 | 508 | |
| 509 | /// Resize the array, adding `n` new elements, which have `undefined` values. | |
| 510 | /// The return value is an array pointing to the newly allocated elements. | |
| 511 | pub fn addManyAsArray(self: *Self, allocator: *Allocator, comptime n: usize) !*[n]T { | |
| 512 | const prev_len = self.items.len; | |
| 513 | try self.resize(allocator, self.items.len + n); | |
| 514 | return self.items[prev_len..][0..n]; | |
| 515 | } | |
| 516 | ||
| 517 | /// Resize the array, adding `n` new elements, which have `undefined` values. | |
| 518 | /// The return value is an array pointing to the newly allocated elements. | |
| 519 | /// Asserts that there is already space for the new item without allocating more. | |
| 520 | pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T { | |
| 521 | assert(self.items.len + n <= self.capacity); | |
| 522 | const prev_len = self.items.len; | |
| 523 | self.items.len += n; | |
| 524 | return self.items[prev_len..][0..n]; | |
| 525 | } | |
| 526 | ||
| 491 | 527 | /// Remove and return the last element from the list. |
| 492 | 528 | /// Asserts the list has at least one item. |
| 493 | 529 | /// This operation does not invalidate any element pointers. |
| ... | ... | @@ -727,3 +763,27 @@ test "std.ArrayList.writer" { |
| 727 | 763 | try writer.writeAll("efg"); |
| 728 | 764 | testing.expectEqualSlices(u8, list.items, "abcdefg"); |
| 729 | 765 | } |
| 766 | ||
| 767 | test "addManyAsArray" { | |
| 768 | const a = std.testing.allocator; | |
| 769 | { | |
| 770 | var list = ArrayList(u8).init(a); | |
| 771 | defer list.deinit(); | |
| 772 | ||
| 773 | (try list.addManyAsArray(4)).* = "aoeu".*; | |
| 774 | try list.ensureCapacity(8); | |
| 775 | list.addManyAsArrayAssumeCapacity(4).* = "asdf".*; | |
| 776 | ||
| 777 | testing.expectEqualSlices(u8, list.items, "aoeuasdf"); | |
| 778 | } | |
| 779 | { | |
| 780 | var list = ArrayListUnmanaged(u8){}; | |
| 781 | defer list.deinit(a); | |
| 782 | ||
| 783 | (try list.addManyAsArray(a, 4)).* = "aoeu".*; | |
| 784 | try list.ensureCapacity(a, 8); | |
| 785 | list.addManyAsArrayAssumeCapacity(4).* = "asdf".*; | |
| 786 | ||
| 787 | testing.expectEqualSlices(u8, list.items, "aoeuasdf"); | |
| 788 | } | |
| 789 | } |
lib/std/hash_map.zig+4| ... | ... | @@ -15,6 +15,10 @@ pub fn AutoHashMap(comptime K: type, comptime V: type) type { |
| 15 | 15 | return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K)); |
| 16 | 16 | } |
| 17 | 17 | |
| 18 | pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type { | |
| 19 | return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K)); | |
| 20 | } | |
| 21 | ||
| 18 | 22 | /// Builtin hashmap for strings as keys. |
| 19 | 23 | pub fn StringHashMap(comptime V: type) type { |
| 20 | 24 | return HashMap([]const u8, V, hashString, eqlString, true); |
lib/std/math.zig-5| ... | ... | @@ -1047,19 +1047,14 @@ pub fn order(a: var, b: var) Order { |
| 1047 | 1047 | pub const CompareOperator = enum { |
| 1048 | 1048 | /// Less than (`<`) |
| 1049 | 1049 | lt, |
| 1050 | ||
| 1051 | 1050 | /// Less than or equal (`<=`) |
| 1052 | 1051 | lte, |
| 1053 | ||
| 1054 | 1052 | /// Equal (`==`) |
| 1055 | 1053 | eq, |
| 1056 | ||
| 1057 | 1054 | /// Greater than or equal (`>=`) |
| 1058 | 1055 | gte, |
| 1059 | ||
| 1060 | 1056 | /// Greater than (`>`) |
| 1061 | 1057 | gt, |
| 1062 | ||
| 1063 | 1058 | /// Not equal (`!=`) |
| 1064 | 1059 | neq, |
| 1065 | 1060 | }; |
lib/std/special/test_runner.zig+12| ... | ... | @@ -21,6 +21,7 @@ pub fn main() anyerror!void { |
| 21 | 21 | |
| 22 | 22 | for (test_fn_list) |test_fn, i| { |
| 23 | 23 | std.testing.base_allocator_instance.reset(); |
| 24 | std.testing.log_level = .warn; | |
| 24 | 25 | |
| 25 | 26 | var test_node = root_node.start(test_fn.name, null); |
| 26 | 27 | test_node.activate(); |
| ... | ... | @@ -73,3 +74,14 @@ pub fn main() anyerror!void { |
| 73 | 74 | std.debug.warn("{} passed; {} skipped.\n", .{ ok_count, skip_count }); |
| 74 | 75 | } |
| 75 | 76 | } |
| 77 | ||
| 78 | pub fn log( | |
| 79 | comptime message_level: std.log.Level, | |
| 80 | comptime scope: @Type(.EnumLiteral), | |
| 81 | comptime format: []const u8, | |
| 82 | args: var, | |
| 83 | ) void { | |
| 84 | if (@enumToInt(message_level) <= @enumToInt(std.testing.log_level)) { | |
| 85 | std.debug.print("[{}] ({}): " ++ format, .{@tagName(scope), @tagName(message_level)} ++ args); | |
| 86 | } | |
| 87 | } |
lib/std/std.zig+5-3| ... | ... | @@ -3,14 +3,16 @@ pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned; |
| 3 | 3 | pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged; |
| 4 | 4 | pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled; |
| 5 | 5 | pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged; |
| 6 | pub const AutoHashMap = @import("hash_map.zig").AutoHashMap; | |
| 6 | pub const AutoHashMap = hash_map.AutoHashMap; | |
| 7 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; | |
| 7 | 8 | pub const BloomFilter = @import("bloom_filter.zig").BloomFilter; |
| 8 | 9 | pub const BufMap = @import("buf_map.zig").BufMap; |
| 9 | 10 | pub const BufSet = @import("buf_set.zig").BufSet; |
| 10 | 11 | pub const ChildProcess = @import("child_process.zig").ChildProcess; |
| 11 | 12 | pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringMap; |
| 12 | 13 | pub const DynLib = @import("dynamic_library.zig").DynLib; |
| 13 | pub const HashMap = @import("hash_map.zig").HashMap; | |
| 14 | pub const HashMap = hash_map.HashMap; | |
| 15 | pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; | |
| 14 | 16 | pub const Mutex = @import("mutex.zig").Mutex; |
| 15 | 17 | pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; |
| 16 | 18 | pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; |
| ... | ... | @@ -22,7 +24,7 @@ pub const ResetEvent = @import("reset_event.zig").ResetEvent; |
| 22 | 24 | pub const SegmentedList = @import("segmented_list.zig").SegmentedList; |
| 23 | 25 | pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; |
| 24 | 26 | pub const SpinLock = @import("spinlock.zig").SpinLock; |
| 25 | pub const StringHashMap = @import("hash_map.zig").StringHashMap; | |
| 27 | pub const StringHashMap = hash_map.StringHashMap; | |
| 26 | 28 | pub const TailQueue = @import("linked_list.zig").TailQueue; |
| 27 | 29 | pub const Target = @import("target.zig").Target; |
| 28 | 30 | pub const Thread = @import("thread.zig").Thread; |
lib/std/testing.zig+3| ... | ... | @@ -14,6 +14,9 @@ pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_insta |
| 14 | 14 | pub var base_allocator_instance = std.mem.validationWrap(std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..])); |
| 15 | 15 | var allocator_mem: [2 * 1024 * 1024]u8 = undefined; |
| 16 | 16 | |
| 17 | /// TODO https://github.com/ziglang/zig/issues/5738 | |
| 18 | pub var log_level = std.log.Level.warn; | |
| 19 | ||
| 17 | 20 | /// This function is intended to be used only in tests. It prints diagnostics to stderr |
| 18 | 21 | /// and then aborts when actual_error_union is not expected_error. |
| 19 | 22 | pub fn expectError(expected_error: anyerror, actual_error_union: var) void { |
lib/std/zig/ast.zig+2| ... | ... | @@ -959,6 +959,8 @@ pub const Node = struct { |
| 959 | 959 | }; |
| 960 | 960 | |
| 961 | 961 | /// The params are directly after the FnProto in memory. |
| 962 | /// TODO have a flags field for the optional nodes, and have them appended | |
| 963 | /// before or after the parameters in memory. | |
| 962 | 964 | pub const FnProto = struct { |
| 963 | 965 | base: Node = Node{ .id = .FnProto }, |
| 964 | 966 | doc_comments: ?*DocComment, |
src-self-hosted/Module.zig+388-267| ... | ... | @@ -18,9 +18,10 @@ const Inst = ir.Inst; |
| 18 | 18 | const Body = ir.Body; |
| 19 | 19 | const ast = std.zig.ast; |
| 20 | 20 | const trace = @import("tracy.zig").trace; |
| 21 | const liveness = @import("liveness.zig"); | |
| 21 | 22 | |
| 22 | /// General-purpose allocator. | |
| 23 | allocator: *Allocator, | |
| 23 | /// General-purpose allocator. Used for both temporary and long-term storage. | |
| 24 | gpa: *Allocator, | |
| 24 | 25 | /// Pointer to externally managed resource. |
| 25 | 26 | root_pkg: *Package, |
| 26 | 27 | /// Module owns this resource. |
| ... | ... | @@ -32,7 +33,7 @@ bin_file_path: []const u8, |
| 32 | 33 | /// It's rare for a decl to be exported, so we save memory by having a sparse map of |
| 33 | 34 | /// Decl pointers to details about them being exported. |
| 34 | 35 | /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. |
| 35 | decl_exports: std.AutoHashMap(*Decl, []*Export), | |
| 36 | decl_exports: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, | |
| 36 | 37 | /// We track which export is associated with the given symbol name for quick |
| 37 | 38 | /// detection of symbol collisions. |
| 38 | 39 | symbol_exports: std.StringHashMap(*Export), |
| ... | ... | @@ -40,9 +41,9 @@ symbol_exports: std.StringHashMap(*Export), |
| 40 | 41 | /// is modified. Note that the key of this table is not the Decl being exported, but the Decl that |
| 41 | 42 | /// is performing the export of another Decl. |
| 42 | 43 | /// This table owns the Export memory. |
| 43 | export_owners: std.AutoHashMap(*Decl, []*Export), | |
| 44 | export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, | |
| 44 | 45 | /// Maps fully qualified namespaced names to the Decl struct for them. |
| 45 | decl_table: DeclTable, | |
| 46 | decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{}, | |
| 46 | 47 | |
| 47 | 48 | optimize_mode: std.builtin.Mode, |
| 48 | 49 | link_error_flags: link.File.ErrorFlags = .{}, |
| ... | ... | @@ -54,13 +55,13 @@ work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), |
| 54 | 55 | /// The ErrorMsg memory is owned by the decl, using Module's allocator. |
| 55 | 56 | /// Note that a Decl can succeed but the Fn it represents can fail. In this case, |
| 56 | 57 | /// a Decl can have a failed_decls entry but have analysis status of success. |
| 57 | failed_decls: std.AutoHashMap(*Decl, *ErrorMsg), | |
| 58 | failed_decls: std.AutoHashMapUnmanaged(*Decl, *ErrorMsg) = .{}, | |
| 58 | 59 | /// Using a map here for consistency with the other fields here. |
| 59 | 60 | /// The ErrorMsg memory is owned by the `Scope`, using Module's allocator. |
| 60 | failed_files: std.AutoHashMap(*Scope, *ErrorMsg), | |
| 61 | failed_files: std.AutoHashMapUnmanaged(*Scope, *ErrorMsg) = .{}, | |
| 61 | 62 | /// Using a map here for consistency with the other fields here. |
| 62 | 63 | /// The ErrorMsg memory is owned by the `Export`, using Module's allocator. |
| 63 | failed_exports: std.AutoHashMap(*Export, *ErrorMsg), | |
| 64 | failed_exports: std.AutoHashMapUnmanaged(*Export, *ErrorMsg) = .{}, | |
| 64 | 65 | |
| 65 | 66 | /// Incrementing integer used to compare against the corresponding Decl |
| 66 | 67 | /// field to determine whether a Decl's status applies to an ongoing update, or a |
| ... | ... | @@ -75,8 +76,6 @@ deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, |
| 75 | 76 | |
| 76 | 77 | keep_source_files_loaded: bool, |
| 77 | 78 | |
| 78 | const DeclTable = std.HashMap(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false); | |
| 79 | ||
| 80 | 79 | const WorkItem = union(enum) { |
| 81 | 80 | /// Write the machine code for a Decl to the output file. |
| 82 | 81 | codegen_decl: *Decl, |
| ... | ... | @@ -175,19 +174,23 @@ pub const Decl = struct { |
| 175 | 174 | |
| 176 | 175 | /// The shallow set of other decls whose typed_value could possibly change if this Decl's |
| 177 | 176 | /// typed_value is modified. |
| 178 | dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, | |
| 177 | dependants: DepsTable = .{}, | |
| 179 | 178 | /// The shallow set of other decls whose typed_value changing indicates that this Decl's |
| 180 | 179 | /// typed_value may need to be regenerated. |
| 181 | dependencies: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, | |
| 180 | dependencies: DepsTable = .{}, | |
| 181 | ||
| 182 | /// The reason this is not `std.AutoHashMapUnmanaged` is a workaround for | |
| 183 | /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself` | |
| 184 | pub const DepsTable = std.HashMapUnmanaged(*Decl, void, std.hash_map.getAutoHashFn(*Decl), std.hash_map.getAutoEqlFn(*Decl), false); | |
| 182 | 185 | |
| 183 | pub fn destroy(self: *Decl, allocator: *Allocator) void { | |
| 184 | allocator.free(mem.spanZ(self.name)); | |
| 186 | pub fn destroy(self: *Decl, gpa: *Allocator) void { | |
| 187 | gpa.free(mem.spanZ(self.name)); | |
| 185 | 188 | if (self.typedValueManaged()) |tvm| { |
| 186 | tvm.deinit(allocator); | |
| 189 | tvm.deinit(gpa); | |
| 187 | 190 | } |
| 188 | self.dependants.deinit(allocator); | |
| 189 | self.dependencies.deinit(allocator); | |
| 190 | allocator.destroy(self); | |
| 191 | self.dependants.deinit(gpa); | |
| 192 | self.dependencies.deinit(gpa); | |
| 193 | gpa.destroy(self); | |
| 191 | 194 | } |
| 192 | 195 | |
| 193 | 196 | pub fn src(self: Decl) usize { |
| ... | ... | @@ -246,23 +249,11 @@ pub const Decl = struct { |
| 246 | 249 | } |
| 247 | 250 | |
| 248 | 251 | fn removeDependant(self: *Decl, other: *Decl) void { |
| 249 | for (self.dependants.items) |item, i| { | |
| 250 | if (item == other) { | |
| 251 | _ = self.dependants.swapRemove(i); | |
| 252 | return; | |
| 253 | } | |
| 254 | } | |
| 255 | unreachable; | |
| 252 | self.dependants.removeAssertDiscard(other); | |
| 256 | 253 | } |
| 257 | 254 | |
| 258 | 255 | fn removeDependency(self: *Decl, other: *Decl) void { |
| 259 | for (self.dependencies.items) |item, i| { | |
| 260 | if (item == other) { | |
| 261 | _ = self.dependencies.swapRemove(i); | |
| 262 | return; | |
| 263 | } | |
| 264 | } | |
| 265 | unreachable; | |
| 256 | self.dependencies.removeAssertDiscard(other); | |
| 266 | 257 | } |
| 267 | 258 | }; |
| 268 | 259 | |
| ... | ... | @@ -312,14 +303,14 @@ pub const Scope = struct { |
| 312 | 303 | switch (self.tag) { |
| 313 | 304 | .block => return self.cast(Block).?.arena, |
| 314 | 305 | .decl => return &self.cast(DeclAnalysis).?.arena.allocator, |
| 315 | .gen_zir => return &self.cast(GenZIR).?.arena.allocator, | |
| 306 | .gen_zir => return self.cast(GenZIR).?.arena, | |
| 316 | 307 | .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, |
| 317 | 308 | .file => unreachable, |
| 318 | 309 | } |
| 319 | 310 | } |
| 320 | 311 | |
| 321 | /// Asserts the scope has a parent which is a DeclAnalysis and | |
| 322 | /// returns the Decl. | |
| 312 | /// If the scope has a parent which is a `DeclAnalysis`, | |
| 313 | /// returns the `Decl`, otherwise returns `null`. | |
| 323 | 314 | pub fn decl(self: *Scope) ?*Decl { |
| 324 | 315 | return switch (self.tag) { |
| 325 | 316 | .block => self.cast(Block).?.decl, |
| ... | ... | @@ -389,10 +380,10 @@ pub const Scope = struct { |
| 389 | 380 | } |
| 390 | 381 | } |
| 391 | 382 | |
| 392 | pub fn unload(base: *Scope, allocator: *Allocator) void { | |
| 383 | pub fn unload(base: *Scope, gpa: *Allocator) void { | |
| 393 | 384 | switch (base.tag) { |
| 394 | .file => return @fieldParentPtr(File, "base", base).unload(allocator), | |
| 395 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).unload(allocator), | |
| 385 | .file => return @fieldParentPtr(File, "base", base).unload(gpa), | |
| 386 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).unload(gpa), | |
| 396 | 387 | .block => unreachable, |
| 397 | 388 | .gen_zir => unreachable, |
| 398 | 389 | .decl => unreachable, |
| ... | ... | @@ -421,17 +412,17 @@ pub const Scope = struct { |
| 421 | 412 | } |
| 422 | 413 | |
| 423 | 414 | /// Asserts the scope is a File or ZIRModule and deinitializes it, then deallocates it. |
| 424 | pub fn destroy(base: *Scope, allocator: *Allocator) void { | |
| 415 | pub fn destroy(base: *Scope, gpa: *Allocator) void { | |
| 425 | 416 | switch (base.tag) { |
| 426 | 417 | .file => { |
| 427 | 418 | const scope_file = @fieldParentPtr(File, "base", base); |
| 428 | scope_file.deinit(allocator); | |
| 429 | allocator.destroy(scope_file); | |
| 419 | scope_file.deinit(gpa); | |
| 420 | gpa.destroy(scope_file); | |
| 430 | 421 | }, |
| 431 | 422 | .zir_module => { |
| 432 | 423 | const scope_zir_module = @fieldParentPtr(ZIRModule, "base", base); |
| 433 | scope_zir_module.deinit(allocator); | |
| 434 | allocator.destroy(scope_zir_module); | |
| 424 | scope_zir_module.deinit(gpa); | |
| 425 | gpa.destroy(scope_zir_module); | |
| 435 | 426 | }, |
| 436 | 427 | .block => unreachable, |
| 437 | 428 | .gen_zir => unreachable, |
| ... | ... | @@ -482,7 +473,7 @@ pub const Scope = struct { |
| 482 | 473 | /// Direct children of the file. |
| 483 | 474 | decls: ArrayListUnmanaged(*Decl), |
| 484 | 475 | |
| 485 | pub fn unload(self: *File, allocator: *Allocator) void { | |
| 476 | pub fn unload(self: *File, gpa: *Allocator) void { | |
| 486 | 477 | switch (self.status) { |
| 487 | 478 | .never_loaded, |
| 488 | 479 | .unloaded_parse_failure, |
| ... | ... | @@ -496,16 +487,16 @@ pub const Scope = struct { |
| 496 | 487 | } |
| 497 | 488 | switch (self.source) { |
| 498 | 489 | .bytes => |bytes| { |
| 499 | allocator.free(bytes); | |
| 490 | gpa.free(bytes); | |
| 500 | 491 | self.source = .{ .unloaded = {} }; |
| 501 | 492 | }, |
| 502 | 493 | .unloaded => {}, |
| 503 | 494 | } |
| 504 | 495 | } |
| 505 | 496 | |
| 506 | pub fn deinit(self: *File, allocator: *Allocator) void { | |
| 507 | self.decls.deinit(allocator); | |
| 508 | self.unload(allocator); | |
| 497 | pub fn deinit(self: *File, gpa: *Allocator) void { | |
| 498 | self.decls.deinit(gpa); | |
| 499 | self.unload(gpa); | |
| 509 | 500 | self.* = undefined; |
| 510 | 501 | } |
| 511 | 502 | |
| ... | ... | @@ -527,7 +518,7 @@ pub const Scope = struct { |
| 527 | 518 | switch (self.source) { |
| 528 | 519 | .unloaded => { |
| 529 | 520 | const source = try module.root_pkg.root_src_dir.readFileAllocOptions( |
| 530 | module.allocator, | |
| 521 | module.gpa, | |
| 531 | 522 | self.sub_file_path, |
| 532 | 523 | std.math.maxInt(u32), |
| 533 | 524 | 1, |
| ... | ... | @@ -575,7 +566,7 @@ pub const Scope = struct { |
| 575 | 566 | /// not this one. |
| 576 | 567 | decls: ArrayListUnmanaged(*Decl), |
| 577 | 568 | |
| 578 | pub fn unload(self: *ZIRModule, allocator: *Allocator) void { | |
| 569 | pub fn unload(self: *ZIRModule, gpa: *Allocator) void { | |
| 579 | 570 | switch (self.status) { |
| 580 | 571 | .never_loaded, |
| 581 | 572 | .unloaded_parse_failure, |
| ... | ... | @@ -584,30 +575,30 @@ pub const Scope = struct { |
| 584 | 575 | => {}, |
| 585 | 576 | |
| 586 | 577 | .loaded_success => { |
| 587 | self.contents.module.deinit(allocator); | |
| 588 | allocator.destroy(self.contents.module); | |
| 578 | self.contents.module.deinit(gpa); | |
| 579 | gpa.destroy(self.contents.module); | |
| 589 | 580 | self.contents = .{ .not_available = {} }; |
| 590 | 581 | self.status = .unloaded_success; |
| 591 | 582 | }, |
| 592 | 583 | .loaded_sema_failure => { |
| 593 | self.contents.module.deinit(allocator); | |
| 594 | allocator.destroy(self.contents.module); | |
| 584 | self.contents.module.deinit(gpa); | |
| 585 | gpa.destroy(self.contents.module); | |
| 595 | 586 | self.contents = .{ .not_available = {} }; |
| 596 | 587 | self.status = .unloaded_sema_failure; |
| 597 | 588 | }, |
| 598 | 589 | } |
| 599 | 590 | switch (self.source) { |
| 600 | 591 | .bytes => |bytes| { |
| 601 | allocator.free(bytes); | |
| 592 | gpa.free(bytes); | |
| 602 | 593 | self.source = .{ .unloaded = {} }; |
| 603 | 594 | }, |
| 604 | 595 | .unloaded => {}, |
| 605 | 596 | } |
| 606 | 597 | } |
| 607 | 598 | |
| 608 | pub fn deinit(self: *ZIRModule, allocator: *Allocator) void { | |
| 609 | self.decls.deinit(allocator); | |
| 610 | self.unload(allocator); | |
| 599 | pub fn deinit(self: *ZIRModule, gpa: *Allocator) void { | |
| 600 | self.decls.deinit(gpa); | |
| 601 | self.unload(gpa); | |
| 611 | 602 | self.* = undefined; |
| 612 | 603 | } |
| 613 | 604 | |
| ... | ... | @@ -629,7 +620,7 @@ pub const Scope = struct { |
| 629 | 620 | switch (self.source) { |
| 630 | 621 | .unloaded => { |
| 631 | 622 | const source = try module.root_pkg.root_src_dir.readFileAllocOptions( |
| 632 | module.allocator, | |
| 623 | module.gpa, | |
| 633 | 624 | self.sub_file_path, |
| 634 | 625 | std.math.maxInt(u32), |
| 635 | 626 | 1, |
| ... | ... | @@ -662,7 +653,7 @@ pub const Scope = struct { |
| 662 | 653 | label: ?Label = null, |
| 663 | 654 | |
| 664 | 655 | pub const Label = struct { |
| 665 | name: []const u8, | |
| 656 | zir_block: *zir.Inst.Block, | |
| 666 | 657 | results: ArrayListUnmanaged(*Inst), |
| 667 | 658 | block_inst: *Inst.Block, |
| 668 | 659 | }; |
| ... | ... | @@ -683,8 +674,8 @@ pub const Scope = struct { |
| 683 | 674 | pub const base_tag: Tag = .gen_zir; |
| 684 | 675 | base: Scope = Scope{ .tag = base_tag }, |
| 685 | 676 | decl: *Decl, |
| 686 | arena: std.heap.ArenaAllocator, | |
| 687 | instructions: std.ArrayList(*zir.Inst), | |
| 677 | arena: *Allocator, | |
| 678 | instructions: std.ArrayListUnmanaged(*zir.Inst) = .{}, | |
| 688 | 679 | }; |
| 689 | 680 | }; |
| 690 | 681 | |
| ... | ... | @@ -700,8 +691,8 @@ pub const AllErrors = struct { |
| 700 | 691 | msg: []const u8, |
| 701 | 692 | }; |
| 702 | 693 | |
| 703 | pub fn deinit(self: *AllErrors, allocator: *Allocator) void { | |
| 704 | self.arena.promote(allocator).deinit(); | |
| 694 | pub fn deinit(self: *AllErrors, gpa: *Allocator) void { | |
| 695 | self.arena.promote(gpa).deinit(); | |
| 705 | 696 | } |
| 706 | 697 | |
| 707 | 698 | fn add( |
| ... | ... | @@ -773,20 +764,14 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 773 | 764 | }; |
| 774 | 765 | |
| 775 | 766 | return Module{ |
| 776 | .allocator = gpa, | |
| 767 | .gpa = gpa, | |
| 777 | 768 | .root_pkg = options.root_pkg, |
| 778 | 769 | .root_scope = root_scope, |
| 779 | 770 | .bin_file_dir = bin_file_dir, |
| 780 | 771 | .bin_file_path = options.bin_file_path, |
| 781 | 772 | .bin_file = bin_file, |
| 782 | 773 | .optimize_mode = options.optimize_mode, |
| 783 | .decl_table = DeclTable.init(gpa), | |
| 784 | .decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa), | |
| 785 | 774 | .symbol_exports = std.StringHashMap(*Export).init(gpa), |
| 786 | .export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa), | |
| 787 | .failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa), | |
| 788 | .failed_files = std.AutoHashMap(*Scope, *ErrorMsg).init(gpa), | |
| 789 | .failed_exports = std.AutoHashMap(*Export, *ErrorMsg).init(gpa), | |
| 790 | 775 | .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa), |
| 791 | 776 | .keep_source_files_loaded = options.keep_source_files_loaded, |
| 792 | 777 | }; |
| ... | ... | @@ -794,51 +779,51 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 794 | 779 | |
| 795 | 780 | pub fn deinit(self: *Module) void { |
| 796 | 781 | self.bin_file.destroy(); |
| 797 | const allocator = self.allocator; | |
| 798 | self.deletion_set.deinit(allocator); | |
| 782 | const gpa = self.gpa; | |
| 783 | self.deletion_set.deinit(gpa); | |
| 799 | 784 | self.work_queue.deinit(); |
| 800 | 785 | |
| 801 | 786 | for (self.decl_table.items()) |entry| { |
| 802 | entry.value.destroy(allocator); | |
| 787 | entry.value.destroy(gpa); | |
| 803 | 788 | } |
| 804 | self.decl_table.deinit(); | |
| 789 | self.decl_table.deinit(gpa); | |
| 805 | 790 | |
| 806 | 791 | for (self.failed_decls.items()) |entry| { |
| 807 | entry.value.destroy(allocator); | |
| 792 | entry.value.destroy(gpa); | |
| 808 | 793 | } |
| 809 | self.failed_decls.deinit(); | |
| 794 | self.failed_decls.deinit(gpa); | |
| 810 | 795 | |
| 811 | 796 | for (self.failed_files.items()) |entry| { |
| 812 | entry.value.destroy(allocator); | |
| 797 | entry.value.destroy(gpa); | |
| 813 | 798 | } |
| 814 | self.failed_files.deinit(); | |
| 799 | self.failed_files.deinit(gpa); | |
| 815 | 800 | |
| 816 | 801 | for (self.failed_exports.items()) |entry| { |
| 817 | entry.value.destroy(allocator); | |
| 802 | entry.value.destroy(gpa); | |
| 818 | 803 | } |
| 819 | self.failed_exports.deinit(); | |
| 804 | self.failed_exports.deinit(gpa); | |
| 820 | 805 | |
| 821 | 806 | for (self.decl_exports.items()) |entry| { |
| 822 | 807 | const export_list = entry.value; |
| 823 | allocator.free(export_list); | |
| 808 | gpa.free(export_list); | |
| 824 | 809 | } |
| 825 | self.decl_exports.deinit(); | |
| 810 | self.decl_exports.deinit(gpa); | |
| 826 | 811 | |
| 827 | 812 | for (self.export_owners.items()) |entry| { |
| 828 | freeExportList(allocator, entry.value); | |
| 813 | freeExportList(gpa, entry.value); | |
| 829 | 814 | } |
| 830 | self.export_owners.deinit(); | |
| 815 | self.export_owners.deinit(gpa); | |
| 831 | 816 | |
| 832 | 817 | self.symbol_exports.deinit(); |
| 833 | self.root_scope.destroy(allocator); | |
| 818 | self.root_scope.destroy(gpa); | |
| 834 | 819 | self.* = undefined; |
| 835 | 820 | } |
| 836 | 821 | |
| 837 | fn freeExportList(allocator: *Allocator, export_list: []*Export) void { | |
| 822 | fn freeExportList(gpa: *Allocator, export_list: []*Export) void { | |
| 838 | 823 | for (export_list) |exp| { |
| 839 | allocator.destroy(exp); | |
| 824 | gpa.destroy(exp); | |
| 840 | 825 | } |
| 841 | allocator.free(export_list); | |
| 826 | gpa.free(export_list); | |
| 842 | 827 | } |
| 843 | 828 | |
| 844 | 829 | pub fn target(self: Module) std.Target { |
| ... | ... | @@ -856,7 +841,7 @@ pub fn update(self: *Module) !void { |
| 856 | 841 | // Until then we simulate a full cache miss. Source files could have been loaded for any reason; |
| 857 | 842 | // to force a refresh we unload now. |
| 858 | 843 | if (self.root_scope.cast(Scope.File)) |zig_file| { |
| 859 | zig_file.unload(self.allocator); | |
| 844 | zig_file.unload(self.gpa); | |
| 860 | 845 | self.analyzeRootSrcFile(zig_file) catch |err| switch (err) { |
| 861 | 846 | error.AnalysisFail => { |
| 862 | 847 | assert(self.totalErrorCount() != 0); |
| ... | ... | @@ -864,7 +849,7 @@ pub fn update(self: *Module) !void { |
| 864 | 849 | else => |e| return e, |
| 865 | 850 | }; |
| 866 | 851 | } else if (self.root_scope.cast(Scope.ZIRModule)) |zir_module| { |
| 867 | zir_module.unload(self.allocator); | |
| 852 | zir_module.unload(self.gpa); | |
| 868 | 853 | self.analyzeRootZIRModule(zir_module) catch |err| switch (err) { |
| 869 | 854 | error.AnalysisFail => { |
| 870 | 855 | assert(self.totalErrorCount() != 0); |
| ... | ... | @@ -877,22 +862,25 @@ pub fn update(self: *Module) !void { |
| 877 | 862 | |
| 878 | 863 | // Process the deletion set. |
| 879 | 864 | while (self.deletion_set.popOrNull()) |decl| { |
| 880 | if (decl.dependants.items.len != 0) { | |
| 865 | if (decl.dependants.items().len != 0) { | |
| 881 | 866 | decl.deletion_flag = false; |
| 882 | 867 | continue; |
| 883 | 868 | } |
| 884 | 869 | try self.deleteDecl(decl); |
| 885 | 870 | } |
| 886 | 871 | |
| 872 | if (self.totalErrorCount() == 0) { | |
| 873 | // This is needed before reading the error flags. | |
| 874 | try self.bin_file.flush(); | |
| 875 | } | |
| 876 | ||
| 887 | 877 | self.link_error_flags = self.bin_file.errorFlags(); |
| 878 | std.log.debug(.module, "link_error_flags: {}\n", .{self.link_error_flags}); | |
| 888 | 879 | |
| 889 | 880 | // If there are any errors, we anticipate the source files being loaded |
| 890 | 881 | // to report error messages. Otherwise we unload all source files to save memory. |
| 891 | if (self.totalErrorCount() == 0) { | |
| 892 | if (!self.keep_source_files_loaded) { | |
| 893 | self.root_scope.unload(self.allocator); | |
| 894 | } | |
| 895 | try self.bin_file.flush(); | |
| 882 | if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { | |
| 883 | self.root_scope.unload(self.gpa); | |
| 896 | 884 | } |
| 897 | 885 | } |
| 898 | 886 | |
| ... | ... | @@ -916,10 +904,10 @@ pub fn totalErrorCount(self: *Module) usize { |
| 916 | 904 | } |
| 917 | 905 | |
| 918 | 906 | pub fn getAllErrorsAlloc(self: *Module) !AllErrors { |
| 919 | var arena = std.heap.ArenaAllocator.init(self.allocator); | |
| 907 | var arena = std.heap.ArenaAllocator.init(self.gpa); | |
| 920 | 908 | errdefer arena.deinit(); |
| 921 | 909 | |
| 922 | var errors = std.ArrayList(AllErrors.Message).init(self.allocator); | |
| 910 | var errors = std.ArrayList(AllErrors.Message).init(self.gpa); | |
| 923 | 911 | defer errors.deinit(); |
| 924 | 912 | |
| 925 | 913 | for (self.failed_files.items()) |entry| { |
| ... | ... | @@ -988,6 +976,12 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 988 | 976 | .sema_failure, .dependency_failure => continue, |
| 989 | 977 | .success => {}, |
| 990 | 978 | } |
| 979 | // Here we tack on additional allocations to the Decl's arena. The allocations are | |
| 980 | // lifetime annotations in the ZIR. | |
| 981 | var decl_arena = decl.typed_value.most_recent.arena.?.promote(self.gpa); | |
| 982 | defer decl.typed_value.most_recent.arena.?.* = decl_arena.state; | |
| 983 | std.log.debug(.module, "analyze liveness of {}\n", .{decl.name}); | |
| 984 | try liveness.analyze(self.gpa, &decl_arena.allocator, payload.func.analysis.success); | |
| 991 | 985 | } |
| 992 | 986 | |
| 993 | 987 | assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); |
| ... | ... | @@ -998,9 +992,9 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 998 | 992 | decl.analysis = .dependency_failure; |
| 999 | 993 | }, |
| 1000 | 994 | else => { |
| 1001 | try self.failed_decls.ensureCapacity(self.failed_decls.items().len + 1); | |
| 995 | try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); | |
| 1002 | 996 | self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( |
| 1003 | self.allocator, | |
| 997 | self.gpa, | |
| 1004 | 998 | decl.src(), |
| 1005 | 999 | "unable to codegen: {}", |
| 1006 | 1000 | .{@errorName(err)}, |
| ... | ... | @@ -1044,16 +1038,17 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { |
| 1044 | 1038 | // prior to re-analysis. |
| 1045 | 1039 | self.deleteDeclExports(decl); |
| 1046 | 1040 | // Dependencies will be re-discovered, so we remove them here prior to re-analysis. |
| 1047 | for (decl.dependencies.items) |dep| { | |
| 1041 | for (decl.dependencies.items()) |entry| { | |
| 1042 | const dep = entry.key; | |
| 1048 | 1043 | dep.removeDependant(decl); |
| 1049 | if (dep.dependants.items.len == 0 and !dep.deletion_flag) { | |
| 1044 | if (dep.dependants.items().len == 0 and !dep.deletion_flag) { | |
| 1050 | 1045 | // We don't perform a deletion here, because this Decl or another one |
| 1051 | 1046 | // may end up referencing it before the update is complete. |
| 1052 | 1047 | dep.deletion_flag = true; |
| 1053 | try self.deletion_set.append(self.allocator, dep); | |
| 1048 | try self.deletion_set.append(self.gpa, dep); | |
| 1054 | 1049 | } |
| 1055 | 1050 | } |
| 1056 | decl.dependencies.shrink(self.allocator, 0); | |
| 1051 | decl.dependencies.clearRetainingCapacity(); | |
| 1057 | 1052 | |
| 1058 | 1053 | break :blk true; |
| 1059 | 1054 | }, |
| ... | ... | @@ -1068,9 +1063,9 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { |
| 1068 | 1063 | error.OutOfMemory => return error.OutOfMemory, |
| 1069 | 1064 | error.AnalysisFail => return error.AnalysisFail, |
| 1070 | 1065 | else => { |
| 1071 | try self.failed_decls.ensureCapacity(self.failed_decls.items().len + 1); | |
| 1066 | try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); | |
| 1072 | 1067 | self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( |
| 1073 | self.allocator, | |
| 1068 | self.gpa, | |
| 1074 | 1069 | decl.src(), |
| 1075 | 1070 | "unable to analyze: {}", |
| 1076 | 1071 | .{@errorName(err)}, |
| ... | ... | @@ -1084,7 +1079,8 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { |
| 1084 | 1079 | // We may need to chase the dependants and re-analyze them. |
| 1085 | 1080 | // However, if the decl is a function, and the type is the same, we do not need to. |
| 1086 | 1081 | if (type_changed or decl.typed_value.most_recent.typed_value.val.tag() != .function) { |
| 1087 | for (decl.dependants.items) |dep| { | |
| 1082 | for (decl.dependants.items()) |entry| { | |
| 1083 | const dep = entry.key; | |
| 1088 | 1084 | switch (dep.analysis) { |
| 1089 | 1085 | .unreferenced => unreachable, |
| 1090 | 1086 | .in_progress => unreachable, |
| ... | ... | @@ -1121,19 +1117,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1121 | 1117 | // This arena allocator's memory is discarded at the end of this function. It is used |
| 1122 | 1118 | // to determine the type of the function, and hence the type of the decl, which is needed |
| 1123 | 1119 | // to complete the Decl analysis. |
| 1120 | var fn_type_scope_arena = std.heap.ArenaAllocator.init(self.gpa); | |
| 1121 | defer fn_type_scope_arena.deinit(); | |
| 1124 | 1122 | var fn_type_scope: Scope.GenZIR = .{ |
| 1125 | 1123 | .decl = decl, |
| 1126 | .arena = std.heap.ArenaAllocator.init(self.allocator), | |
| 1127 | .instructions = std.ArrayList(*zir.Inst).init(self.allocator), | |
| 1124 | .arena = &fn_type_scope_arena.allocator, | |
| 1128 | 1125 | }; |
| 1129 | defer fn_type_scope.arena.deinit(); | |
| 1130 | defer fn_type_scope.instructions.deinit(); | |
| 1126 | defer fn_type_scope.instructions.deinit(self.gpa); | |
| 1131 | 1127 | |
| 1132 | 1128 | const body_node = fn_proto.body_node orelse |
| 1133 | 1129 | return self.failTok(&fn_type_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{}); |
| 1134 | 1130 | |
| 1135 | 1131 | const param_decls = fn_proto.params(); |
| 1136 | const param_types = try fn_type_scope.arena.allocator.alloc(*zir.Inst, param_decls.len); | |
| 1132 | const param_types = try fn_type_scope.arena.alloc(*zir.Inst, param_decls.len); | |
| 1137 | 1133 | for (param_decls) |param_decl, i| { |
| 1138 | 1134 | const param_type_node = switch (param_decl.param_type) { |
| 1139 | 1135 | .var_type => |node| return self.failNode(&fn_type_scope.base, node, "TODO implement anytype parameter", .{}), |
| ... | ... | @@ -1174,7 +1170,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1174 | 1170 | _ = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.Return, .{ .operand = fn_type_inst }, .{}); |
| 1175 | 1171 | |
| 1176 | 1172 | // We need the memory for the Type to go into the arena for the Decl |
| 1177 | var decl_arena = std.heap.ArenaAllocator.init(self.allocator); | |
| 1173 | var decl_arena = std.heap.ArenaAllocator.init(self.gpa); | |
| 1178 | 1174 | errdefer decl_arena.deinit(); |
| 1179 | 1175 | const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); |
| 1180 | 1176 | |
| ... | ... | @@ -1185,7 +1181,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1185 | 1181 | .instructions = .{}, |
| 1186 | 1182 | .arena = &decl_arena.allocator, |
| 1187 | 1183 | }; |
| 1188 | defer block_scope.instructions.deinit(self.allocator); | |
| 1184 | defer block_scope.instructions.deinit(self.gpa); | |
| 1189 | 1185 | |
| 1190 | 1186 | const fn_type = try self.analyzeBodyValueAsType(&block_scope, .{ |
| 1191 | 1187 | .instructions = fn_type_scope.instructions.items, |
| ... | ... | @@ -1196,24 +1192,24 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1196 | 1192 | const fn_zir = blk: { |
| 1197 | 1193 | // This scope's arena memory is discarded after the ZIR generation |
| 1198 | 1194 | // pass completes, and semantic analysis of it completes. |
| 1195 | var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa); | |
| 1196 | errdefer gen_scope_arena.deinit(); | |
| 1199 | 1197 | var gen_scope: Scope.GenZIR = .{ |
| 1200 | 1198 | .decl = decl, |
| 1201 | .arena = std.heap.ArenaAllocator.init(self.allocator), | |
| 1202 | .instructions = std.ArrayList(*zir.Inst).init(self.allocator), | |
| 1199 | .arena = &gen_scope_arena.allocator, | |
| 1203 | 1200 | }; |
| 1204 | errdefer gen_scope.arena.deinit(); | |
| 1205 | defer gen_scope.instructions.deinit(); | |
| 1201 | defer gen_scope.instructions.deinit(self.gpa); | |
| 1206 | 1202 | |
| 1207 | 1203 | const body_block = body_node.cast(ast.Node.Block).?; |
| 1208 | 1204 | |
| 1209 | 1205 | try self.astGenBlock(&gen_scope.base, body_block); |
| 1210 | 1206 | |
| 1211 | const fn_zir = try gen_scope.arena.allocator.create(Fn.ZIR); | |
| 1207 | const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); | |
| 1212 | 1208 | fn_zir.* = .{ |
| 1213 | 1209 | .body = .{ |
| 1214 | .instructions = try gen_scope.arena.allocator.dupe(*zir.Inst, gen_scope.instructions.items), | |
| 1210 | .instructions = try gen_scope.arena.dupe(*zir.Inst, gen_scope.instructions.items), | |
| 1215 | 1211 | }, |
| 1216 | .arena = gen_scope.arena.state, | |
| 1212 | .arena = gen_scope_arena.state, | |
| 1217 | 1213 | }; |
| 1218 | 1214 | break :blk fn_zir; |
| 1219 | 1215 | }; |
| ... | ... | @@ -1231,7 +1227,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1231 | 1227 | prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); |
| 1232 | 1228 | type_changed = !tvm.typed_value.ty.eql(fn_type); |
| 1233 | 1229 | |
| 1234 | tvm.deinit(self.allocator); | |
| 1230 | tvm.deinit(self.gpa); | |
| 1235 | 1231 | } |
| 1236 | 1232 | |
| 1237 | 1233 | decl_arena_state.* = decl_arena.state; |
| ... | ... | @@ -1315,6 +1311,33 @@ fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) In |
| 1315 | 1311 | |
| 1316 | 1312 | return self.addZIRInst(scope, src, zir.Inst.Add, .{ .lhs = lhs, .rhs = rhs }, .{}); |
| 1317 | 1313 | }, |
| 1314 | .BangEqual, | |
| 1315 | .EqualEqual, | |
| 1316 | .GreaterThan, | |
| 1317 | .GreaterOrEqual, | |
| 1318 | .LessThan, | |
| 1319 | .LessOrEqual, | |
| 1320 | => { | |
| 1321 | const lhs = try self.astGenExpr(scope, infix_node.lhs); | |
| 1322 | const rhs = try self.astGenExpr(scope, infix_node.rhs); | |
| 1323 | ||
| 1324 | const tree = scope.tree(); | |
| 1325 | const src = tree.token_locs[infix_node.op_token].start; | |
| 1326 | ||
| 1327 | return self.addZIRInst(scope, src, zir.Inst.Cmp, .{ | |
| 1328 | .lhs = lhs, | |
| 1329 | .op = @as(std.math.CompareOperator, switch (infix_node.op) { | |
| 1330 | .BangEqual => .neq, | |
| 1331 | .EqualEqual => .eq, | |
| 1332 | .GreaterThan => .gt, | |
| 1333 | .GreaterOrEqual => .gte, | |
| 1334 | .LessThan => .lt, | |
| 1335 | .LessOrEqual => .lte, | |
| 1336 | else => unreachable, | |
| 1337 | }), | |
| 1338 | .rhs = rhs, | |
| 1339 | }, .{}); | |
| 1340 | }, | |
| 1318 | 1341 | else => |op| { |
| 1319 | 1342 | return self.failNode(scope, &infix_node.base, "TODO implement infix operator {}", .{op}); |
| 1320 | 1343 | }, |
| ... | ... | @@ -1330,9 +1353,70 @@ fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir |
| 1330 | 1353 | return self.failNode(scope, payload, "TODO implement astGenIf for error unions", .{}); |
| 1331 | 1354 | } |
| 1332 | 1355 | } |
| 1333 | const cond = try self.astGenExpr(scope, if_node.condition); | |
| 1334 | const body = try self.astGenExpr(scope, if_node.condition); | |
| 1335 | return self.failNode(scope, if_node.condition, "TODO implement astGenIf", .{}); | |
| 1356 | var block_scope: Scope.GenZIR = .{ | |
| 1357 | .decl = scope.decl().?, | |
| 1358 | .arena = scope.arena(), | |
| 1359 | .instructions = .{}, | |
| 1360 | }; | |
| 1361 | defer block_scope.instructions.deinit(self.gpa); | |
| 1362 | ||
| 1363 | const cond = try self.astGenExpr(&block_scope.base, if_node.condition); | |
| 1364 | ||
| 1365 | const tree = scope.tree(); | |
| 1366 | const if_src = tree.token_locs[if_node.if_token].start; | |
| 1367 | const condbr = try self.addZIRInstSpecial(&block_scope.base, if_src, zir.Inst.CondBr, .{ | |
| 1368 | .condition = cond, | |
| 1369 | .true_body = undefined, // populated below | |
| 1370 | .false_body = undefined, // populated below | |
| 1371 | }, .{}); | |
| 1372 | ||
| 1373 | const block = try self.addZIRInstBlock(scope, if_src, .{ | |
| 1374 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), | |
| 1375 | }); | |
| 1376 | var then_scope: Scope.GenZIR = .{ | |
| 1377 | .decl = block_scope.decl, | |
| 1378 | .arena = block_scope.arena, | |
| 1379 | .instructions = .{}, | |
| 1380 | }; | |
| 1381 | defer then_scope.instructions.deinit(self.gpa); | |
| 1382 | ||
| 1383 | const then_result = try self.astGenExpr(&then_scope.base, if_node.body); | |
| 1384 | const then_src = tree.token_locs[if_node.body.lastToken()].start; | |
| 1385 | _ = try self.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ | |
| 1386 | .block = block, | |
| 1387 | .operand = then_result, | |
| 1388 | }, .{}); | |
| 1389 | condbr.positionals.true_body = .{ | |
| 1390 | .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items), | |
| 1391 | }; | |
| 1392 | ||
| 1393 | var else_scope: Scope.GenZIR = .{ | |
| 1394 | .decl = block_scope.decl, | |
| 1395 | .arena = block_scope.arena, | |
| 1396 | .instructions = .{}, | |
| 1397 | }; | |
| 1398 | defer else_scope.instructions.deinit(self.gpa); | |
| 1399 | ||
| 1400 | if (if_node.@"else") |else_node| { | |
| 1401 | const else_result = try self.astGenExpr(&else_scope.base, else_node.body); | |
| 1402 | const else_src = tree.token_locs[else_node.body.lastToken()].start; | |
| 1403 | _ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ | |
| 1404 | .block = block, | |
| 1405 | .operand = else_result, | |
| 1406 | }, .{}); | |
| 1407 | } else { | |
| 1408 | // TODO Optimization opportunity: we can avoid an allocation and a memcpy here | |
| 1409 | // by directly allocating the body for this one instruction. | |
| 1410 | const else_src = tree.token_locs[if_node.lastToken()].start; | |
| 1411 | _ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.BreakVoid, .{ | |
| 1412 | .block = block, | |
| 1413 | }, .{}); | |
| 1414 | } | |
| 1415 | condbr.positionals.false_body = .{ | |
| 1416 | .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), | |
| 1417 | }; | |
| 1418 | ||
| 1419 | return &block.base; | |
| 1336 | 1420 | } |
| 1337 | 1421 | |
| 1338 | 1422 | fn astGenControlFlowExpression( |
| ... | ... | @@ -1358,12 +1442,12 @@ fn astGenControlFlowExpression( |
| 1358 | 1442 | fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerError!*zir.Inst { |
| 1359 | 1443 | const tree = scope.tree(); |
| 1360 | 1444 | const ident_name = tree.tokenSlice(ident.token); |
| 1445 | const src = tree.token_locs[ident.token].start; | |
| 1361 | 1446 | if (mem.eql(u8, ident_name, "_")) { |
| 1362 | 1447 | return self.failNode(scope, &ident.base, "TODO implement '_' identifier", .{}); |
| 1363 | 1448 | } |
| 1364 | 1449 | |
| 1365 | 1450 | if (getSimplePrimitiveValue(ident_name)) |typed_value| { |
| 1366 | const src = tree.token_locs[ident.token].start; | |
| 1367 | 1451 | return self.addZIRInstConst(scope, src, typed_value); |
| 1368 | 1452 | } |
| 1369 | 1453 | |
| ... | ... | @@ -1387,7 +1471,6 @@ fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerE |
| 1387 | 1471 | 64 => if (is_signed) Value.initTag(.i64_type) else Value.initTag(.u64_type), |
| 1388 | 1472 | else => return self.failNode(scope, &ident.base, "TODO implement arbitrary integer bitwidth types", .{}), |
| 1389 | 1473 | }; |
| 1390 | const src = tree.token_locs[ident.token].start; | |
| 1391 | 1474 | return self.addZIRInstConst(scope, src, .{ |
| 1392 | 1475 | .ty = Type.initTag(.type), |
| 1393 | 1476 | .val = val, |
| ... | ... | @@ -1396,10 +1479,21 @@ fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerE |
| 1396 | 1479 | } |
| 1397 | 1480 | |
| 1398 | 1481 | if (self.lookupDeclName(scope, ident_name)) |decl| { |
| 1399 | const src = tree.token_locs[ident.token].start; | |
| 1400 | 1482 | return try self.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); |
| 1401 | 1483 | } |
| 1402 | 1484 | |
| 1485 | // Function parameter | |
| 1486 | if (scope.decl()) |decl| { | |
| 1487 | if (tree.root_node.decls()[decl.src_index].cast(ast.Node.FnProto)) |fn_proto| { | |
| 1488 | for (fn_proto.params()) |param, i| { | |
| 1489 | const param_name = tree.tokenSlice(param.name_token.?); | |
| 1490 | if (mem.eql(u8, param_name, ident_name)) { | |
| 1491 | return try self.addZIRInst(scope, src, zir.Inst.Arg, .{ .index = i }, .{}); | |
| 1492 | } | |
| 1493 | } | |
| 1494 | } | |
| 1495 | } | |
| 1496 | ||
| 1403 | 1497 | return self.failNode(scope, &ident.base, "TODO implement local variable identifier lookup", .{}); |
| 1404 | 1498 | } |
| 1405 | 1499 | |
| ... | ... | @@ -1542,7 +1636,7 @@ fn astGenCall(self: *Module, scope: *Scope, call: *ast.Node.Call) InnerError!*zi |
| 1542 | 1636 | const lhs = try self.astGenExpr(scope, call.lhs); |
| 1543 | 1637 | |
| 1544 | 1638 | const param_nodes = call.params(); |
| 1545 | const args = try scope.cast(Scope.GenZIR).?.arena.allocator.alloc(*zir.Inst, param_nodes.len); | |
| 1639 | const args = try scope.cast(Scope.GenZIR).?.arena.alloc(*zir.Inst, param_nodes.len); | |
| 1546 | 1640 | for (param_nodes) |param_node, i| { |
| 1547 | 1641 | args[i] = try self.astGenExpr(scope, param_node); |
| 1548 | 1642 | } |
| ... | ... | @@ -1622,40 +1716,31 @@ fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { |
| 1622 | 1716 | } |
| 1623 | 1717 | |
| 1624 | 1718 | fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void { |
| 1625 | try depender.dependencies.ensureCapacity(self.allocator, depender.dependencies.items.len + 1); | |
| 1626 | try dependee.dependants.ensureCapacity(self.allocator, dependee.dependants.items.len + 1); | |
| 1719 | try depender.dependencies.ensureCapacity(self.gpa, depender.dependencies.items().len + 1); | |
| 1720 | try dependee.dependants.ensureCapacity(self.gpa, dependee.dependants.items().len + 1); | |
| 1627 | 1721 | |
| 1628 | for (depender.dependencies.items) |item| { | |
| 1629 | if (item == dependee) break; // Already in the set. | |
| 1630 | } else { | |
| 1631 | depender.dependencies.appendAssumeCapacity(dependee); | |
| 1632 | } | |
| 1633 | ||
| 1634 | for (dependee.dependants.items) |item| { | |
| 1635 | if (item == depender) break; // Already in the set. | |
| 1636 | } else { | |
| 1637 | dependee.dependants.appendAssumeCapacity(depender); | |
| 1638 | } | |
| 1722 | depender.dependencies.putAssumeCapacity(dependee, {}); | |
| 1723 | dependee.dependants.putAssumeCapacity(depender, {}); | |
| 1639 | 1724 | } |
| 1640 | 1725 | |
| 1641 | 1726 | fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { |
| 1642 | 1727 | switch (root_scope.status) { |
| 1643 | 1728 | .never_loaded, .unloaded_success => { |
| 1644 | try self.failed_files.ensureCapacity(self.failed_files.items().len + 1); | |
| 1729 | try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); | |
| 1645 | 1730 | |
| 1646 | 1731 | const source = try root_scope.getSource(self); |
| 1647 | 1732 | |
| 1648 | 1733 | var keep_zir_module = false; |
| 1649 | const zir_module = try self.allocator.create(zir.Module); | |
| 1650 | defer if (!keep_zir_module) self.allocator.destroy(zir_module); | |
| 1734 | const zir_module = try self.gpa.create(zir.Module); | |
| 1735 | defer if (!keep_zir_module) self.gpa.destroy(zir_module); | |
| 1651 | 1736 | |
| 1652 | zir_module.* = try zir.parse(self.allocator, source); | |
| 1653 | defer if (!keep_zir_module) zir_module.deinit(self.allocator); | |
| 1737 | zir_module.* = try zir.parse(self.gpa, source); | |
| 1738 | defer if (!keep_zir_module) zir_module.deinit(self.gpa); | |
| 1654 | 1739 | |
| 1655 | 1740 | if (zir_module.error_msg) |src_err_msg| { |
| 1656 | 1741 | self.failed_files.putAssumeCapacityNoClobber( |
| 1657 | 1742 | &root_scope.base, |
| 1658 | try ErrorMsg.create(self.allocator, src_err_msg.byte_offset, "{}", .{src_err_msg.msg}), | |
| 1743 | try ErrorMsg.create(self.gpa, src_err_msg.byte_offset, "{}", .{src_err_msg.msg}), | |
| 1659 | 1744 | ); |
| 1660 | 1745 | root_scope.status = .unloaded_parse_failure; |
| 1661 | 1746 | return error.AnalysisFail; |
| ... | ... | @@ -1682,22 +1767,22 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { |
| 1682 | 1767 | |
| 1683 | 1768 | switch (root_scope.status) { |
| 1684 | 1769 | .never_loaded, .unloaded_success => { |
| 1685 | try self.failed_files.ensureCapacity(self.failed_files.items().len + 1); | |
| 1770 | try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); | |
| 1686 | 1771 | |
| 1687 | 1772 | const source = try root_scope.getSource(self); |
| 1688 | 1773 | |
| 1689 | 1774 | var keep_tree = false; |
| 1690 | const tree = try std.zig.parse(self.allocator, source); | |
| 1775 | const tree = try std.zig.parse(self.gpa, source); | |
| 1691 | 1776 | defer if (!keep_tree) tree.deinit(); |
| 1692 | 1777 | |
| 1693 | 1778 | if (tree.errors.len != 0) { |
| 1694 | 1779 | const parse_err = tree.errors[0]; |
| 1695 | 1780 | |
| 1696 | var msg = std.ArrayList(u8).init(self.allocator); | |
| 1781 | var msg = std.ArrayList(u8).init(self.gpa); | |
| 1697 | 1782 | defer msg.deinit(); |
| 1698 | 1783 | |
| 1699 | 1784 | try parse_err.render(tree.token_ids, msg.outStream()); |
| 1700 | const err_msg = try self.allocator.create(ErrorMsg); | |
| 1785 | const err_msg = try self.gpa.create(ErrorMsg); | |
| 1701 | 1786 | err_msg.* = .{ |
| 1702 | 1787 | .msg = msg.toOwnedSlice(), |
| 1703 | 1788 | .byte_offset = tree.token_locs[parse_err.loc()].start, |
| ... | ... | @@ -1728,11 +1813,11 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1728 | 1813 | const decls = tree.root_node.decls(); |
| 1729 | 1814 | |
| 1730 | 1815 | try self.work_queue.ensureUnusedCapacity(decls.len); |
| 1731 | try root_scope.decls.ensureCapacity(self.allocator, decls.len); | |
| 1816 | try root_scope.decls.ensureCapacity(self.gpa, decls.len); | |
| 1732 | 1817 | |
| 1733 | 1818 | // Keep track of the decls that we expect to see in this file so that |
| 1734 | 1819 | // we know which ones have been deleted. |
| 1735 | var deleted_decls = std.AutoHashMap(*Decl, void).init(self.allocator); | |
| 1820 | var deleted_decls = std.AutoHashMap(*Decl, void).init(self.gpa); | |
| 1736 | 1821 | defer deleted_decls.deinit(); |
| 1737 | 1822 | try deleted_decls.ensureCapacity(root_scope.decls.items.len); |
| 1738 | 1823 | for (root_scope.decls.items) |file_decl| { |
| ... | ... | @@ -1756,9 +1841,9 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1756 | 1841 | decl.src_index = decl_i; |
| 1757 | 1842 | if (deleted_decls.remove(decl) == null) { |
| 1758 | 1843 | decl.analysis = .sema_failure; |
| 1759 | const err_msg = try ErrorMsg.create(self.allocator, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name}); | |
| 1760 | errdefer err_msg.destroy(self.allocator); | |
| 1761 | try self.failed_decls.putNoClobber(decl, err_msg); | |
| 1844 | const err_msg = try ErrorMsg.create(self.gpa, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name}); | |
| 1845 | errdefer err_msg.destroy(self.gpa); | |
| 1846 | try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); | |
| 1762 | 1847 | } else { |
| 1763 | 1848 | if (!srcHashEql(decl.contents_hash, contents_hash)) { |
| 1764 | 1849 | try self.markOutdatedDecl(decl); |
| ... | ... | @@ -1792,14 +1877,14 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 1792 | 1877 | const src_module = try self.getSrcModule(root_scope); |
| 1793 | 1878 | |
| 1794 | 1879 | try self.work_queue.ensureUnusedCapacity(src_module.decls.len); |
| 1795 | try root_scope.decls.ensureCapacity(self.allocator, src_module.decls.len); | |
| 1880 | try root_scope.decls.ensureCapacity(self.gpa, src_module.decls.len); | |
| 1796 | 1881 | |
| 1797 | var exports_to_resolve = std.ArrayList(*zir.Decl).init(self.allocator); | |
| 1882 | var exports_to_resolve = std.ArrayList(*zir.Decl).init(self.gpa); | |
| 1798 | 1883 | defer exports_to_resolve.deinit(); |
| 1799 | 1884 | |
| 1800 | 1885 | // Keep track of the decls that we expect to see in this file so that |
| 1801 | 1886 | // we know which ones have been deleted. |
| 1802 | var deleted_decls = std.AutoHashMap(*Decl, void).init(self.allocator); | |
| 1887 | var deleted_decls = std.AutoHashMap(*Decl, void).init(self.gpa); | |
| 1803 | 1888 | defer deleted_decls.deinit(); |
| 1804 | 1889 | try deleted_decls.ensureCapacity(self.decl_table.items().len); |
| 1805 | 1890 | for (self.decl_table.items()) |entry| { |
| ... | ... | @@ -1841,7 +1926,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 1841 | 1926 | } |
| 1842 | 1927 | |
| 1843 | 1928 | fn deleteDecl(self: *Module, decl: *Decl) !void { |
| 1844 | try self.deletion_set.ensureCapacity(self.allocator, self.deletion_set.items.len + decl.dependencies.items.len); | |
| 1929 | try self.deletion_set.ensureCapacity(self.gpa, self.deletion_set.items.len + decl.dependencies.items().len); | |
| 1845 | 1930 | |
| 1846 | 1931 | // Remove from the namespace it resides in. In the case of an anonymous Decl it will |
| 1847 | 1932 | // not be present in the set, and this does nothing. |
| ... | ... | @@ -1851,9 +1936,10 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { |
| 1851 | 1936 | const name_hash = decl.fullyQualifiedNameHash(); |
| 1852 | 1937 | self.decl_table.removeAssertDiscard(name_hash); |
| 1853 | 1938 | // Remove itself from its dependencies, because we are about to destroy the decl pointer. |
| 1854 | for (decl.dependencies.items) |dep| { | |
| 1939 | for (decl.dependencies.items()) |entry| { | |
| 1940 | const dep = entry.key; | |
| 1855 | 1941 | dep.removeDependant(decl); |
| 1856 | if (dep.dependants.items.len == 0 and !dep.deletion_flag) { | |
| 1942 | if (dep.dependants.items().len == 0 and !dep.deletion_flag) { | |
| 1857 | 1943 | // We don't recursively perform a deletion here, because during the update, |
| 1858 | 1944 | // another reference to it may turn up. |
| 1859 | 1945 | dep.deletion_flag = true; |
| ... | ... | @@ -1861,7 +1947,8 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { |
| 1861 | 1947 | } |
| 1862 | 1948 | } |
| 1863 | 1949 | // Anything that depends on this deleted decl certainly needs to be re-analyzed. |
| 1864 | for (decl.dependants.items) |dep| { | |
| 1950 | for (decl.dependants.items()) |entry| { | |
| 1951 | const dep = entry.key; | |
| 1865 | 1952 | dep.removeDependency(decl); |
| 1866 | 1953 | if (dep.analysis != .outdated) { |
| 1867 | 1954 | // TODO Move this failure possibility to the top of the function. |
| ... | ... | @@ -1869,11 +1956,11 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { |
| 1869 | 1956 | } |
| 1870 | 1957 | } |
| 1871 | 1958 | if (self.failed_decls.remove(decl)) |entry| { |
| 1872 | entry.value.destroy(self.allocator); | |
| 1959 | entry.value.destroy(self.gpa); | |
| 1873 | 1960 | } |
| 1874 | 1961 | self.deleteDeclExports(decl); |
| 1875 | 1962 | self.bin_file.freeDecl(decl); |
| 1876 | decl.destroy(self.allocator); | |
| 1963 | decl.destroy(self.gpa); | |
| 1877 | 1964 | } |
| 1878 | 1965 | |
| 1879 | 1966 | /// Delete all the Export objects that are caused by this Decl. Re-analysis of |
| ... | ... | @@ -1895,7 +1982,7 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { |
| 1895 | 1982 | i += 1; |
| 1896 | 1983 | } |
| 1897 | 1984 | } |
| 1898 | decl_exports_kv.value = self.allocator.shrink(list, new_len); | |
| 1985 | decl_exports_kv.value = self.gpa.shrink(list, new_len); | |
| 1899 | 1986 | if (new_len == 0) { |
| 1900 | 1987 | self.decl_exports.removeAssertDiscard(exp.exported_decl); |
| 1901 | 1988 | } |
| ... | ... | @@ -1904,12 +1991,12 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { |
| 1904 | 1991 | elf.deleteExport(exp.link); |
| 1905 | 1992 | } |
| 1906 | 1993 | if (self.failed_exports.remove(exp)) |entry| { |
| 1907 | entry.value.destroy(self.allocator); | |
| 1994 | entry.value.destroy(self.gpa); | |
| 1908 | 1995 | } |
| 1909 | 1996 | _ = self.symbol_exports.remove(exp.options.name); |
| 1910 | self.allocator.destroy(exp); | |
| 1997 | self.gpa.destroy(exp); | |
| 1911 | 1998 | } |
| 1912 | self.allocator.free(kv.value); | |
| 1999 | self.gpa.free(kv.value); | |
| 1913 | 2000 | } |
| 1914 | 2001 | |
| 1915 | 2002 | fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { |
| ... | ... | @@ -1917,7 +2004,7 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { |
| 1917 | 2004 | defer tracy.end(); |
| 1918 | 2005 | |
| 1919 | 2006 | // Use the Decl's arena for function memory. |
| 1920 | var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); | |
| 2007 | var arena = decl.typed_value.most_recent.arena.?.promote(self.gpa); | |
| 1921 | 2008 | defer decl.typed_value.most_recent.arena.?.* = arena.state; |
| 1922 | 2009 | var inner_block: Scope.Block = .{ |
| 1923 | 2010 | .parent = null, |
| ... | ... | @@ -1926,10 +2013,10 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { |
| 1926 | 2013 | .instructions = .{}, |
| 1927 | 2014 | .arena = &arena.allocator, |
| 1928 | 2015 | }; |
| 1929 | defer inner_block.instructions.deinit(self.allocator); | |
| 2016 | defer inner_block.instructions.deinit(self.gpa); | |
| 1930 | 2017 | |
| 1931 | 2018 | const fn_zir = func.analysis.queued; |
| 1932 | defer fn_zir.arena.promote(self.allocator).deinit(); | |
| 2019 | defer fn_zir.arena.promote(self.gpa).deinit(); | |
| 1933 | 2020 | func.analysis = .{ .in_progress = {} }; |
| 1934 | 2021 | //std.debug.warn("set {} to in_progress\n", .{decl.name}); |
| 1935 | 2022 | |
| ... | ... | @@ -1944,7 +2031,7 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { |
| 1944 | 2031 | //std.debug.warn("mark {} outdated\n", .{decl.name}); |
| 1945 | 2032 | try self.work_queue.writeItem(.{ .analyze_decl = decl }); |
| 1946 | 2033 | if (self.failed_decls.remove(decl)) |entry| { |
| 1947 | entry.value.destroy(self.allocator); | |
| 2034 | entry.value.destroy(self.gpa); | |
| 1948 | 2035 | } |
| 1949 | 2036 | decl.analysis = .outdated; |
| 1950 | 2037 | } |
| ... | ... | @@ -1955,7 +2042,7 @@ fn allocateNewDecl( |
| 1955 | 2042 | src_index: usize, |
| 1956 | 2043 | contents_hash: std.zig.SrcHash, |
| 1957 | 2044 | ) !*Decl { |
| 1958 | const new_decl = try self.allocator.create(Decl); | |
| 2045 | const new_decl = try self.gpa.create(Decl); | |
| 1959 | 2046 | new_decl.* = .{ |
| 1960 | 2047 | .name = "", |
| 1961 | 2048 | .scope = scope.namespace(), |
| ... | ... | @@ -1978,10 +2065,10 @@ fn createNewDecl( |
| 1978 | 2065 | name_hash: Scope.NameHash, |
| 1979 | 2066 | contents_hash: std.zig.SrcHash, |
| 1980 | 2067 | ) !*Decl { |
| 1981 | try self.decl_table.ensureCapacity(self.decl_table.items().len + 1); | |
| 2068 | try self.decl_table.ensureCapacity(self.gpa, self.decl_table.items().len + 1); | |
| 1982 | 2069 | const new_decl = try self.allocateNewDecl(scope, src_index, contents_hash); |
| 1983 | errdefer self.allocator.destroy(new_decl); | |
| 1984 | new_decl.name = try mem.dupeZ(self.allocator, u8, decl_name); | |
| 2070 | errdefer self.gpa.destroy(new_decl); | |
| 2071 | new_decl.name = try mem.dupeZ(self.gpa, u8, decl_name); | |
| 1985 | 2072 | self.decl_table.putAssumeCapacityNoClobber(name_hash, new_decl); |
| 1986 | 2073 | return new_decl; |
| 1987 | 2074 | } |
| ... | ... | @@ -1989,7 +2076,7 @@ fn createNewDecl( |
| 1989 | 2076 | fn analyzeZirDecl(self: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { |
| 1990 | 2077 | var decl_scope: Scope.DeclAnalysis = .{ |
| 1991 | 2078 | .decl = decl, |
| 1992 | .arena = std.heap.ArenaAllocator.init(self.allocator), | |
| 2079 | .arena = std.heap.ArenaAllocator.init(self.gpa), | |
| 1993 | 2080 | }; |
| 1994 | 2081 | errdefer decl_scope.arena.deinit(); |
| 1995 | 2082 | |
| ... | ... | @@ -2005,7 +2092,7 @@ fn analyzeZirDecl(self: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bo |
| 2005 | 2092 | prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); |
| 2006 | 2093 | type_changed = !tvm.typed_value.ty.eql(typed_value.ty); |
| 2007 | 2094 | |
| 2008 | tvm.deinit(self.allocator); | |
| 2095 | tvm.deinit(self.gpa); | |
| 2009 | 2096 | } |
| 2010 | 2097 | |
| 2011 | 2098 | arena_state.* = decl_scope.arena.state; |
| ... | ... | @@ -2143,11 +2230,11 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const |
| 2143 | 2230 | else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), |
| 2144 | 2231 | } |
| 2145 | 2232 | |
| 2146 | try self.decl_exports.ensureCapacity(self.decl_exports.items().len + 1); | |
| 2147 | try self.export_owners.ensureCapacity(self.export_owners.items().len + 1); | |
| 2233 | try self.decl_exports.ensureCapacity(self.gpa, self.decl_exports.items().len + 1); | |
| 2234 | try self.export_owners.ensureCapacity(self.gpa, self.export_owners.items().len + 1); | |
| 2148 | 2235 | |
| 2149 | const new_export = try self.allocator.create(Export); | |
| 2150 | errdefer self.allocator.destroy(new_export); | |
| 2236 | const new_export = try self.gpa.create(Export); | |
| 2237 | errdefer self.gpa.destroy(new_export); | |
| 2151 | 2238 | |
| 2152 | 2239 | const owner_decl = scope.decl().?; |
| 2153 | 2240 | |
| ... | ... | @@ -2161,27 +2248,27 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const |
| 2161 | 2248 | }; |
| 2162 | 2249 | |
| 2163 | 2250 | // Add to export_owners table. |
| 2164 | const eo_gop = self.export_owners.getOrPut(owner_decl) catch unreachable; | |
| 2251 | const eo_gop = self.export_owners.getOrPut(self.gpa, owner_decl) catch unreachable; | |
| 2165 | 2252 | if (!eo_gop.found_existing) { |
| 2166 | 2253 | eo_gop.entry.value = &[0]*Export{}; |
| 2167 | 2254 | } |
| 2168 | eo_gop.entry.value = try self.allocator.realloc(eo_gop.entry.value, eo_gop.entry.value.len + 1); | |
| 2255 | eo_gop.entry.value = try self.gpa.realloc(eo_gop.entry.value, eo_gop.entry.value.len + 1); | |
| 2169 | 2256 | eo_gop.entry.value[eo_gop.entry.value.len - 1] = new_export; |
| 2170 | errdefer eo_gop.entry.value = self.allocator.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); | |
| 2257 | errdefer eo_gop.entry.value = self.gpa.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); | |
| 2171 | 2258 | |
| 2172 | 2259 | // Add to exported_decl table. |
| 2173 | const de_gop = self.decl_exports.getOrPut(exported_decl) catch unreachable; | |
| 2260 | const de_gop = self.decl_exports.getOrPut(self.gpa, exported_decl) catch unreachable; | |
| 2174 | 2261 | if (!de_gop.found_existing) { |
| 2175 | 2262 | de_gop.entry.value = &[0]*Export{}; |
| 2176 | 2263 | } |
| 2177 | de_gop.entry.value = try self.allocator.realloc(de_gop.entry.value, de_gop.entry.value.len + 1); | |
| 2264 | de_gop.entry.value = try self.gpa.realloc(de_gop.entry.value, de_gop.entry.value.len + 1); | |
| 2178 | 2265 | de_gop.entry.value[de_gop.entry.value.len - 1] = new_export; |
| 2179 | errdefer de_gop.entry.value = self.allocator.shrink(de_gop.entry.value, de_gop.entry.value.len - 1); | |
| 2266 | errdefer de_gop.entry.value = self.gpa.shrink(de_gop.entry.value, de_gop.entry.value.len - 1); | |
| 2180 | 2267 | |
| 2181 | 2268 | if (self.symbol_exports.get(symbol_name)) |_| { |
| 2182 | try self.failed_exports.ensureCapacity(self.failed_exports.items().len + 1); | |
| 2269 | try self.failed_exports.ensureCapacity(self.gpa, self.failed_exports.items().len + 1); | |
| 2183 | 2270 | self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( |
| 2184 | self.allocator, | |
| 2271 | self.gpa, | |
| 2185 | 2272 | src, |
| 2186 | 2273 | "exported symbol collision: {}", |
| 2187 | 2274 | .{symbol_name}, |
| ... | ... | @@ -2192,21 +2279,19 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const |
| 2192 | 2279 | } |
| 2193 | 2280 | |
| 2194 | 2281 | try self.symbol_exports.putNoClobber(symbol_name, new_export); |
| 2195 | if (self.bin_file.cast(link.File.Elf)) |elf| { | |
| 2196 | elf.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) { | |
| 2197 | error.OutOfMemory => return error.OutOfMemory, | |
| 2198 | else => { | |
| 2199 | try self.failed_exports.ensureCapacity(self.failed_exports.items().len + 1); | |
| 2200 | self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( | |
| 2201 | self.allocator, | |
| 2202 | src, | |
| 2203 | "unable to export: {}", | |
| 2204 | .{@errorName(err)}, | |
| 2205 | )); | |
| 2206 | new_export.status = .failed_retryable; | |
| 2207 | }, | |
| 2208 | }; | |
| 2209 | } | |
| 2282 | self.bin_file.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) { | |
| 2283 | error.OutOfMemory => return error.OutOfMemory, | |
| 2284 | else => { | |
| 2285 | try self.failed_exports.ensureCapacity(self.gpa, self.failed_exports.items().len + 1); | |
| 2286 | self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( | |
| 2287 | self.gpa, | |
| 2288 | src, | |
| 2289 | "unable to export: {}", | |
| 2290 | .{@errorName(err)}, | |
| 2291 | )); | |
| 2292 | new_export.status = .failed_retryable; | |
| 2293 | }, | |
| 2294 | }; | |
| 2210 | 2295 | } |
| 2211 | 2296 | |
| 2212 | 2297 | fn addNewInstArgs( |
| ... | ... | @@ -2223,13 +2308,13 @@ fn addNewInstArgs( |
| 2223 | 2308 | } |
| 2224 | 2309 | |
| 2225 | 2310 | fn newZIRInst( |
| 2226 | allocator: *Allocator, | |
| 2311 | gpa: *Allocator, | |
| 2227 | 2312 | src: usize, |
| 2228 | 2313 | comptime T: type, |
| 2229 | 2314 | positionals: std.meta.fieldInfo(T, "positionals").field_type, |
| 2230 | 2315 | kw_args: std.meta.fieldInfo(T, "kw_args").field_type, |
| 2231 | ) !*zir.Inst { | |
| 2232 | const inst = try allocator.create(T); | |
| 2316 | ) !*T { | |
| 2317 | const inst = try gpa.create(T); | |
| 2233 | 2318 | inst.* = .{ |
| 2234 | 2319 | .base = .{ |
| 2235 | 2320 | .tag = T.base_tag, |
| ... | ... | @@ -2238,30 +2323,48 @@ fn newZIRInst( |
| 2238 | 2323 | .positionals = positionals, |
| 2239 | 2324 | .kw_args = kw_args, |
| 2240 | 2325 | }; |
| 2241 | return &inst.base; | |
| 2326 | return inst; | |
| 2242 | 2327 | } |
| 2243 | 2328 | |
| 2244 | fn addZIRInst( | |
| 2329 | fn addZIRInstSpecial( | |
| 2245 | 2330 | self: *Module, |
| 2246 | 2331 | scope: *Scope, |
| 2247 | 2332 | src: usize, |
| 2248 | 2333 | comptime T: type, |
| 2249 | 2334 | positionals: std.meta.fieldInfo(T, "positionals").field_type, |
| 2250 | 2335 | kw_args: std.meta.fieldInfo(T, "kw_args").field_type, |
| 2251 | ) !*zir.Inst { | |
| 2336 | ) !*T { | |
| 2252 | 2337 | const gen_zir = scope.cast(Scope.GenZIR).?; |
| 2253 | try gen_zir.instructions.ensureCapacity(gen_zir.instructions.items.len + 1); | |
| 2254 | const inst = try newZIRInst(&gen_zir.arena.allocator, src, T, positionals, kw_args); | |
| 2255 | gen_zir.instructions.appendAssumeCapacity(inst); | |
| 2338 | try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); | |
| 2339 | const inst = try newZIRInst(gen_zir.arena, src, T, positionals, kw_args); | |
| 2340 | gen_zir.instructions.appendAssumeCapacity(&inst.base); | |
| 2256 | 2341 | return inst; |
| 2257 | 2342 | } |
| 2258 | 2343 | |
| 2344 | fn addZIRInst( | |
| 2345 | self: *Module, | |
| 2346 | scope: *Scope, | |
| 2347 | src: usize, | |
| 2348 | comptime T: type, | |
| 2349 | positionals: std.meta.fieldInfo(T, "positionals").field_type, | |
| 2350 | kw_args: std.meta.fieldInfo(T, "kw_args").field_type, | |
| 2351 | ) !*zir.Inst { | |
| 2352 | const inst_special = try self.addZIRInstSpecial(scope, src, T, positionals, kw_args); | |
| 2353 | return &inst_special.base; | |
| 2354 | } | |
| 2355 | ||
| 2259 | 2356 | /// TODO The existence of this function is a workaround for a bug in stage1. |
| 2260 | 2357 | fn addZIRInstConst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst { |
| 2261 | 2358 | const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type; |
| 2262 | 2359 | return self.addZIRInst(scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{}); |
| 2263 | 2360 | } |
| 2264 | 2361 | |
| 2362 | /// TODO The existence of this function is a workaround for a bug in stage1. | |
| 2363 | fn addZIRInstBlock(self: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block { | |
| 2364 | const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type; | |
| 2365 | return self.addZIRInstSpecial(scope, src, zir.Inst.Block, P{ .body = body }, .{}); | |
| 2366 | } | |
| 2367 | ||
| 2265 | 2368 | fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime T: type) !*T { |
| 2266 | 2369 | const inst = try block.arena.create(T); |
| 2267 | 2370 | inst.* = .{ |
| ... | ... | @@ -2272,7 +2375,7 @@ fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime |
| 2272 | 2375 | }, |
| 2273 | 2376 | .args = undefined, |
| 2274 | 2377 | }; |
| 2275 | try block.instructions.append(self.allocator, &inst.base); | |
| 2378 | try block.instructions.append(self.gpa, &inst.base); | |
| 2276 | 2379 | return inst; |
| 2277 | 2380 | } |
| 2278 | 2381 | |
| ... | ... | @@ -2392,6 +2495,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In |
| 2392 | 2495 | switch (old_inst.tag) { |
| 2393 | 2496 | .arg => return self.analyzeInstArg(scope, old_inst.cast(zir.Inst.Arg).?), |
| 2394 | 2497 | .block => return self.analyzeInstBlock(scope, old_inst.cast(zir.Inst.Block).?), |
| 2498 | .@"break" => return self.analyzeInstBreak(scope, old_inst.cast(zir.Inst.Break).?), | |
| 2395 | 2499 | .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.cast(zir.Inst.Breakpoint).?), |
| 2396 | 2500 | .breakvoid => return self.analyzeInstBreakVoid(scope, old_inst.cast(zir.Inst.BreakVoid).?), |
| 2397 | 2501 | .call => return self.analyzeInstCall(scope, old_inst.cast(zir.Inst.Call).?), |
| ... | ... | @@ -2406,6 +2510,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In |
| 2406 | 2510 | const big_int = old_inst.cast(zir.Inst.Int).?.positionals.int; |
| 2407 | 2511 | return self.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int); |
| 2408 | 2512 | }, |
| 2513 | .inttype => return self.analyzeInstIntType(scope, old_inst.cast(zir.Inst.IntType).?), | |
| 2409 | 2514 | .ptrtoint => return self.analyzeInstPtrToInt(scope, old_inst.cast(zir.Inst.PtrToInt).?), |
| 2410 | 2515 | .fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.cast(zir.Inst.FieldPtr).?), |
| 2411 | 2516 | .deref => return self.analyzeInstDeref(scope, old_inst.cast(zir.Inst.Deref).?), |
| ... | ... | @@ -2422,6 +2527,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In |
| 2422 | 2527 | .bitcast => return self.analyzeInstBitCast(scope, old_inst.cast(zir.Inst.BitCast).?), |
| 2423 | 2528 | .elemptr => return self.analyzeInstElemPtr(scope, old_inst.cast(zir.Inst.ElemPtr).?), |
| 2424 | 2529 | .add => return self.analyzeInstAdd(scope, old_inst.cast(zir.Inst.Add).?), |
| 2530 | .sub => return self.analyzeInstSub(scope, old_inst.cast(zir.Inst.Sub).?), | |
| 2425 | 2531 | .cmp => return self.analyzeInstCmp(scope, old_inst.cast(zir.Inst.Cmp).?), |
| 2426 | 2532 | .condbr => return self.analyzeInstCondBr(scope, old_inst.cast(zir.Inst.CondBr).?), |
| 2427 | 2533 | .isnull => return self.analyzeInstIsNull(scope, old_inst.cast(zir.Inst.IsNull).?), |
| ... | ... | @@ -2432,7 +2538,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In |
| 2432 | 2538 | fn analyzeInstStr(self: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst { |
| 2433 | 2539 | // The bytes references memory inside the ZIR module, which can get deallocated |
| 2434 | 2540 | // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. |
| 2435 | var new_decl_arena = std.heap.ArenaAllocator.init(self.allocator); | |
| 2541 | var new_decl_arena = std.heap.ArenaAllocator.init(self.gpa); | |
| 2436 | 2542 | const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes); |
| 2437 | 2543 | |
| 2438 | 2544 | const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); |
| ... | ... | @@ -2456,8 +2562,8 @@ fn createAnonymousDecl( |
| 2456 | 2562 | ) !*Decl { |
| 2457 | 2563 | const name_index = self.getNextAnonNameIndex(); |
| 2458 | 2564 | const scope_decl = scope.decl().?; |
| 2459 | const name = try std.fmt.allocPrint(self.allocator, "{}__anon_{}", .{ scope_decl.name, name_index }); | |
| 2460 | defer self.allocator.free(name); | |
| 2565 | const name = try std.fmt.allocPrint(self.gpa, "{}__anon_{}", .{ scope_decl.name, name_index }); | |
| 2566 | defer self.gpa.free(name); | |
| 2461 | 2567 | const name_hash = scope.namespace().fullyQualifiedNameHash(name); |
| 2462 | 2568 | const src_hash: std.zig.SrcHash = undefined; |
| 2463 | 2569 | const new_decl = try self.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash); |
| ... | ... | @@ -2546,15 +2652,15 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr |
| 2546 | 2652 | .arena = parent_block.arena, |
| 2547 | 2653 | // TODO @as here is working around a miscompilation compiler bug :( |
| 2548 | 2654 | .label = @as(?Scope.Block.Label, Scope.Block.Label{ |
| 2549 | .name = inst.positionals.label, | |
| 2655 | .zir_block = inst, | |
| 2550 | 2656 | .results = .{}, |
| 2551 | 2657 | .block_inst = block_inst, |
| 2552 | 2658 | }), |
| 2553 | 2659 | }; |
| 2554 | 2660 | const label = &child_block.label.?; |
| 2555 | 2661 | |
| 2556 | defer child_block.instructions.deinit(self.allocator); | |
| 2557 | defer label.results.deinit(self.allocator); | |
| 2662 | defer child_block.instructions.deinit(self.gpa); | |
| 2663 | defer label.results.deinit(self.gpa); | |
| 2558 | 2664 | |
| 2559 | 2665 | try self.analyzeBody(&child_block.base, inst.positionals.body); |
| 2560 | 2666 | |
| ... | ... | @@ -2562,21 +2668,9 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr |
| 2562 | 2668 | assert(child_block.instructions.items.len != 0); |
| 2563 | 2669 | assert(child_block.instructions.items[child_block.instructions.items.len - 1].tag.isNoReturn()); |
| 2564 | 2670 | |
| 2565 | if (label.results.items.len <= 1) { | |
| 2566 | // No need to add the Block instruction; we can add the instructions to the parent block directly. | |
| 2567 | // Blocks are terminated with a noreturn instruction which we do not want to include. | |
| 2568 | const instrs = child_block.instructions.items; | |
| 2569 | try parent_block.instructions.appendSlice(self.allocator, instrs[0 .. instrs.len - 1]); | |
| 2570 | if (label.results.items.len == 1) { | |
| 2571 | return label.results.items[0]; | |
| 2572 | } else { | |
| 2573 | return self.constNoReturn(scope, inst.base.src); | |
| 2574 | } | |
| 2575 | } | |
| 2576 | ||
| 2577 | 2671 | // Need to set the type and emit the Block instruction. This allows machine code generation |
| 2578 | 2672 | // to emit a jump instruction to after the block when it encounters the break. |
| 2579 | try parent_block.instructions.append(self.allocator, &block_inst.base); | |
| 2673 | try parent_block.instructions.append(self.gpa, &block_inst.base); | |
| 2580 | 2674 | block_inst.base.ty = try self.resolvePeerTypes(scope, label.results.items); |
| 2581 | 2675 | block_inst.args.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; |
| 2582 | 2676 | return &block_inst.base; |
| ... | ... | @@ -2587,22 +2681,39 @@ fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.Breakpoin |
| 2587 | 2681 | return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, {}); |
| 2588 | 2682 | } |
| 2589 | 2683 | |
| 2684 | fn analyzeInstBreak(self: *Module, scope: *Scope, inst: *zir.Inst.Break) InnerError!*Inst { | |
| 2685 | const operand = try self.resolveInst(scope, inst.positionals.operand); | |
| 2686 | const block = inst.positionals.block; | |
| 2687 | return self.analyzeBreak(scope, inst.base.src, block, operand); | |
| 2688 | } | |
| 2689 | ||
| 2590 | 2690 | fn analyzeInstBreakVoid(self: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) InnerError!*Inst { |
| 2591 | const label_name = inst.positionals.label; | |
| 2691 | const block = inst.positionals.block; | |
| 2592 | 2692 | const void_inst = try self.constVoid(scope, inst.base.src); |
| 2693 | return self.analyzeBreak(scope, inst.base.src, block, void_inst); | |
| 2694 | } | |
| 2593 | 2695 | |
| 2696 | fn analyzeBreak( | |
| 2697 | self: *Module, | |
| 2698 | scope: *Scope, | |
| 2699 | src: usize, | |
| 2700 | zir_block: *zir.Inst.Block, | |
| 2701 | operand: *Inst, | |
| 2702 | ) InnerError!*Inst { | |
| 2594 | 2703 | var opt_block = scope.cast(Scope.Block); |
| 2595 | 2704 | while (opt_block) |block| { |
| 2596 | 2705 | if (block.label) |*label| { |
| 2597 | if (mem.eql(u8, label.name, label_name)) { | |
| 2598 | try label.results.append(self.allocator, void_inst); | |
| 2599 | return self.constNoReturn(scope, inst.base.src); | |
| 2706 | if (label.zir_block == zir_block) { | |
| 2707 | try label.results.append(self.gpa, operand); | |
| 2708 | const b = try self.requireRuntimeBlock(scope, src); | |
| 2709 | return self.addNewInstArgs(b, src, Type.initTag(.noreturn), Inst.Br, .{ | |
| 2710 | .block = label.block_inst, | |
| 2711 | .operand = operand, | |
| 2712 | }); | |
| 2600 | 2713 | } |
| 2601 | 2714 | } |
| 2602 | 2715 | opt_block = block.parent; |
| 2603 | } else { | |
| 2604 | return self.fail(scope, inst.base.src, "use of undeclared label '{}'", .{label_name}); | |
| 2605 | } | |
| 2716 | } else unreachable; | |
| 2606 | 2717 | } |
| 2607 | 2718 | |
| 2608 | 2719 | fn analyzeInstDeclRefStr(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst { |
| ... | ... | @@ -2718,8 +2829,8 @@ fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerErro |
| 2718 | 2829 | |
| 2719 | 2830 | // TODO handle function calls of generic functions |
| 2720 | 2831 | |
| 2721 | const fn_param_types = try self.allocator.alloc(Type, fn_params_len); | |
| 2722 | defer self.allocator.free(fn_param_types); | |
| 2832 | const fn_param_types = try self.gpa.alloc(Type, fn_params_len); | |
| 2833 | defer self.gpa.free(fn_param_types); | |
| 2723 | 2834 | func.ty.fnParamTypes(fn_param_types); |
| 2724 | 2835 | |
| 2725 | 2836 | const casted_args = try scope.arena().alloc(*Inst, fn_params_len); |
| ... | ... | @@ -2738,7 +2849,7 @@ fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerErro |
| 2738 | 2849 | fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst { |
| 2739 | 2850 | const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type); |
| 2740 | 2851 | const fn_zir = blk: { |
| 2741 | var fn_arena = std.heap.ArenaAllocator.init(self.allocator); | |
| 2852 | var fn_arena = std.heap.ArenaAllocator.init(self.gpa); | |
| 2742 | 2853 | errdefer fn_arena.deinit(); |
| 2743 | 2854 | |
| 2744 | 2855 | const fn_zir = try scope.arena().create(Fn.ZIR); |
| ... | ... | @@ -2763,6 +2874,10 @@ fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError |
| 2763 | 2874 | }); |
| 2764 | 2875 | } |
| 2765 | 2876 | |
| 2877 | fn analyzeInstIntType(self: *Module, scope: *Scope, inttype: *zir.Inst.IntType) InnerError!*Inst { | |
| 2878 | return self.fail(scope, inttype.base.src, "TODO implement inttype", .{}); | |
| 2879 | } | |
| 2880 | ||
| 2766 | 2881 | fn analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { |
| 2767 | 2882 | const return_type = try self.resolveType(scope, fntype.positionals.return_type); |
| 2768 | 2883 | |
| ... | ... | @@ -2923,6 +3038,10 @@ fn analyzeInstElemPtr(self: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inn |
| 2923 | 3038 | return self.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{}); |
| 2924 | 3039 | } |
| 2925 | 3040 | |
| 3041 | fn analyzeInstSub(self: *Module, scope: *Scope, inst: *zir.Inst.Sub) InnerError!*Inst { | |
| 3042 | return self.fail(scope, inst.base.src, "TODO implement analysis of sub", .{}); | |
| 3043 | } | |
| 3044 | ||
| 2926 | 3045 | fn analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError!*Inst { |
| 2927 | 3046 | const tracy = trace(@src()); |
| 2928 | 3047 | defer tracy.end(); |
| ... | ... | @@ -3119,7 +3238,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner |
| 3119 | 3238 | .instructions = .{}, |
| 3120 | 3239 | .arena = parent_block.arena, |
| 3121 | 3240 | }; |
| 3122 | defer true_block.instructions.deinit(self.allocator); | |
| 3241 | defer true_block.instructions.deinit(self.gpa); | |
| 3123 | 3242 | try self.analyzeBody(&true_block.base, inst.positionals.true_body); |
| 3124 | 3243 | |
| 3125 | 3244 | var false_block: Scope.Block = .{ |
| ... | ... | @@ -3129,7 +3248,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner |
| 3129 | 3248 | .instructions = .{}, |
| 3130 | 3249 | .arena = parent_block.arena, |
| 3131 | 3250 | }; |
| 3132 | defer false_block.instructions.deinit(self.allocator); | |
| 3251 | defer false_block.instructions.deinit(self.gpa); | |
| 3133 | 3252 | try self.analyzeBody(&false_block.base, inst.positionals.false_body); |
| 3134 | 3253 | |
| 3135 | 3254 | return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){ |
| ... | ... | @@ -3283,7 +3402,7 @@ fn cmpNumeric( |
| 3283 | 3402 | return self.constUndef(scope, src, Type.initTag(.bool)); |
| 3284 | 3403 | const is_unsigned = if (lhs_is_float) x: { |
| 3285 | 3404 | var bigint_space: Value.BigIntSpace = undefined; |
| 3286 | var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(self.allocator); | |
| 3405 | var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(self.gpa); | |
| 3287 | 3406 | defer bigint.deinit(); |
| 3288 | 3407 | const zcmp = lhs_val.orderAgainstZero(); |
| 3289 | 3408 | if (lhs_val.floatHasFraction()) { |
| ... | ... | @@ -3318,7 +3437,7 @@ fn cmpNumeric( |
| 3318 | 3437 | return self.constUndef(scope, src, Type.initTag(.bool)); |
| 3319 | 3438 | const is_unsigned = if (rhs_is_float) x: { |
| 3320 | 3439 | var bigint_space: Value.BigIntSpace = undefined; |
| 3321 | var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(self.allocator); | |
| 3440 | var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(self.gpa); | |
| 3322 | 3441 | defer bigint.deinit(); |
| 3323 | 3442 | const zcmp = rhs_val.orderAgainstZero(); |
| 3324 | 3443 | if (rhs_val.floatHasFraction()) { |
| ... | ... | @@ -3355,7 +3474,7 @@ fn cmpNumeric( |
| 3355 | 3474 | break :blk try self.makeIntType(scope, dest_int_is_signed, casted_bits); |
| 3356 | 3475 | }; |
| 3357 | 3476 | const casted_lhs = try self.coerce(scope, dest_type, lhs); |
| 3358 | const casted_rhs = try self.coerce(scope, dest_type, lhs); | |
| 3477 | const casted_rhs = try self.coerce(scope, dest_type, rhs); | |
| 3359 | 3478 | |
| 3360 | 3479 | return self.addNewInstArgs(b, src, Type.initTag(.bool), Inst.Cmp, .{ |
| 3361 | 3480 | .lhs = casted_lhs, |
| ... | ... | @@ -3379,6 +3498,8 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { |
| 3379 | 3498 | fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { |
| 3380 | 3499 | if (instructions.len == 0) |
| 3381 | 3500 | return Type.initTag(.noreturn); |
| 3501 | if (instructions.len == 1) | |
| 3502 | return instructions[0].ty; | |
| 3382 | 3503 | return self.fail(scope, instructions[0].src, "TODO peer type resolution", .{}); |
| 3383 | 3504 | } |
| 3384 | 3505 | |
| ... | ... | @@ -3456,7 +3577,7 @@ fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *I |
| 3456 | 3577 | |
| 3457 | 3578 | fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: var) InnerError { |
| 3458 | 3579 | @setCold(true); |
| 3459 | const err_msg = try ErrorMsg.create(self.allocator, src, format, args); | |
| 3580 | const err_msg = try ErrorMsg.create(self.gpa, src, format, args); | |
| 3460 | 3581 | return self.failWithOwnedErrorMsg(scope, src, err_msg); |
| 3461 | 3582 | } |
| 3462 | 3583 | |
| ... | ... | @@ -3486,9 +3607,9 @@ fn failNode( |
| 3486 | 3607 | |
| 3487 | 3608 | fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *ErrorMsg) InnerError { |
| 3488 | 3609 | { |
| 3489 | errdefer err_msg.destroy(self.allocator); | |
| 3490 | try self.failed_decls.ensureCapacity(self.failed_decls.items().len + 1); | |
| 3491 | try self.failed_files.ensureCapacity(self.failed_files.items().len + 1); | |
| 3610 | errdefer err_msg.destroy(self.gpa); | |
| 3611 | try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); | |
| 3612 | try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); | |
| 3492 | 3613 | } |
| 3493 | 3614 | switch (scope.tag) { |
| 3494 | 3615 | .decl => { |
| ... | ... | @@ -3541,28 +3662,28 @@ pub const ErrorMsg = struct { |
| 3541 | 3662 | byte_offset: usize, |
| 3542 | 3663 | msg: []const u8, |
| 3543 | 3664 | |
| 3544 | pub fn create(allocator: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !*ErrorMsg { | |
| 3545 | const self = try allocator.create(ErrorMsg); | |
| 3546 | errdefer allocator.destroy(self); | |
| 3547 | self.* = try init(allocator, byte_offset, format, args); | |
| 3665 | pub fn create(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !*ErrorMsg { | |
| 3666 | const self = try gpa.create(ErrorMsg); | |
| 3667 | errdefer gpa.destroy(self); | |
| 3668 | self.* = try init(gpa, byte_offset, format, args); | |
| 3548 | 3669 | return self; |
| 3549 | 3670 | } |
| 3550 | 3671 | |
| 3551 | 3672 | /// Assumes the ErrorMsg struct and msg were both allocated with allocator. |
| 3552 | pub fn destroy(self: *ErrorMsg, allocator: *Allocator) void { | |
| 3553 | self.deinit(allocator); | |
| 3554 | allocator.destroy(self); | |
| 3673 | pub fn destroy(self: *ErrorMsg, gpa: *Allocator) void { | |
| 3674 | self.deinit(gpa); | |
| 3675 | gpa.destroy(self); | |
| 3555 | 3676 | } |
| 3556 | 3677 | |
| 3557 | pub fn init(allocator: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !ErrorMsg { | |
| 3678 | pub fn init(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !ErrorMsg { | |
| 3558 | 3679 | return ErrorMsg{ |
| 3559 | 3680 | .byte_offset = byte_offset, |
| 3560 | .msg = try std.fmt.allocPrint(allocator, format, args), | |
| 3681 | .msg = try std.fmt.allocPrint(gpa, format, args), | |
| 3561 | 3682 | }; |
| 3562 | 3683 | } |
| 3563 | 3684 | |
| 3564 | pub fn deinit(self: *ErrorMsg, allocator: *Allocator) void { | |
| 3565 | allocator.free(self.msg); | |
| 3685 | pub fn deinit(self: *ErrorMsg, gpa: *Allocator) void { | |
| 3686 | gpa.free(self.msg); | |
| 3566 | 3687 | self.* = undefined; |
| 3567 | 3688 | } |
| 3568 | 3689 | }; |
src-self-hosted/codegen.zig+460-83| ... | ... | @@ -12,6 +12,18 @@ const Target = std.Target; |
| 12 | 12 | const Allocator = mem.Allocator; |
| 13 | 13 | const trace = @import("tracy.zig").trace; |
| 14 | 14 | |
| 15 | /// The codegen-related data that is stored in `ir.Inst.Block` instructions. | |
| 16 | pub const BlockData = struct { | |
| 17 | relocs: std.ArrayListUnmanaged(Reloc) = .{}, | |
| 18 | }; | |
| 19 | ||
| 20 | pub const Reloc = union(enum) { | |
| 21 | /// The value is an offset into the `Function` `code` from the beginning. | |
| 22 | /// To perform the reloc, write 32-bit signed little-endian integer | |
| 23 | /// which is a relative jump, based on the address following the reloc. | |
| 24 | rel32: usize, | |
| 25 | }; | |
| 26 | ||
| 15 | 27 | pub const Result = union(enum) { |
| 16 | 28 | /// The `code` parameter passed to `generateSymbol` has the value appended. |
| 17 | 29 | appended: void, |
| ... | ... | @@ -46,7 +58,14 @@ pub fn generateSymbol( |
| 46 | 58 | var mc_args = try std.ArrayList(Function.MCValue).initCapacity(bin_file.allocator, param_types.len); |
| 47 | 59 | defer mc_args.deinit(); |
| 48 | 60 | |
| 49 | var next_stack_offset: u64 = 0; | |
| 61 | var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator); | |
| 62 | defer { | |
| 63 | assert(branch_stack.items.len == 1); | |
| 64 | branch_stack.items[0].deinit(bin_file.allocator); | |
| 65 | branch_stack.deinit(); | |
| 66 | } | |
| 67 | const branch = try branch_stack.addOne(); | |
| 68 | branch.* = .{}; | |
| 50 | 69 | |
| 51 | 70 | switch (fn_type.fnCallingConvention()) { |
| 52 | 71 | .Naked => assert(mc_args.items.len == 0), |
| ... | ... | @@ -61,8 +80,8 @@ pub fn generateSymbol( |
| 61 | 80 | switch (param_type.zigTypeTag()) { |
| 62 | 81 | .Bool, .Int => { |
| 63 | 82 | if (next_int_reg >= integer_registers.len) { |
| 64 | try mc_args.append(.{ .stack_offset = next_stack_offset }); | |
| 65 | next_stack_offset += param_type.abiSize(bin_file.options.target); | |
| 83 | try mc_args.append(.{ .stack_offset = branch.next_stack_offset }); | |
| 84 | branch.next_stack_offset += @intCast(u32, param_type.abiSize(bin_file.options.target)); | |
| 66 | 85 | } else { |
| 67 | 86 | try mc_args.append(.{ .register = @enumToInt(integer_registers[next_int_reg]) }); |
| 68 | 87 | next_int_reg += 1; |
| ... | ... | @@ -100,16 +119,17 @@ pub fn generateSymbol( |
| 100 | 119 | } |
| 101 | 120 | |
| 102 | 121 | var function = Function{ |
| 122 | .gpa = bin_file.allocator, | |
| 103 | 123 | .target = &bin_file.options.target, |
| 104 | 124 | .bin_file = bin_file, |
| 105 | 125 | .mod_fn = module_fn, |
| 106 | 126 | .code = code, |
| 107 | .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(bin_file.allocator), | |
| 108 | 127 | .err_msg = null, |
| 109 | 128 | .args = mc_args.items, |
| 129 | .branch_stack = &branch_stack, | |
| 110 | 130 | }; |
| 111 | defer function.inst_table.deinit(); | |
| 112 | 131 | |
| 132 | branch.max_end_stack = branch.next_stack_offset; | |
| 113 | 133 | function.gen() catch |err| switch (err) { |
| 114 | 134 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, |
| 115 | 135 | else => |e| return e, |
| ... | ... | @@ -210,18 +230,67 @@ pub fn generateSymbol( |
| 210 | 230 | } |
| 211 | 231 | } |
| 212 | 232 | |
| 233 | const InnerError = error { | |
| 234 | OutOfMemory, | |
| 235 | CodegenFail, | |
| 236 | }; | |
| 237 | ||
| 213 | 238 | const Function = struct { |
| 239 | gpa: *Allocator, | |
| 214 | 240 | bin_file: *link.File.Elf, |
| 215 | 241 | target: *const std.Target, |
| 216 | 242 | mod_fn: *const Module.Fn, |
| 217 | 243 | code: *std.ArrayList(u8), |
| 218 | inst_table: std.AutoHashMap(*ir.Inst, MCValue), | |
| 219 | 244 | err_msg: ?*ErrorMsg, |
| 220 | 245 | args: []MCValue, |
| 221 | 246 | |
| 247 | /// Whenever there is a runtime branch, we push a Branch onto this stack, | |
| 248 | /// and pop it off when the runtime branch joins. This provides an "overlay" | |
| 249 | /// of the table of mappings from instructions to `MCValue` from within the branch. | |
| 250 | /// This way we can modify the `MCValue` for an instruction in different ways | |
| 251 | /// within different branches. Special consideration is needed when a branch | |
| 252 | /// joins with its parent, to make sure all instructions have the same MCValue | |
| 253 | /// across each runtime branch upon joining. | |
| 254 | branch_stack: *std.ArrayList(Branch), | |
| 255 | ||
| 256 | const Branch = struct { | |
| 257 | inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, | |
| 258 | ||
| 259 | /// The key is an enum value of an arch-specific register. | |
| 260 | registers: std.AutoHashMapUnmanaged(usize, RegisterAllocation) = .{}, | |
| 261 | ||
| 262 | /// Maps offset to what is stored there. | |
| 263 | stack: std.AutoHashMapUnmanaged(usize, StackAllocation) = .{}, | |
| 264 | /// Offset from the stack base, representing the end of the stack frame. | |
| 265 | max_end_stack: u32 = 0, | |
| 266 | /// Represents the current end stack offset. If there is no existing slot | |
| 267 | /// to place a new stack allocation, it goes here, and then bumps `max_end_stack`. | |
| 268 | next_stack_offset: u32 = 0, | |
| 269 | ||
| 270 | fn deinit(self: *Branch, gpa: *Allocator) void { | |
| 271 | self.inst_table.deinit(gpa); | |
| 272 | self.registers.deinit(gpa); | |
| 273 | self.stack.deinit(gpa); | |
| 274 | self.* = undefined; | |
| 275 | } | |
| 276 | }; | |
| 277 | ||
| 278 | const RegisterAllocation = struct { | |
| 279 | inst: *ir.Inst, | |
| 280 | }; | |
| 281 | ||
| 282 | const StackAllocation = struct { | |
| 283 | inst: *ir.Inst, | |
| 284 | size: u32, | |
| 285 | }; | |
| 286 | ||
| 222 | 287 | const MCValue = union(enum) { |
| 288 | /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc. | |
| 223 | 289 | none, |
| 290 | /// Control flow will not allow this value to be observed. | |
| 224 | 291 | unreach, |
| 292 | /// No more references to this value remain. | |
| 293 | dead, | |
| 225 | 294 | /// A pointer-sized integer that fits in a register. |
| 226 | 295 | immediate: u64, |
| 227 | 296 | /// The constant was emitted into the code, at this offset. |
| ... | ... | @@ -233,6 +302,45 @@ const Function = struct { |
| 233 | 302 | memory: u64, |
| 234 | 303 | /// The value is one of the stack variables. |
| 235 | 304 | stack_offset: u64, |
| 305 | /// The value is in the compare flags assuming an unsigned operation, | |
| 306 | /// with this operator applied on top of it. | |
| 307 | compare_flags_unsigned: std.math.CompareOperator, | |
| 308 | /// The value is in the compare flags assuming a signed operation, | |
| 309 | /// with this operator applied on top of it. | |
| 310 | compare_flags_signed: std.math.CompareOperator, | |
| 311 | ||
| 312 | fn isMemory(mcv: MCValue) bool { | |
| 313 | return switch (mcv) { | |
| 314 | .embedded_in_code, .memory, .stack_offset => true, | |
| 315 | else => false, | |
| 316 | }; | |
| 317 | } | |
| 318 | ||
| 319 | fn isImmediate(mcv: MCValue) bool { | |
| 320 | return switch (mcv) { | |
| 321 | .immediate => true, | |
| 322 | else => false, | |
| 323 | }; | |
| 324 | } | |
| 325 | ||
| 326 | fn isMutable(mcv: MCValue) bool { | |
| 327 | return switch (mcv) { | |
| 328 | .none => unreachable, | |
| 329 | .unreach => unreachable, | |
| 330 | .dead => unreachable, | |
| 331 | ||
| 332 | .immediate, | |
| 333 | .embedded_in_code, | |
| 334 | .memory, | |
| 335 | .compare_flags_unsigned, | |
| 336 | .compare_flags_signed, | |
| 337 | => false, | |
| 338 | ||
| 339 | .register, | |
| 340 | .stack_offset, | |
| 341 | => true, | |
| 342 | }; | |
| 343 | } | |
| 236 | 344 | }; |
| 237 | 345 | |
| 238 | 346 | fn gen(self: *Function) !void { |
| ... | ... | @@ -292,9 +400,14 @@ const Function = struct { |
| 292 | 400 | } |
| 293 | 401 | |
| 294 | 402 | fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void { |
| 295 | for (self.mod_fn.analysis.success.instructions) |inst| { | |
| 403 | return self.genBody(self.mod_fn.analysis.success, arch); | |
| 404 | } | |
| 405 | ||
| 406 | fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void { | |
| 407 | const inst_table = &self.branch_stack.items[0].inst_table; | |
| 408 | for (body.instructions) |inst| { | |
| 296 | 409 | const new_inst = try self.genFuncInst(inst, arch); |
| 297 | try self.inst_table.putNoClobber(inst, new_inst); | |
| 410 | try inst_table.putNoClobber(self.gpa, inst, new_inst); | |
| 298 | 411 | } |
| 299 | 412 | } |
| 300 | 413 | |
| ... | ... | @@ -302,39 +415,166 @@ const Function = struct { |
| 302 | 415 | switch (inst.tag) { |
| 303 | 416 | .add => return self.genAdd(inst.cast(ir.Inst.Add).?, arch), |
| 304 | 417 | .arg => return self.genArg(inst.cast(ir.Inst.Arg).?), |
| 418 | .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?, arch), | |
| 419 | .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), | |
| 305 | 420 | .block => return self.genBlock(inst.cast(ir.Inst.Block).?, arch), |
| 421 | .br => return self.genBr(inst.cast(ir.Inst.Br).?, arch), | |
| 306 | 422 | .breakpoint => return self.genBreakpoint(inst.src, arch), |
| 423 | .brvoid => return self.genBrVoid(inst.cast(ir.Inst.BrVoid).?, arch), | |
| 307 | 424 | .call => return self.genCall(inst.cast(ir.Inst.Call).?, arch), |
| 308 | .unreach => return MCValue{ .unreach = {} }, | |
| 425 | .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?, arch), | |
| 426 | .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?, arch), | |
| 309 | 427 | .constant => unreachable, // excluded from function bodies |
| 310 | .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?, arch), | |
| 428 | .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?, arch), | |
| 429 | .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?, arch), | |
| 311 | 430 | .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?), |
| 312 | .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), | |
| 313 | 431 | .ret => return self.genRet(inst.cast(ir.Inst.Ret).?, arch), |
| 314 | 432 | .retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?, arch), |
| 315 | .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?, arch), | |
| 316 | .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?, arch), | |
| 317 | .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?, arch), | |
| 318 | .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?, arch), | |
| 433 | .sub => return self.genSub(inst.cast(ir.Inst.Sub).?, arch), | |
| 434 | .unreach => return MCValue{ .unreach = {} }, | |
| 319 | 435 | } |
| 320 | 436 | } |
| 321 | 437 | |
| 322 | 438 | fn genAdd(self: *Function, inst: *ir.Inst.Add, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| 323 | const lhs = try self.resolveInst(inst.args.lhs); | |
| 324 | const rhs = try self.resolveInst(inst.args.rhs); | |
| 439 | // No side effects, so if it's unreferenced, do nothing. | |
| 440 | if (inst.base.isUnused()) | |
| 441 | return MCValue.dead; | |
| 325 | 442 | switch (arch) { |
| 326 | .i386, .x86_64 => { | |
| 327 | // const lhs_reg = try self.instAsReg(lhs); | |
| 328 | // const rhs_reg = try self.instAsReg(rhs); | |
| 329 | // const result = try self.allocateReg(); | |
| 443 | .x86_64 => { | |
| 444 | return try self.genX8664BinMath(&inst.base, inst.args.lhs, inst.args.rhs, 0, 0x00); | |
| 445 | }, | |
| 446 | else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}), | |
| 447 | } | |
| 448 | } | |
| 330 | 449 | |
| 331 | // try self.code.append(??); | |
| 450 | fn genSub(self: *Function, inst: *ir.Inst.Sub, comptime arch: std.Target.Cpu.Arch) !MCValue { | |
| 451 | // No side effects, so if it's unreferenced, do nothing. | |
| 452 | if (inst.base.isUnused()) | |
| 453 | return MCValue.dead; | |
| 454 | switch (arch) { | |
| 455 | .x86_64 => { | |
| 456 | return try self.genX8664BinMath(&inst.base, inst.args.lhs, inst.args.rhs, 5, 0x28); | |
| 457 | }, | |
| 458 | else => return self.fail(inst.base.src, "TODO implement sub for {}", .{self.target.cpu.arch}), | |
| 459 | } | |
| 460 | } | |
| 461 | ||
| 462 | /// ADD, SUB | |
| 463 | fn genX8664BinMath(self: *Function, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, opx: u8, mr: u8) !MCValue { | |
| 464 | try self.code.ensureCapacity(self.code.items.len + 8); | |
| 465 | ||
| 466 | const lhs = try self.resolveInst(op_lhs); | |
| 467 | const rhs = try self.resolveInst(op_rhs); | |
| 332 | 468 | |
| 333 | // lhs_reg.release(); | |
| 334 | // rhs_reg.release(); | |
| 335 | return self.fail(inst.base.src, "TODO implement register allocation", .{}); | |
| 469 | // There are 2 operands, destination and source. | |
| 470 | // Either one, but not both, can be a memory operand. | |
| 471 | // Source operand can be an immediate, 8 bits or 32 bits. | |
| 472 | // So, if either one of the operands dies with this instruction, we can use it | |
| 473 | // as the result MCValue. | |
| 474 | var dst_mcv: MCValue = undefined; | |
| 475 | var src_mcv: MCValue = undefined; | |
| 476 | var src_inst: *ir.Inst = undefined; | |
| 477 | if (inst.operandDies(0) and lhs.isMutable()) { | |
| 478 | // LHS dies; use it as the destination. | |
| 479 | // Both operands cannot be memory. | |
| 480 | src_inst = op_rhs; | |
| 481 | if (lhs.isMemory() and rhs.isMemory()) { | |
| 482 | dst_mcv = try self.copyToNewRegister(op_lhs); | |
| 483 | src_mcv = rhs; | |
| 484 | } else { | |
| 485 | dst_mcv = lhs; | |
| 486 | src_mcv = rhs; | |
| 487 | } | |
| 488 | } else if (inst.operandDies(1) and rhs.isMutable()) { | |
| 489 | // RHS dies; use it as the destination. | |
| 490 | // Both operands cannot be memory. | |
| 491 | src_inst = op_lhs; | |
| 492 | if (lhs.isMemory() and rhs.isMemory()) { | |
| 493 | dst_mcv = try self.copyToNewRegister(op_rhs); | |
| 494 | src_mcv = lhs; | |
| 495 | } else { | |
| 496 | dst_mcv = rhs; | |
| 497 | src_mcv = lhs; | |
| 498 | } | |
| 499 | } else { | |
| 500 | if (lhs.isMemory()) { | |
| 501 | dst_mcv = try self.copyToNewRegister(op_lhs); | |
| 502 | src_mcv = rhs; | |
| 503 | src_inst = op_rhs; | |
| 504 | } else { | |
| 505 | dst_mcv = try self.copyToNewRegister(op_rhs); | |
| 506 | src_mcv = lhs; | |
| 507 | src_inst = op_lhs; | |
| 508 | } | |
| 509 | } | |
| 510 | // This instruction supports only signed 32-bit immediates at most. If the immediate | |
| 511 | // value is larger than this, we put it in a register. | |
| 512 | // A potential opportunity for future optimization here would be keeping track | |
| 513 | // of the fact that the instruction is available both as an immediate | |
| 514 | // and as a register. | |
| 515 | switch (src_mcv) { | |
| 516 | .immediate => |imm| { | |
| 517 | if (imm > std.math.maxInt(u31)) { | |
| 518 | src_mcv = try self.copyToNewRegister(src_inst); | |
| 519 | } | |
| 520 | }, | |
| 521 | else => {}, | |
| 522 | } | |
| 523 | ||
| 524 | try self.genX8664BinMathCode(inst.src, dst_mcv, src_mcv, opx, mr); | |
| 525 | ||
| 526 | return dst_mcv; | |
| 527 | } | |
| 528 | ||
| 529 | fn genX8664BinMathCode(self: *Function, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void { | |
| 530 | switch (dst_mcv) { | |
| 531 | .none => unreachable, | |
| 532 | .dead, .unreach, .immediate => unreachable, | |
| 533 | .compare_flags_unsigned => unreachable, | |
| 534 | .compare_flags_signed => unreachable, | |
| 535 | .register => |dst_reg_usize| { | |
| 536 | const dst_reg = @intToEnum(Reg(.x86_64), @intCast(u8, dst_reg_usize)); | |
| 537 | switch (src_mcv) { | |
| 538 | .none => unreachable, | |
| 539 | .dead, .unreach => unreachable, | |
| 540 | .register => |src_reg_usize| { | |
| 541 | const src_reg = @intToEnum(Reg(.x86_64), @intCast(u8, src_reg_usize)); | |
| 542 | self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); | |
| 543 | self.code.appendSliceAssumeCapacity(&[_]u8{ mr + 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) }); | |
| 544 | }, | |
| 545 | .immediate => |imm| { | |
| 546 | const imm32 = @intCast(u31, imm); // This case must be handled before calling genX8664BinMathCode. | |
| 547 | // 81 /opx id | |
| 548 | if (imm32 <= std.math.maxInt(u7)) { | |
| 549 | self.rex(.{ .b = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); | |
| 550 | self.code.appendSliceAssumeCapacity(&[_]u8{ | |
| 551 | 0x83, | |
| 552 | 0xC0 | (opx << 3) | @truncate(u3, dst_reg.id()), | |
| 553 | @intCast(u8, imm32), | |
| 554 | }); | |
| 555 | } else { | |
| 556 | self.rex(.{ .r = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); | |
| 557 | self.code.appendSliceAssumeCapacity(&[_]u8{ | |
| 558 | 0x81, | |
| 559 | 0xC0 | (opx << 3) | @truncate(u3, dst_reg.id()), | |
| 560 | }); | |
| 561 | std.mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), imm32); | |
| 562 | } | |
| 563 | }, | |
| 564 | .embedded_in_code, .memory, .stack_offset => { | |
| 565 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{}); | |
| 566 | }, | |
| 567 | .compare_flags_unsigned => { | |
| 568 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{}); | |
| 569 | }, | |
| 570 | .compare_flags_signed => { | |
| 571 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{}); | |
| 572 | }, | |
| 573 | } | |
| 574 | }, | |
| 575 | .embedded_in_code, .memory, .stack_offset => { | |
| 576 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP destination memory", .{}); | |
| 336 | 577 | }, |
| 337 | else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}), | |
| 338 | 578 | } |
| 339 | 579 | } |
| 340 | 580 | |
| ... | ... | @@ -410,17 +650,86 @@ const Function = struct { |
| 410 | 650 | } |
| 411 | 651 | |
| 412 | 652 | fn genCmp(self: *Function, inst: *ir.Inst.Cmp, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| 653 | // No side effects, so if it's unreferenced, do nothing. | |
| 654 | if (inst.base.isUnused()) | |
| 655 | return MCValue.dead; | |
| 413 | 656 | switch (arch) { |
| 657 | .x86_64 => { | |
| 658 | try self.code.ensureCapacity(self.code.items.len + 8); | |
| 659 | ||
| 660 | const lhs = try self.resolveInst(inst.args.lhs); | |
| 661 | const rhs = try self.resolveInst(inst.args.rhs); | |
| 662 | ||
| 663 | // There are 2 operands, destination and source. | |
| 664 | // Either one, but not both, can be a memory operand. | |
| 665 | // Source operand can be an immediate, 8 bits or 32 bits. | |
| 666 | const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory())) | |
| 667 | try self.copyToNewRegister(inst.args.lhs) | |
| 668 | else | |
| 669 | lhs; | |
| 670 | // This instruction supports only signed 32-bit immediates at most. | |
| 671 | const src_mcv = try self.limitImmediateType(inst.args.rhs, i32); | |
| 672 | ||
| 673 | try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38); | |
| 674 | const info = inst.args.lhs.ty.intInfo(self.target.*); | |
| 675 | if (info.signed) { | |
| 676 | return MCValue{.compare_flags_signed = inst.args.op}; | |
| 677 | } else { | |
| 678 | return MCValue{.compare_flags_unsigned = inst.args.op}; | |
| 679 | } | |
| 680 | }, | |
| 414 | 681 | else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}), |
| 415 | 682 | } |
| 416 | 683 | } |
| 417 | 684 | |
| 418 | 685 | fn genCondBr(self: *Function, inst: *ir.Inst.CondBr, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| 419 | 686 | switch (arch) { |
| 687 | .i386, .x86_64 => { | |
| 688 | try self.code.ensureCapacity(self.code.items.len + 6); | |
| 689 | ||
| 690 | const cond = try self.resolveInst(inst.args.condition); | |
| 691 | switch (cond) { | |
| 692 | .compare_flags_signed => |cmp_op| { | |
| 693 | // Here we map to the opposite opcode because the jump is to the false branch. | |
| 694 | const opcode: u8 = switch (cmp_op) { | |
| 695 | .gte => 0x8c, | |
| 696 | .gt => 0x8e, | |
| 697 | .neq => 0x84, | |
| 698 | .lt => 0x8d, | |
| 699 | .lte => 0x8f, | |
| 700 | .eq => 0x85, | |
| 701 | }; | |
| 702 | return self.genX86CondBr(inst, opcode, arch); | |
| 703 | }, | |
| 704 | .compare_flags_unsigned => |cmp_op| { | |
| 705 | // Here we map to the opposite opcode because the jump is to the false branch. | |
| 706 | const opcode: u8 = switch (cmp_op) { | |
| 707 | .gte => 0x82, | |
| 708 | .gt => 0x86, | |
| 709 | .neq => 0x84, | |
| 710 | .lt => 0x83, | |
| 711 | .lte => 0x87, | |
| 712 | .eq => 0x85, | |
| 713 | }; | |
| 714 | return self.genX86CondBr(inst, opcode, arch); | |
| 715 | }, | |
| 716 | else => return self.fail(inst.base.src, "TODO implement condbr {} when condition not already in the compare flags", .{self.target.cpu.arch}), | |
| 717 | } | |
| 718 | }, | |
| 420 | 719 | else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}), |
| 421 | 720 | } |
| 422 | 721 | } |
| 423 | 722 | |
| 723 | fn genX86CondBr(self: *Function, inst: *ir.Inst.CondBr, opcode: u8, comptime arch: std.Target.Cpu.Arch) !MCValue { | |
| 724 | self.code.appendSliceAssumeCapacity(&[_]u8{0x0f, opcode}); | |
| 725 | const reloc = Reloc{ .rel32 = self.code.items.len }; | |
| 726 | self.code.items.len += 4; | |
| 727 | try self.genBody(inst.args.true_body, arch); | |
| 728 | try self.performReloc(inst.base.src, reloc); | |
| 729 | try self.genBody(inst.args.false_body, arch); | |
| 730 | return MCValue.unreach; | |
| 731 | } | |
| 732 | ||
| 424 | 733 | fn genIsNull(self: *Function, inst: *ir.Inst.IsNull, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| 425 | 734 | switch (arch) { |
| 426 | 735 | else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.target.cpu.arch}), |
| ... | ... | @@ -435,29 +744,52 @@ const Function = struct { |
| 435 | 744 | } |
| 436 | 745 | } |
| 437 | 746 | |
| 438 | fn genRelativeFwdJump(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, amount: u32) !void { | |
| 439 | switch (arch) { | |
| 440 | .i386, .x86_64 => { | |
| 441 | // TODO x86 treats the operands as signed | |
| 442 | if (amount <= std.math.maxInt(u8)) { | |
| 443 | try self.code.resize(self.code.items.len + 2); | |
| 444 | self.code.items[self.code.items.len - 2] = 0xeb; | |
| 445 | self.code.items[self.code.items.len - 1] = @intCast(u8, amount); | |
| 446 | } else { | |
| 447 | try self.code.resize(self.code.items.len + 5); | |
| 448 | self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 | |
| 449 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | |
| 450 | mem.writeIntLittle(u32, imm_ptr, amount); | |
| 451 | } | |
| 747 | fn genBlock(self: *Function, inst: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { | |
| 748 | if (inst.base.ty.hasCodeGenBits()) { | |
| 749 | return self.fail(inst.base.src, "TODO codegen Block with non-void type", .{}); | |
| 750 | } | |
| 751 | // A block is nothing but a setup to be able to jump to the end. | |
| 752 | defer inst.codegen.relocs.deinit(self.gpa); | |
| 753 | try self.genBody(inst.args.body, arch); | |
| 754 | ||
| 755 | for (inst.codegen.relocs.items) |reloc| try self.performReloc(inst.base.src, reloc); | |
| 756 | ||
| 757 | return MCValue.none; | |
| 758 | } | |
| 759 | ||
| 760 | fn performReloc(self: *Function, src: usize, reloc: Reloc) !void { | |
| 761 | switch (reloc) { | |
| 762 | .rel32 => |pos| { | |
| 763 | const amt = self.code.items.len - (pos + 4); | |
| 764 | const s32_amt = std.math.cast(i32, amt) catch | |
| 765 | return self.fail(src, "unable to perform relocation: jump too far", .{}); | |
| 766 | mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt); | |
| 452 | 767 | }, |
| 453 | else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.target.cpu.arch}), | |
| 454 | 768 | } |
| 455 | 769 | } |
| 456 | 770 | |
| 457 | fn genBlock(self: *Function, inst: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { | |
| 771 | fn genBr(self: *Function, inst: *ir.Inst.Br, comptime arch: std.Target.Cpu.Arch) !MCValue { | |
| 458 | 772 | switch (arch) { |
| 459 | else => return self.fail(inst.base.src, "TODO implement codegen Block for {}", .{self.target.cpu.arch}), | |
| 773 | else => return self.fail(inst.base.src, "TODO implement br for {}", .{self.target.cpu.arch}), | |
| 774 | } | |
| 775 | } | |
| 776 | ||
| 777 | fn genBrVoid(self: *Function, inst: *ir.Inst.BrVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { | |
| 778 | // Emit a jump with a relocation. It will be patched up after the block ends. | |
| 779 | try inst.args.block.codegen.relocs.ensureCapacity(self.gpa, inst.args.block.codegen.relocs.items.len + 1); | |
| 780 | ||
| 781 | switch (arch) { | |
| 782 | .i386, .x86_64 => { | |
| 783 | // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction | |
| 784 | // which is available if the jump is 127 bytes or less forward. | |
| 785 | try self.code.resize(self.code.items.len + 5); | |
| 786 | self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 | |
| 787 | // Leave the jump offset undefined | |
| 788 | inst.args.block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); | |
| 789 | }, | |
| 790 | else => return self.fail(inst.base.src, "TODO implement brvoid for {}", .{self.target.cpu.arch}), | |
| 460 | 791 | } |
| 792 | return .none; | |
| 461 | 793 | } |
| 462 | 794 | |
| 463 | 795 | fn genAsm(self: *Function, inst: *ir.Inst.Assembly, comptime arch: Target.Cpu.Arch) !MCValue { |
| ... | ... | @@ -502,30 +834,38 @@ const Function = struct { |
| 502 | 834 | /// resulting REX is meaningful, but will remain the same if it is not. |
| 503 | 835 | /// * Deliberately inserting a "meaningless REX" requires explicit usage of |
| 504 | 836 | /// 0x40, and cannot be done via this function. |
| 505 | fn REX(self: *Function, arg: struct { B: bool = false, W: bool = false, X: bool = false, R: bool = false }) !void { | |
| 837 | fn rex(self: *Function, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void { | |
| 506 | 838 | // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. |
| 507 | 839 | var value: u8 = 0x40; |
| 508 | if (arg.B) { | |
| 840 | if (arg.b) { | |
| 509 | 841 | value |= 0x1; |
| 510 | 842 | } |
| 511 | if (arg.X) { | |
| 843 | if (arg.x) { | |
| 512 | 844 | value |= 0x2; |
| 513 | 845 | } |
| 514 | if (arg.R) { | |
| 846 | if (arg.r) { | |
| 515 | 847 | value |= 0x4; |
| 516 | 848 | } |
| 517 | if (arg.W) { | |
| 849 | if (arg.w) { | |
| 518 | 850 | value |= 0x8; |
| 519 | 851 | } |
| 520 | 852 | if (value != 0x40) { |
| 521 | try self.code.append(value); | |
| 853 | self.code.appendAssumeCapacity(value); | |
| 522 | 854 | } |
| 523 | 855 | } |
| 524 | 856 | |
| 525 | 857 | fn genSetReg(self: *Function, src: usize, comptime arch: Target.Cpu.Arch, reg: Reg(arch), mcv: MCValue) error{ CodegenFail, OutOfMemory }!void { |
| 526 | 858 | switch (arch) { |
| 527 | 859 | .x86_64 => switch (mcv) { |
| 528 | .none, .unreach => unreachable, | |
| 860 | .dead => unreachable, | |
| 861 | .none => unreachable, | |
| 862 | .unreach => unreachable, | |
| 863 | .compare_flags_unsigned => |op| { | |
| 864 | return self.fail(src, "TODO set register with compare flags value (unsigned)", .{}); | |
| 865 | }, | |
| 866 | .compare_flags_signed => |op| { | |
| 867 | return self.fail(src, "TODO set register with compare flags value (signed)", .{}); | |
| 868 | }, | |
| 529 | 869 | .immediate => |x| { |
| 530 | 870 | if (reg.size() != 64) { |
| 531 | 871 | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); |
| ... | ... | @@ -544,11 +884,11 @@ const Function = struct { |
| 544 | 884 | // If we're accessing e.g. r8d, we need to use a REX prefix before the actual operation. Since |
| 545 | 885 | // this is a 32-bit operation, the W flag is set to zero. X is also zero, as we're not using a SIB. |
| 546 | 886 | // Both R and B are set, as we're extending, in effect, the register bits *and* the operand. |
| 547 | try self.REX(.{ .R = reg.isExtended(), .B = reg.isExtended() }); | |
| 887 | try self.code.ensureCapacity(self.code.items.len + 3); | |
| 888 | self.rex(.{ .r = reg.isExtended(), .b = reg.isExtended() }); | |
| 548 | 889 | const id = @as(u8, reg.id() & 0b111); |
| 549 | return self.code.appendSlice(&[_]u8{ | |
| 550 | 0x31, 0xC0 | id << 3 | id, | |
| 551 | }); | |
| 890 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x31, 0xC0 | id << 3 | id }); | |
| 891 | return; | |
| 552 | 892 | } |
| 553 | 893 | if (x <= std.math.maxInt(u32)) { |
| 554 | 894 | // Next best case: if we set the lower four bytes, the upper four will be zeroed. |
| ... | ... | @@ -581,9 +921,9 @@ const Function = struct { |
| 581 | 921 | // Since we always need a REX here, let's just check if we also need to set REX.B. |
| 582 | 922 | // |
| 583 | 923 | // In this case, the encoding of the REX byte is 0b0100100B |
| 584 | ||
| 585 | try self.REX(.{ .W = true, .B = reg.isExtended() }); | |
| 586 | try self.code.resize(self.code.items.len + 9); | |
| 924 | try self.code.ensureCapacity(self.code.items.len + 10); | |
| 925 | self.rex(.{ .w = true, .b = reg.isExtended() }); | |
| 926 | self.code.items.len += 9; | |
| 587 | 927 | self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111); |
| 588 | 928 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; |
| 589 | 929 | mem.writeIntLittle(u64, imm_ptr, x); |
| ... | ... | @@ -594,13 +934,13 @@ const Function = struct { |
| 594 | 934 | } |
| 595 | 935 | // We need the offset from RIP in a signed i32 twos complement. |
| 596 | 936 | // The instruction is 7 bytes long and RIP points to the next instruction. |
| 597 | // | |
| 937 | try self.code.ensureCapacity(self.code.items.len + 7); | |
| 598 | 938 | // 64-bit LEA is encoded as REX.W 8D /r. If the register is extended, the REX byte is modified, |
| 599 | 939 | // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three |
| 600 | 940 | // bits as five. |
| 601 | 941 | // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id. |
| 602 | try self.REX(.{ .W = true, .B = reg.isExtended() }); | |
| 603 | try self.code.resize(self.code.items.len + 6); | |
| 942 | self.rex(.{ .w = true, .b = reg.isExtended() }); | |
| 943 | self.code.items.len += 6; | |
| 604 | 944 | const rip = self.code.items.len; |
| 605 | 945 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); |
| 606 | 946 | const offset = @intCast(i32, big_offset); |
| ... | ... | @@ -620,9 +960,10 @@ const Function = struct { |
| 620 | 960 | // If the *source* is extended, the B field must be 1. |
| 621 | 961 | // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle |
| 622 | 962 | // three bits) contain the destination, and the R/M field (the lower three bits) contain the source. |
| 623 | try self.REX(.{ .W = true, .R = reg.isExtended(), .B = src_reg.isExtended() }); | |
| 963 | try self.code.ensureCapacity(self.code.items.len + 3); | |
| 964 | self.rex(.{ .w = true, .r = reg.isExtended(), .b = src_reg.isExtended() }); | |
| 624 | 965 | const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111); |
| 625 | try self.code.appendSlice(&[_]u8{ 0x8B, R }); | |
| 966 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R }); | |
| 626 | 967 | }, |
| 627 | 968 | .memory => |x| { |
| 628 | 969 | if (reg.size() != 64) { |
| ... | ... | @@ -636,14 +977,14 @@ const Function = struct { |
| 636 | 977 | // The SIB must be 0x25, to indicate a disp32 with no scaled index. |
| 637 | 978 | // 0b00RRR100, where RRR is the lower three bits of the register ID. |
| 638 | 979 | // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32. |
| 639 | try self.REX(.{ .W = true, .B = reg.isExtended() }); | |
| 640 | try self.code.resize(self.code.items.len + 7); | |
| 641 | const r = 0x04 | (@as(u8, reg.id() & 0b111) << 3); | |
| 642 | self.code.items[self.code.items.len - 7] = 0x8B; | |
| 643 | self.code.items[self.code.items.len - 6] = r; | |
| 644 | self.code.items[self.code.items.len - 5] = 0x25; | |
| 645 | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; | |
| 646 | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); | |
| 980 | try self.code.ensureCapacity(self.code.items.len + 8); | |
| 981 | self.rex(.{ .w = true, .b = reg.isExtended() }); | |
| 982 | self.code.appendSliceAssumeCapacity(&[_]u8{ | |
| 983 | 0x8B, | |
| 984 | 0x04 | (@as(u8, reg.id() & 0b111) << 3), // R | |
| 985 | 0x25, | |
| 986 | }); | |
| 987 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), @intCast(u32, x)); | |
| 647 | 988 | } else { |
| 648 | 989 | // If this is RAX, we can use a direct load; otherwise, we need to load the address, then indirectly load |
| 649 | 990 | // the value. |
| ... | ... | @@ -674,15 +1015,15 @@ const Function = struct { |
| 674 | 1015 | // Currently, we're only allowing 64-bit registers, so we need the `REX.W 8B /r` variant. |
| 675 | 1016 | // TODO: determine whether to allow other sized registers, and if so, handle them properly. |
| 676 | 1017 | // This operation requires three bytes: REX 0x8B R/M |
| 677 | // | |
| 1018 | try self.code.ensureCapacity(self.code.items.len + 3); | |
| 678 | 1019 | // For this operation, we want R/M mode *zero* (use register indirectly), and the two register |
| 679 | 1020 | // values must match. Thus, it's 00ABCABC where ABC is the lower three bits of the register ID. |
| 680 | 1021 | // |
| 681 | 1022 | // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both* |
| 682 | 1023 | // register operands need to be marked as extended. |
| 683 | try self.REX(.{ .W = true, .B = reg.isExtended(), .R = reg.isExtended() }); | |
| 1024 | self.rex(.{ .w = true, .b = reg.isExtended(), .r = reg.isExtended() }); | |
| 684 | 1025 | const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); |
| 685 | try self.code.appendSlice(&[_]u8{ 0x8B, RM }); | |
| 1026 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM }); | |
| 686 | 1027 | } |
| 687 | 1028 | } |
| 688 | 1029 | }, |
| ... | ... | @@ -705,22 +1046,58 @@ const Function = struct { |
| 705 | 1046 | } |
| 706 | 1047 | |
| 707 | 1048 | fn resolveInst(self: *Function, inst: *ir.Inst) !MCValue { |
| 708 | if (self.inst_table.get(inst)) |mcv| { | |
| 709 | return mcv; | |
| 710 | } | |
| 1049 | // Constants have static lifetimes, so they are always memoized in the outer most table. | |
| 711 | 1050 | if (inst.cast(ir.Inst.Constant)) |const_inst| { |
| 712 | const mcvalue = try self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); | |
| 713 | try self.inst_table.putNoClobber(inst, mcvalue); | |
| 714 | return mcvalue; | |
| 715 | } else { | |
| 716 | return self.inst_table.get(inst).?; | |
| 1051 | const branch = &self.branch_stack.items[0]; | |
| 1052 | const gop = try branch.inst_table.getOrPut(self.gpa, inst); | |
| 1053 | if (!gop.found_existing) { | |
| 1054 | gop.entry.value = try self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); | |
| 1055 | } | |
| 1056 | return gop.entry.value; | |
| 1057 | } | |
| 1058 | ||
| 1059 | // Treat each stack item as a "layer" on top of the previous one. | |
| 1060 | var i: usize = self.branch_stack.items.len; | |
| 1061 | while (true) { | |
| 1062 | i -= 1; | |
| 1063 | if (self.branch_stack.items[i].inst_table.get(inst)) |mcv| { | |
| 1064 | return mcv; | |
| 1065 | } | |
| 717 | 1066 | } |
| 718 | 1067 | } |
| 719 | 1068 | |
| 1069 | fn copyToNewRegister(self: *Function, inst: *ir.Inst) !MCValue { | |
| 1070 | return self.fail(inst.src, "TODO implement copyToNewRegister", .{}); | |
| 1071 | } | |
| 1072 | ||
| 1073 | /// If the MCValue is an immediate, and it does not fit within this type, | |
| 1074 | /// we put it in a register. | |
| 1075 | /// A potential opportunity for future optimization here would be keeping track | |
| 1076 | /// of the fact that the instruction is available both as an immediate | |
| 1077 | /// and as a register. | |
| 1078 | fn limitImmediateType(self: *Function, inst: *ir.Inst, comptime T: type) !MCValue { | |
| 1079 | const mcv = try self.resolveInst(inst); | |
| 1080 | const ti = @typeInfo(T).Int; | |
| 1081 | switch (mcv) { | |
| 1082 | .immediate => |imm| { | |
| 1083 | // This immediate is unsigned. | |
| 1084 | const U = @Type(.{ .Int = .{ | |
| 1085 | .bits = ti.bits - @boolToInt(ti.is_signed), | |
| 1086 | .is_signed = false, | |
| 1087 | }}); | |
| 1088 | if (imm >= std.math.maxInt(U)) { | |
| 1089 | return self.copyToNewRegister(inst); | |
| 1090 | } | |
| 1091 | }, | |
| 1092 | else => {}, | |
| 1093 | } | |
| 1094 | return mcv; | |
| 1095 | } | |
| 1096 | ||
| 1097 | ||
| 720 | 1098 | fn genTypedValue(self: *Function, src: usize, typed_value: TypedValue) !MCValue { |
| 721 | 1099 | const ptr_bits = self.target.cpu.arch.ptrBitWidth(); |
| 722 | 1100 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 723 | const allocator = self.code.allocator; | |
| 724 | 1101 | switch (typed_value.ty.zigTypeTag()) { |
| 725 | 1102 | .Pointer => { |
| 726 | 1103 | if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| { |
| ... | ... | @@ -747,7 +1124,7 @@ const Function = struct { |
| 747 | 1124 | fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } { |
| 748 | 1125 | @setCold(true); |
| 749 | 1126 | assert(self.err_msg == null); |
| 750 | self.err_msg = try ErrorMsg.create(self.code.allocator, src, format, args); | |
| 1127 | self.err_msg = try ErrorMsg.create(self.bin_file.allocator, src, format, args); | |
| 751 | 1128 | return error.CodegenFail; |
| 752 | 1129 | } |
| 753 | 1130 | }; |
src-self-hosted/ir.zig+68-1| ... | ... | @@ -2,6 +2,8 @@ const std = @import("std"); |
| 2 | 2 | const Value = @import("value.zig").Value; |
| 3 | 3 | const Type = @import("type.zig").Type; |
| 4 | 4 | const Module = @import("Module.zig"); |
| 5 | const assert = std.debug.assert; | |
| 6 | const codegen = @import("codegen.zig"); | |
| 5 | 7 | |
| 6 | 8 | /// These are in-memory, analyzed instructions. See `zir.Inst` for the representation |
| 7 | 9 | /// of instructions that correspond to the ZIR text format. |
| ... | ... | @@ -10,17 +12,43 @@ const Module = @import("Module.zig"); |
| 10 | 12 | /// a memory location for the value to survive after a const instruction. |
| 11 | 13 | pub const Inst = struct { |
| 12 | 14 | tag: Tag, |
| 15 | /// Each bit represents the index of an `Inst` parameter in the `args` field. | |
| 16 | /// If a bit is set, it marks the end of the lifetime of the corresponding | |
| 17 | /// instruction parameter. For example, 0b000_00101 means that the first and | |
| 18 | /// third `Inst` parameters' lifetimes end after this instruction, and will | |
| 19 | /// not have any more following references. | |
| 20 | /// The most significant bit being set means that the instruction itself is | |
| 21 | /// never referenced, in other words its lifetime ends as soon as it finishes. | |
| 22 | /// If bit 7 (0b1xxx_xxxx) is set, it means this instruction itself is unreferenced. | |
| 23 | /// If bit 6 (0bx1xx_xxxx) is set, it means this is a special case and the | |
| 24 | /// lifetimes of operands are encoded elsewhere. | |
| 25 | deaths: u8 = undefined, | |
| 13 | 26 | ty: Type, |
| 14 | 27 | /// Byte offset into the source. |
| 15 | 28 | src: usize, |
| 16 | 29 | |
| 30 | pub fn isUnused(self: Inst) bool { | |
| 31 | return (self.deaths & 0b1000_0000) != 0; | |
| 32 | } | |
| 33 | ||
| 34 | pub fn operandDies(self: Inst, index: u3) bool { | |
| 35 | assert(index < 6); | |
| 36 | return @truncate(u1, self.deaths << index) != 0; | |
| 37 | } | |
| 38 | ||
| 39 | pub fn specialOperandDeaths(self: Inst) bool { | |
| 40 | return (self.deaths & 0b1000_0000) != 0; | |
| 41 | } | |
| 42 | ||
| 17 | 43 | pub const Tag = enum { |
| 18 | 44 | add, |
| 19 | 45 | arg, |
| 20 | 46 | assembly, |
| 21 | 47 | bitcast, |
| 22 | 48 | block, |
| 49 | br, | |
| 23 | 50 | breakpoint, |
| 51 | brvoid, | |
| 24 | 52 | call, |
| 25 | 53 | cmp, |
| 26 | 54 | condbr, |
| ... | ... | @@ -30,6 +58,7 @@ pub const Inst = struct { |
| 30 | 58 | ptrtoint, |
| 31 | 59 | ret, |
| 32 | 60 | retvoid, |
| 61 | sub, | |
| 33 | 62 | unreach, |
| 34 | 63 | |
| 35 | 64 | /// Returns whether the instruction is one of the control flow "noreturn" types. |
| ... | ... | @@ -43,14 +72,17 @@ pub const Inst = struct { |
| 43 | 72 | .bitcast, |
| 44 | 73 | .block, |
| 45 | 74 | .breakpoint, |
| 75 | .call, | |
| 46 | 76 | .cmp, |
| 47 | 77 | .constant, |
| 48 | 78 | .isnonnull, |
| 49 | 79 | .isnull, |
| 50 | 80 | .ptrtoint, |
| 51 | .call, | |
| 81 | .sub, | |
| 52 | 82 | => false, |
| 53 | 83 | |
| 84 | .br, | |
| 85 | .brvoid, | |
| 54 | 86 | .condbr, |
| 55 | 87 | .ret, |
| 56 | 88 | .retvoid, |
| ... | ... | @@ -128,6 +160,17 @@ pub const Inst = struct { |
| 128 | 160 | args: struct { |
| 129 | 161 | body: Body, |
| 130 | 162 | }, |
| 163 | /// This memory is reserved for codegen code to do whatever it needs to here. | |
| 164 | codegen: codegen.BlockData = .{}, | |
| 165 | }; | |
| 166 | ||
| 167 | pub const Br = struct { | |
| 168 | pub const base_tag = Tag.br; | |
| 169 | base: Inst, | |
| 170 | args: struct { | |
| 171 | block: *Block, | |
| 172 | operand: *Inst, | |
| 173 | }, | |
| 131 | 174 | }; |
| 132 | 175 | |
| 133 | 176 | pub const Breakpoint = struct { |
| ... | ... | @@ -136,6 +179,14 @@ pub const Inst = struct { |
| 136 | 179 | args: void, |
| 137 | 180 | }; |
| 138 | 181 | |
| 182 | pub const BrVoid = struct { | |
| 183 | pub const base_tag = Tag.brvoid; | |
| 184 | base: Inst, | |
| 185 | args: struct { | |
| 186 | block: *Block, | |
| 187 | }, | |
| 188 | }; | |
| 189 | ||
| 139 | 190 | pub const Call = struct { |
| 140 | 191 | pub const base_tag = Tag.call; |
| 141 | 192 | base: Inst, |
| ... | ... | @@ -165,6 +216,12 @@ pub const Inst = struct { |
| 165 | 216 | true_body: Body, |
| 166 | 217 | false_body: Body, |
| 167 | 218 | }, |
| 219 | /// Set of instructions whose lifetimes end at the start of one of the branches. | |
| 220 | /// The `true` branch is first: `deaths[0..true_death_count]`. | |
| 221 | /// The `false` branch is next: `(deaths + true_death_count)[..false_death_count]`. | |
| 222 | deaths: [*]*Inst = undefined, | |
| 223 | true_death_count: u32 = 0, | |
| 224 | false_death_count: u32 = 0, | |
| 168 | 225 | }; |
| 169 | 226 | |
| 170 | 227 | pub const Constant = struct { |
| ... | ... | @@ -215,6 +272,16 @@ pub const Inst = struct { |
| 215 | 272 | args: void, |
| 216 | 273 | }; |
| 217 | 274 | |
| 275 | pub const Sub = struct { | |
| 276 | pub const base_tag = Tag.sub; | |
| 277 | base: Inst, | |
| 278 | ||
| 279 | args: struct { | |
| 280 | lhs: *Inst, | |
| 281 | rhs: *Inst, | |
| 282 | }, | |
| 283 | }; | |
| 284 | ||
| 218 | 285 | pub const Unreach = struct { |
| 219 | 286 | pub const base_tag = Tag.unreach; |
| 220 | 287 | base: Inst, |
src-self-hosted/link.zig+29-15| ... | ... | @@ -206,6 +206,19 @@ pub const File = struct { |
| 206 | 206 | }; |
| 207 | 207 | } |
| 208 | 208 | |
| 209 | /// Must be called only after a successful call to `updateDecl`. | |
| 210 | pub fn updateDeclExports( | |
| 211 | base: *File, | |
| 212 | module: *Module, | |
| 213 | decl: *const Module.Decl, | |
| 214 | exports: []const *Module.Export, | |
| 215 | ) !void { | |
| 216 | switch (base.tag) { | |
| 217 | .Elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), | |
| 218 | .C => return {}, | |
| 219 | } | |
| 220 | } | |
| 221 | ||
| 209 | 222 | pub const Tag = enum { |
| 210 | 223 | Elf, |
| 211 | 224 | C, |
| ... | ... | @@ -248,7 +261,7 @@ pub const File = struct { |
| 248 | 261 | pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { |
| 249 | 262 | cgen.generate(self, decl) catch |err| { |
| 250 | 263 | if (err == error.CGenFailure) { |
| 251 | try module.failed_decls.put(decl, self.error_msg); | |
| 264 | try module.failed_decls.put(module.gpa, decl, self.error_msg); | |
| 252 | 265 | } |
| 253 | 266 | return err; |
| 254 | 267 | }; |
| ... | ... | @@ -566,7 +579,7 @@ pub const File = struct { |
| 566 | 579 | const file_size = self.options.program_code_size_hint; |
| 567 | 580 | const p_align = 0x1000; |
| 568 | 581 | const off = self.findFreeSpace(file_size, p_align); |
| 569 | //std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 582 | std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 570 | 583 | try self.program_headers.append(self.allocator, .{ |
| 571 | 584 | .p_type = elf.PT_LOAD, |
| 572 | 585 | .p_offset = off, |
| ... | ... | @@ -587,7 +600,7 @@ pub const File = struct { |
| 587 | 600 | // page align. |
| 588 | 601 | const p_align = 0x1000; |
| 589 | 602 | const off = self.findFreeSpace(file_size, p_align); |
| 590 | //std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 603 | std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 591 | 604 | // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. |
| 592 | 605 | // we'll need to re-use that function anyway, in case the GOT grows and overlaps something |
| 593 | 606 | // else in virtual memory. |
| ... | ... | @@ -609,7 +622,7 @@ pub const File = struct { |
| 609 | 622 | assert(self.shstrtab.items.len == 0); |
| 610 | 623 | try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0 |
| 611 | 624 | const off = self.findFreeSpace(self.shstrtab.items.len, 1); |
| 612 | //std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); | |
| 625 | std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); | |
| 613 | 626 | try self.sections.append(self.allocator, .{ |
| 614 | 627 | .sh_name = try self.makeString(".shstrtab"), |
| 615 | 628 | .sh_type = elf.SHT_STRTAB, |
| ... | ... | @@ -667,7 +680,7 @@ pub const File = struct { |
| 667 | 680 | const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); |
| 668 | 681 | const file_size = self.options.symbol_count_hint * each_size; |
| 669 | 682 | const off = self.findFreeSpace(file_size, min_align); |
| 670 | //std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 683 | std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 671 | 684 | |
| 672 | 685 | try self.sections.append(self.allocator, .{ |
| 673 | 686 | .sh_name = try self.makeString(".symtab"), |
| ... | ... | @@ -783,7 +796,7 @@ pub const File = struct { |
| 783 | 796 | shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); |
| 784 | 797 | } |
| 785 | 798 | shstrtab_sect.sh_size = needed_size; |
| 786 | //std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); | |
| 799 | std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); | |
| 787 | 800 | |
| 788 | 801 | try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); |
| 789 | 802 | if (!self.shdr_table_dirty) { |
| ... | ... | @@ -829,7 +842,7 @@ pub const File = struct { |
| 829 | 842 | |
| 830 | 843 | for (buf) |*shdr, i| { |
| 831 | 844 | shdr.* = self.sections.items[i]; |
| 832 | //std.log.debug(.link, "writing section {}\n", .{shdr.*}); | |
| 845 | std.log.debug(.link, "writing section {}\n", .{shdr.*}); | |
| 833 | 846 | if (foreign_endian) { |
| 834 | 847 | bswapAllFields(elf.Elf64_Shdr, shdr); |
| 835 | 848 | } |
| ... | ... | @@ -840,6 +853,7 @@ pub const File = struct { |
| 840 | 853 | self.shdr_table_dirty = false; |
| 841 | 854 | } |
| 842 | 855 | if (self.entry_addr == null and self.options.output_mode == .Exe) { |
| 856 | std.log.debug(.link, "no_entry_point_found = true\n", .{}); | |
| 843 | 857 | self.error_flags.no_entry_point_found = true; |
| 844 | 858 | } else { |
| 845 | 859 | self.error_flags.no_entry_point_found = false; |
| ... | ... | @@ -1153,10 +1167,10 @@ pub const File = struct { |
| 1153 | 1167 | try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); |
| 1154 | 1168 | |
| 1155 | 1169 | if (self.local_symbol_free_list.popOrNull()) |i| { |
| 1156 | //std.log.debug(.link, "reusing symbol index {} for {}\n", .{i, decl.name}); | |
| 1170 | std.log.debug(.link, "reusing symbol index {} for {}\n", .{i, decl.name}); | |
| 1157 | 1171 | decl.link.local_sym_index = i; |
| 1158 | 1172 | } else { |
| 1159 | //std.log.debug(.link, "allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); | |
| 1173 | std.log.debug(.link, "allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); | |
| 1160 | 1174 | decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); |
| 1161 | 1175 | _ = self.local_symbols.addOneAssumeCapacity(); |
| 1162 | 1176 | } |
| ... | ... | @@ -1204,7 +1218,7 @@ pub const File = struct { |
| 1204 | 1218 | .appended => code_buffer.items, |
| 1205 | 1219 | .fail => |em| { |
| 1206 | 1220 | decl.analysis = .codegen_failure; |
| 1207 | try module.failed_decls.put(decl, em); | |
| 1221 | try module.failed_decls.put(module.gpa, decl, em); | |
| 1208 | 1222 | return; |
| 1209 | 1223 | }, |
| 1210 | 1224 | }; |
| ... | ... | @@ -1224,11 +1238,11 @@ pub const File = struct { |
| 1224 | 1238 | !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); |
| 1225 | 1239 | if (need_realloc) { |
| 1226 | 1240 | const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); |
| 1227 | //std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); | |
| 1241 | std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); | |
| 1228 | 1242 | if (vaddr != local_sym.st_value) { |
| 1229 | 1243 | local_sym.st_value = vaddr; |
| 1230 | 1244 | |
| 1231 | //std.log.debug(.link, " (writing new offset table entry)\n", .{}); | |
| 1245 | std.log.debug(.link, " (writing new offset table entry)\n", .{}); | |
| 1232 | 1246 | self.offset_table.items[decl.link.offset_table_index] = vaddr; |
| 1233 | 1247 | try self.writeOffsetTableEntry(decl.link.offset_table_index); |
| 1234 | 1248 | } |
| ... | ... | @@ -1246,7 +1260,7 @@ pub const File = struct { |
| 1246 | 1260 | const decl_name = mem.spanZ(decl.name); |
| 1247 | 1261 | const name_str_index = try self.makeString(decl_name); |
| 1248 | 1262 | const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); |
| 1249 | //std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); | |
| 1263 | std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); | |
| 1250 | 1264 | errdefer self.freeTextBlock(&decl.link); |
| 1251 | 1265 | |
| 1252 | 1266 | local_sym.* = .{ |
| ... | ... | @@ -1290,7 +1304,7 @@ pub const File = struct { |
| 1290 | 1304 | for (exports) |exp| { |
| 1291 | 1305 | if (exp.options.section) |section_name| { |
| 1292 | 1306 | if (!mem.eql(u8, section_name, ".text")) { |
| 1293 | try module.failed_exports.ensureCapacity(module.failed_exports.items().len + 1); | |
| 1307 | try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); | |
| 1294 | 1308 | module.failed_exports.putAssumeCapacityNoClobber( |
| 1295 | 1309 | exp, |
| 1296 | 1310 | try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}), |
| ... | ... | @@ -1308,7 +1322,7 @@ pub const File = struct { |
| 1308 | 1322 | }, |
| 1309 | 1323 | .Weak => elf.STB_WEAK, |
| 1310 | 1324 | .LinkOnce => { |
| 1311 | try module.failed_exports.ensureCapacity(module.failed_exports.items().len + 1); | |
| 1325 | try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); | |
| 1312 | 1326 | module.failed_exports.putAssumeCapacityNoClobber( |
| 1313 | 1327 | exp, |
| 1314 | 1328 | try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), |
src-self-hosted/liveness.zig created+139| ... | ... | @@ -0,0 +1,139 @@ |
| 1 | const std = @import("std"); | |
| 2 | const ir = @import("ir.zig"); | |
| 3 | const trace = @import("tracy.zig").trace; | |
| 4 | ||
| 5 | /// Perform Liveness Analysis over the `Body`. Each `Inst` will have its `deaths` field populated. | |
| 6 | pub fn analyze( | |
| 7 | /// Used for temporary storage during the analysis. | |
| 8 | gpa: *std.mem.Allocator, | |
| 9 | /// Used to tack on extra allocations in the same lifetime as the existing instructions. | |
| 10 | arena: *std.mem.Allocator, | |
| 11 | body: ir.Body, | |
| 12 | ) error{OutOfMemory}!void { | |
| 13 | const tracy = trace(@src()); | |
| 14 | defer tracy.end(); | |
| 15 | ||
| 16 | var table = std.AutoHashMap(*ir.Inst, void).init(gpa); | |
| 17 | defer table.deinit(); | |
| 18 | try table.ensureCapacity(body.instructions.len); | |
| 19 | try analyzeWithTable(arena, &table, body); | |
| 20 | } | |
| 21 | ||
| 22 | fn analyzeWithTable(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), body: ir.Body) error{OutOfMemory}!void { | |
| 23 | var i: usize = body.instructions.len; | |
| 24 | ||
| 25 | while (i != 0) { | |
| 26 | i -= 1; | |
| 27 | const base = body.instructions[i]; | |
| 28 | try analyzeInstGeneric(arena, table, base); | |
| 29 | } | |
| 30 | } | |
| 31 | ||
| 32 | fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), base: *ir.Inst) error{OutOfMemory}!void { | |
| 33 | // Obtain the corresponding instruction type based on the tag type. | |
| 34 | inline for (std.meta.declarations(ir.Inst)) |decl| { | |
| 35 | switch (decl.data) { | |
| 36 | .Type => |T| { | |
| 37 | if (@hasDecl(T, "base_tag")) { | |
| 38 | if (T.base_tag == base.tag) { | |
| 39 | return analyzeInst(arena, table, T, @fieldParentPtr(T, "base", base)); | |
| 40 | } | |
| 41 | } | |
| 42 | }, | |
| 43 | else => {}, | |
| 44 | } | |
| 45 | } | |
| 46 | unreachable; | |
| 47 | } | |
| 48 | ||
| 49 | fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), comptime T: type, inst: *T) error{OutOfMemory}!void { | |
| 50 | inst.base.deaths = 0; | |
| 51 | ||
| 52 | switch (T) { | |
| 53 | ir.Inst.Constant => return, | |
| 54 | ir.Inst.Block => { | |
| 55 | try analyzeWithTable(arena, table, inst.args.body); | |
| 56 | // We let this continue so that it can possibly mark the block as | |
| 57 | // unreferenced below. | |
| 58 | }, | |
| 59 | ir.Inst.CondBr => { | |
| 60 | var true_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); | |
| 61 | defer true_table.deinit(); | |
| 62 | try true_table.ensureCapacity(inst.args.true_body.instructions.len); | |
| 63 | try analyzeWithTable(arena, &true_table, inst.args.true_body); | |
| 64 | ||
| 65 | var false_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); | |
| 66 | defer false_table.deinit(); | |
| 67 | try false_table.ensureCapacity(inst.args.false_body.instructions.len); | |
| 68 | try analyzeWithTable(arena, &false_table, inst.args.false_body); | |
| 69 | ||
| 70 | // Each death that occurs inside one branch, but not the other, needs | |
| 71 | // to be added as a death immediately upon entering the other branch. | |
| 72 | // During the iteration of the table, we additionally propagate the | |
| 73 | // deaths to the parent table. | |
| 74 | var true_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); | |
| 75 | defer true_entry_deaths.deinit(); | |
| 76 | var false_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); | |
| 77 | defer false_entry_deaths.deinit(); | |
| 78 | { | |
| 79 | var it = false_table.iterator(); | |
| 80 | while (it.next()) |entry| { | |
| 81 | const false_death = entry.key; | |
| 82 | if (!true_table.contains(false_death)) { | |
| 83 | try true_entry_deaths.append(false_death); | |
| 84 | // Here we are only adding to the parent table if the following iteration | |
| 85 | // would miss it. | |
| 86 | try table.putNoClobber(false_death, {}); | |
| 87 | } | |
| 88 | } | |
| 89 | } | |
| 90 | { | |
| 91 | var it = true_table.iterator(); | |
| 92 | while (it.next()) |entry| { | |
| 93 | const true_death = entry.key; | |
| 94 | try table.putNoClobber(true_death, {}); | |
| 95 | if (!false_table.contains(true_death)) { | |
| 96 | try false_entry_deaths.append(true_death); | |
| 97 | } | |
| 98 | } | |
| 99 | } | |
| 100 | inst.true_death_count = std.math.cast(@TypeOf(inst.true_death_count), true_entry_deaths.items.len) catch return error.OutOfMemory; | |
| 101 | inst.false_death_count = std.math.cast(@TypeOf(inst.false_death_count), false_entry_deaths.items.len) catch return error.OutOfMemory; | |
| 102 | const allocated_slice = try arena.alloc(*ir.Inst, true_entry_deaths.items.len + false_entry_deaths.items.len); | |
| 103 | inst.deaths = allocated_slice.ptr; | |
| 104 | ||
| 105 | // Continue on with the instruction analysis. The following code will find the condition | |
| 106 | // instruction, and the deaths flag for the CondBr instruction will indicate whether the | |
| 107 | // condition's lifetime ends immediately before entering any branch. | |
| 108 | }, | |
| 109 | else => {}, | |
| 110 | } | |
| 111 | ||
| 112 | if (!table.contains(&inst.base)) { | |
| 113 | // No tombstone for this instruction means it is never referenced, | |
| 114 | // and its birth marks its own death. Very metal 🤘 | |
| 115 | inst.base.deaths |= 1 << 7; | |
| 116 | } | |
| 117 | ||
| 118 | const Args = ir.Inst.Args(T); | |
| 119 | if (Args == void) { | |
| 120 | return; | |
| 121 | } | |
| 122 | ||
| 123 | comptime var arg_index: usize = 0; | |
| 124 | inline for (std.meta.fields(Args)) |field| { | |
| 125 | if (field.field_type == *ir.Inst) { | |
| 126 | if (arg_index >= 6) { | |
| 127 | @compileError("out of bits to mark deaths of operands"); | |
| 128 | } | |
| 129 | const prev = try table.fetchPut(@field(inst.args, field.name), {}); | |
| 130 | if (prev == null) { | |
| 131 | // Death. | |
| 132 | inst.base.deaths |= 1 << arg_index; | |
| 133 | } | |
| 134 | arg_index += 1; | |
| 135 | } | |
| 136 | } | |
| 137 | ||
| 138 | std.log.debug(.liveness, "analyze {}: 0b{b}\n", .{inst.base.tag, inst.base.deaths}); | |
| 139 | } |
src-self-hosted/main.zig+5-2| ... | ... | @@ -50,7 +50,10 @@ pub fn log( |
| 50 | 50 | const scope_prefix = "(" ++ switch (scope) { |
| 51 | 51 | // Uncomment to hide logs |
| 52 | 52 | //.compiler, |
| 53 | .link => return, | |
| 53 | .module, | |
| 54 | .liveness, | |
| 55 | .link, | |
| 56 | => return, | |
| 54 | 57 | |
| 55 | 58 | else => @tagName(scope), |
| 56 | 59 | } ++ "): "; |
| ... | ... | @@ -510,7 +513,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo |
| 510 | 513 | const update_nanos = timer.read(); |
| 511 | 514 | |
| 512 | 515 | var errors = try module.getAllErrorsAlloc(); |
| 513 | defer errors.deinit(module.allocator); | |
| 516 | defer errors.deinit(module.gpa); | |
| 514 | 517 | |
| 515 | 518 | if (errors.list.len != 0) { |
| 516 | 519 | for (errors.list) |full_err_msg| { |
src-self-hosted/zir.zig+220-62| ... | ... | @@ -38,6 +38,8 @@ pub const Inst = struct { |
| 38 | 38 | arg, |
| 39 | 39 | /// A labeled block of code, which can return a value. |
| 40 | 40 | block, |
| 41 | /// Return a value from a `Block`. | |
| 42 | @"break", | |
| 41 | 43 | breakpoint, |
| 42 | 44 | /// Same as `break` but without an operand; the operand is assumed to be the void value. |
| 43 | 45 | breakvoid, |
| ... | ... | @@ -57,6 +59,7 @@ pub const Inst = struct { |
| 57 | 59 | /// String Literal. Makes an anonymous Decl and then takes a pointer to it. |
| 58 | 60 | str, |
| 59 | 61 | int, |
| 62 | inttype, | |
| 60 | 63 | ptrtoint, |
| 61 | 64 | fieldptr, |
| 62 | 65 | deref, |
| ... | ... | @@ -73,6 +76,7 @@ pub const Inst = struct { |
| 73 | 76 | bitcast, |
| 74 | 77 | elemptr, |
| 75 | 78 | add, |
| 79 | sub, | |
| 76 | 80 | cmp, |
| 77 | 81 | condbr, |
| 78 | 82 | isnull, |
| ... | ... | @@ -83,6 +87,7 @@ pub const Inst = struct { |
| 83 | 87 | return switch (tag) { |
| 84 | 88 | .arg => Arg, |
| 85 | 89 | .block => Block, |
| 90 | .@"break" => Break, | |
| 86 | 91 | .breakpoint => Breakpoint, |
| 87 | 92 | .breakvoid => BreakVoid, |
| 88 | 93 | .call => Call, |
| ... | ... | @@ -94,6 +99,7 @@ pub const Inst = struct { |
| 94 | 99 | .@"const" => Const, |
| 95 | 100 | .str => Str, |
| 96 | 101 | .int => Int, |
| 102 | .inttype => IntType, | |
| 97 | 103 | .ptrtoint => PtrToInt, |
| 98 | 104 | .fieldptr => FieldPtr, |
| 99 | 105 | .deref => Deref, |
| ... | ... | @@ -110,6 +116,7 @@ pub const Inst = struct { |
| 110 | 116 | .bitcast => BitCast, |
| 111 | 117 | .elemptr => ElemPtr, |
| 112 | 118 | .add => Add, |
| 119 | .sub => Sub, | |
| 113 | 120 | .cmp => Cmp, |
| 114 | 121 | .condbr => CondBr, |
| 115 | 122 | .isnull => IsNull, |
| ... | ... | @@ -139,12 +146,22 @@ pub const Inst = struct { |
| 139 | 146 | base: Inst, |
| 140 | 147 | |
| 141 | 148 | positionals: struct { |
| 142 | label: []const u8, | |
| 143 | 149 | body: Module.Body, |
| 144 | 150 | }, |
| 145 | 151 | kw_args: struct {}, |
| 146 | 152 | }; |
| 147 | 153 | |
| 154 | pub const Break = struct { | |
| 155 | pub const base_tag = Tag.@"break"; | |
| 156 | base: Inst, | |
| 157 | ||
| 158 | positionals: struct { | |
| 159 | block: *Block, | |
| 160 | operand: *Inst, | |
| 161 | }, | |
| 162 | kw_args: struct {}, | |
| 163 | }; | |
| 164 | ||
| 148 | 165 | pub const Breakpoint = struct { |
| 149 | 166 | pub const base_tag = Tag.breakpoint; |
| 150 | 167 | base: Inst, |
| ... | ... | @@ -158,7 +175,7 @@ pub const Inst = struct { |
| 158 | 175 | base: Inst, |
| 159 | 176 | |
| 160 | 177 | positionals: struct { |
| 161 | label: []const u8, | |
| 178 | block: *Block, | |
| 162 | 179 | }, |
| 163 | 180 | kw_args: struct {}, |
| 164 | 181 | }; |
| ... | ... | @@ -367,6 +384,17 @@ pub const Inst = struct { |
| 367 | 384 | }, |
| 368 | 385 | }; |
| 369 | 386 | |
| 387 | pub const IntType = struct { | |
| 388 | pub const base_tag = Tag.inttype; | |
| 389 | base: Inst, | |
| 390 | ||
| 391 | positionals: struct { | |
| 392 | signed: *Inst, | |
| 393 | bits: *Inst, | |
| 394 | }, | |
| 395 | kw_args: struct {}, | |
| 396 | }; | |
| 397 | ||
| 370 | 398 | pub const Export = struct { |
| 371 | 399 | pub const base_tag = Tag.@"export"; |
| 372 | 400 | base: Inst, |
| ... | ... | @@ -512,6 +540,19 @@ pub const Inst = struct { |
| 512 | 540 | kw_args: struct {}, |
| 513 | 541 | }; |
| 514 | 542 | |
| 543 | pub const Sub = struct { | |
| 544 | pub const base_tag = Tag.sub; | |
| 545 | base: Inst, | |
| 546 | ||
| 547 | positionals: struct { | |
| 548 | lhs: *Inst, | |
| 549 | rhs: *Inst, | |
| 550 | }, | |
| 551 | kw_args: struct {}, | |
| 552 | }; | |
| 553 | ||
| 554 | /// TODO get rid of the op positional arg and make that data part of | |
| 555 | /// the base Inst tag. | |
| 515 | 556 | pub const Cmp = struct { |
| 516 | 557 | pub const base_tag = Tag.cmp; |
| 517 | 558 | base: Inst, |
| ... | ... | @@ -582,8 +623,6 @@ pub const Module = struct { |
| 582 | 623 | self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {}; |
| 583 | 624 | } |
| 584 | 625 | |
| 585 | const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize, name: []const u8 }); | |
| 586 | ||
| 587 | 626 | const DeclAndIndex = struct { |
| 588 | 627 | decl: *Decl, |
| 589 | 628 | index: usize, |
| ... | ... | @@ -617,80 +656,100 @@ pub const Module = struct { |
| 617 | 656 | /// The allocator is used for temporary storage, but this function always returns |
| 618 | 657 | /// with no resources allocated. |
| 619 | 658 | pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void { |
| 620 | // First, build a map of *Inst to @ or % indexes | |
| 621 | var inst_table = InstPtrTable.init(allocator); | |
| 622 | defer inst_table.deinit(); | |
| 659 | var write = Writer{ | |
| 660 | .module = &self, | |
| 661 | .inst_table = InstPtrTable.init(allocator), | |
| 662 | .block_table = std.AutoHashMap(*Inst.Block, []const u8).init(allocator), | |
| 663 | .arena = std.heap.ArenaAllocator.init(allocator), | |
| 664 | .indent = 2, | |
| 665 | }; | |
| 666 | defer write.arena.deinit(); | |
| 667 | defer write.inst_table.deinit(); | |
| 668 | defer write.block_table.deinit(); | |
| 623 | 669 | |
| 624 | try inst_table.ensureCapacity(self.decls.len); | |
| 670 | // First, build a map of *Inst to @ or % indexes | |
| 671 | try write.inst_table.ensureCapacity(self.decls.len); | |
| 625 | 672 | |
| 626 | 673 | for (self.decls) |decl, decl_i| { |
| 627 | try inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name }); | |
| 674 | try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name }); | |
| 628 | 675 | |
| 629 | 676 | if (decl.inst.cast(Inst.Fn)) |fn_inst| { |
| 630 | 677 | for (fn_inst.positionals.body.instructions) |inst, inst_i| { |
| 631 | try inst_table.putNoClobber(inst, .{ .inst = inst, .index = inst_i, .name = undefined }); | |
| 678 | try write.inst_table.putNoClobber(inst, .{ .inst = inst, .index = inst_i, .name = undefined }); | |
| 632 | 679 | } |
| 633 | 680 | } |
| 634 | 681 | } |
| 635 | 682 | |
| 636 | 683 | for (self.decls) |decl, i| { |
| 637 | 684 | try stream.print("@{} ", .{decl.name}); |
| 638 | try self.writeInstToStream(stream, decl.inst, &inst_table); | |
| 685 | try write.writeInstToStream(stream, decl.inst); | |
| 639 | 686 | try stream.writeByte('\n'); |
| 640 | 687 | } |
| 641 | 688 | } |
| 642 | 689 | |
| 690 | }; | |
| 691 | ||
| 692 | const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize, name: []const u8 }); | |
| 693 | ||
| 694 | const Writer = struct { | |
| 695 | module: *const Module, | |
| 696 | inst_table: InstPtrTable, | |
| 697 | block_table: std.AutoHashMap(*Inst.Block, []const u8), | |
| 698 | arena: std.heap.ArenaAllocator, | |
| 699 | indent: usize, | |
| 700 | ||
| 643 | 701 | fn writeInstToStream( |
| 644 | self: Module, | |
| 702 | self: *Writer, | |
| 645 | 703 | stream: var, |
| 646 | 704 | inst: *Inst, |
| 647 | inst_table: *const InstPtrTable, | |
| 648 | ) @TypeOf(stream).Error!void { | |
| 705 | ) (@TypeOf(stream).Error || error{OutOfMemory})!void { | |
| 649 | 706 | // TODO I tried implementing this with an inline for loop and hit a compiler bug |
| 650 | 707 | switch (inst.tag) { |
| 651 | .arg => return self.writeInstToStreamGeneric(stream, .arg, inst, inst_table), | |
| 652 | .block => return self.writeInstToStreamGeneric(stream, .block, inst, inst_table), | |
| 653 | .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, inst, inst_table), | |
| 654 | .breakvoid => return self.writeInstToStreamGeneric(stream, .breakvoid, inst, inst_table), | |
| 655 | .call => return self.writeInstToStreamGeneric(stream, .call, inst, inst_table), | |
| 656 | .declref => return self.writeInstToStreamGeneric(stream, .declref, inst, inst_table), | |
| 657 | .declref_str => return self.writeInstToStreamGeneric(stream, .declref_str, inst, inst_table), | |
| 658 | .declval => return self.writeInstToStreamGeneric(stream, .declval, inst, inst_table), | |
| 659 | .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst, inst_table), | |
| 660 | .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst, inst_table), | |
| 661 | .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst, inst_table), | |
| 662 | .str => return self.writeInstToStreamGeneric(stream, .str, inst, inst_table), | |
| 663 | .int => return self.writeInstToStreamGeneric(stream, .int, inst, inst_table), | |
| 664 | .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, inst, inst_table), | |
| 665 | .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, inst, inst_table), | |
| 666 | .deref => return self.writeInstToStreamGeneric(stream, .deref, inst, inst_table), | |
| 667 | .as => return self.writeInstToStreamGeneric(stream, .as, inst, inst_table), | |
| 668 | .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", inst, inst_table), | |
| 669 | .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", inst, inst_table), | |
| 670 | .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", inst, inst_table), | |
| 671 | .returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, inst, inst_table), | |
| 672 | .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", inst, inst_table), | |
| 673 | .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", inst, inst_table), | |
| 674 | .primitive => return self.writeInstToStreamGeneric(stream, .primitive, inst, inst_table), | |
| 675 | .fntype => return self.writeInstToStreamGeneric(stream, .fntype, inst, inst_table), | |
| 676 | .intcast => return self.writeInstToStreamGeneric(stream, .intcast, inst, inst_table), | |
| 677 | .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst, inst_table), | |
| 678 | .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst, inst_table), | |
| 679 | .add => return self.writeInstToStreamGeneric(stream, .add, inst, inst_table), | |
| 680 | .cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst, inst_table), | |
| 681 | .condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst, inst_table), | |
| 682 | .isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst, inst_table), | |
| 683 | .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, inst, inst_table), | |
| 708 | .arg => return self.writeInstToStreamGeneric(stream, .arg, inst), | |
| 709 | .block => return self.writeInstToStreamGeneric(stream, .block, inst), | |
| 710 | .@"break" => return self.writeInstToStreamGeneric(stream, .@"break", inst), | |
| 711 | .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, inst), | |
| 712 | .breakvoid => return self.writeInstToStreamGeneric(stream, .breakvoid, inst), | |
| 713 | .call => return self.writeInstToStreamGeneric(stream, .call, inst), | |
| 714 | .declref => return self.writeInstToStreamGeneric(stream, .declref, inst), | |
| 715 | .declref_str => return self.writeInstToStreamGeneric(stream, .declref_str, inst), | |
| 716 | .declval => return self.writeInstToStreamGeneric(stream, .declval, inst), | |
| 717 | .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst), | |
| 718 | .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst), | |
| 719 | .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst), | |
| 720 | .str => return self.writeInstToStreamGeneric(stream, .str, inst), | |
| 721 | .int => return self.writeInstToStreamGeneric(stream, .int, inst), | |
| 722 | .inttype => return self.writeInstToStreamGeneric(stream, .inttype, inst), | |
| 723 | .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, inst), | |
| 724 | .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, inst), | |
| 725 | .deref => return self.writeInstToStreamGeneric(stream, .deref, inst), | |
| 726 | .as => return self.writeInstToStreamGeneric(stream, .as, inst), | |
| 727 | .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", inst), | |
| 728 | .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", inst), | |
| 729 | .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", inst), | |
| 730 | .returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, inst), | |
| 731 | .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", inst), | |
| 732 | .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", inst), | |
| 733 | .primitive => return self.writeInstToStreamGeneric(stream, .primitive, inst), | |
| 734 | .fntype => return self.writeInstToStreamGeneric(stream, .fntype, inst), | |
| 735 | .intcast => return self.writeInstToStreamGeneric(stream, .intcast, inst), | |
| 736 | .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst), | |
| 737 | .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst), | |
| 738 | .add => return self.writeInstToStreamGeneric(stream, .add, inst), | |
| 739 | .sub => return self.writeInstToStreamGeneric(stream, .sub, inst), | |
| 740 | .cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst), | |
| 741 | .condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst), | |
| 742 | .isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst), | |
| 743 | .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, inst), | |
| 684 | 744 | } |
| 685 | 745 | } |
| 686 | 746 | |
| 687 | 747 | fn writeInstToStreamGeneric( |
| 688 | self: Module, | |
| 748 | self: *Writer, | |
| 689 | 749 | stream: var, |
| 690 | 750 | comptime inst_tag: Inst.Tag, |
| 691 | 751 | base: *Inst, |
| 692 | inst_table: *const InstPtrTable, | |
| 693 | ) !void { | |
| 752 | ) (@TypeOf(stream).Error || error{OutOfMemory})!void { | |
| 694 | 753 | const SpecificInst = Inst.TagToType(inst_tag); |
| 695 | 754 | const inst = @fieldParentPtr(SpecificInst, "base", base); |
| 696 | 755 | const Positionals = @TypeOf(inst.positionals); |
| ... | ... | @@ -700,7 +759,7 @@ pub const Module = struct { |
| 700 | 759 | if (i != 0) { |
| 701 | 760 | try stream.writeAll(", "); |
| 702 | 761 | } |
| 703 | try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table); | |
| 762 | try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name)); | |
| 704 | 763 | } |
| 705 | 764 | |
| 706 | 765 | comptime var need_comma = pos_fields.len != 0; |
| ... | ... | @@ -710,13 +769,13 @@ pub const Module = struct { |
| 710 | 769 | if (@field(inst.kw_args, arg_field.name)) |non_optional| { |
| 711 | 770 | if (need_comma) try stream.writeAll(", "); |
| 712 | 771 | try stream.print("{}=", .{arg_field.name}); |
| 713 | try self.writeParamToStream(stream, non_optional, inst_table); | |
| 772 | try self.writeParamToStream(stream, non_optional); | |
| 714 | 773 | need_comma = true; |
| 715 | 774 | } |
| 716 | 775 | } else { |
| 717 | 776 | if (need_comma) try stream.writeAll(", "); |
| 718 | 777 | try stream.print("{}=", .{arg_field.name}); |
| 719 | try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name), inst_table); | |
| 778 | try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name)); | |
| 720 | 779 | need_comma = true; |
| 721 | 780 | } |
| 722 | 781 | } |
| ... | ... | @@ -724,29 +783,37 @@ pub const Module = struct { |
| 724 | 783 | try stream.writeByte(')'); |
| 725 | 784 | } |
| 726 | 785 | |
| 727 | fn writeParamToStream(self: Module, stream: var, param: var, inst_table: *const InstPtrTable) !void { | |
| 786 | fn writeParamToStream(self: *Writer, stream: var, param: var) !void { | |
| 728 | 787 | if (@typeInfo(@TypeOf(param)) == .Enum) { |
| 729 | 788 | return stream.writeAll(@tagName(param)); |
| 730 | 789 | } |
| 731 | 790 | switch (@TypeOf(param)) { |
| 732 | *Inst => return self.writeInstParamToStream(stream, param, inst_table), | |
| 791 | *Inst => return self.writeInstParamToStream(stream, param), | |
| 733 | 792 | []*Inst => { |
| 734 | 793 | try stream.writeByte('['); |
| 735 | 794 | for (param) |inst, i| { |
| 736 | 795 | if (i != 0) { |
| 737 | 796 | try stream.writeAll(", "); |
| 738 | 797 | } |
| 739 | try self.writeInstParamToStream(stream, inst, inst_table); | |
| 798 | try self.writeInstParamToStream(stream, inst); | |
| 740 | 799 | } |
| 741 | 800 | try stream.writeByte(']'); |
| 742 | 801 | }, |
| 743 | 802 | Module.Body => { |
| 744 | 803 | try stream.writeAll("{\n"); |
| 745 | 804 | for (param.instructions) |inst, i| { |
| 746 | try stream.print(" %{} ", .{i}); | |
| 747 | try self.writeInstToStream(stream, inst, inst_table); | |
| 805 | try stream.writeByteNTimes(' ', self.indent); | |
| 806 | try stream.print("%{} ", .{i}); | |
| 807 | if (inst.cast(Inst.Block)) |block| { | |
| 808 | const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{}", .{i}); | |
| 809 | try self.block_table.put(block, name); | |
| 810 | } | |
| 811 | self.indent += 2; | |
| 812 | try self.writeInstToStream(stream, inst); | |
| 813 | self.indent -= 2; | |
| 748 | 814 | try stream.writeByte('\n'); |
| 749 | 815 | } |
| 816 | try stream.writeByteNTimes(' ', self.indent - 2); | |
| 750 | 817 | try stream.writeByte('}'); |
| 751 | 818 | }, |
| 752 | 819 | bool => return stream.writeByte("01"[@boolToInt(param)]), |
| ... | ... | @@ -754,12 +821,16 @@ pub const Module = struct { |
| 754 | 821 | BigIntConst, usize => return stream.print("{}", .{param}), |
| 755 | 822 | TypedValue => unreachable, // this is a special case |
| 756 | 823 | *IrModule.Decl => unreachable, // this is a special case |
| 824 | *Inst.Block => { | |
| 825 | const name = self.block_table.get(param).?; | |
| 826 | return std.zig.renderStringLiteral(name, stream); | |
| 827 | }, | |
| 757 | 828 | else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), |
| 758 | 829 | } |
| 759 | 830 | } |
| 760 | 831 | |
| 761 | fn writeInstParamToStream(self: Module, stream: var, inst: *Inst, inst_table: *const InstPtrTable) !void { | |
| 762 | if (inst_table.get(inst)) |info| { | |
| 832 | fn writeInstParamToStream(self: *Writer, stream: var, inst: *Inst) !void { | |
| 833 | if (self.inst_table.get(inst)) |info| { | |
| 763 | 834 | if (info.index) |i| { |
| 764 | 835 | try stream.print("%{}", .{info.index}); |
| 765 | 836 | } else { |
| ... | ... | @@ -789,7 +860,9 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module |
| 789 | 860 | .global_name_map = &global_name_map, |
| 790 | 861 | .decls = .{}, |
| 791 | 862 | .unnamed_index = 0, |
| 863 | .block_table = std.StringHashMap(*Inst.Block).init(allocator), | |
| 792 | 864 | }; |
| 865 | defer parser.block_table.deinit(); | |
| 793 | 866 | errdefer parser.arena.deinit(); |
| 794 | 867 | |
| 795 | 868 | parser.parseRoot() catch |err| switch (err) { |
| ... | ... | @@ -815,6 +888,7 @@ const Parser = struct { |
| 815 | 888 | global_name_map: *std.StringHashMap(*Inst), |
| 816 | 889 | error_msg: ?ErrorMsg = null, |
| 817 | 890 | unnamed_index: usize, |
| 891 | block_table: std.StringHashMap(*Inst.Block), | |
| 818 | 892 | |
| 819 | 893 | const Body = struct { |
| 820 | 894 | instructions: std.ArrayList(*Inst), |
| ... | ... | @@ -1023,6 +1097,10 @@ const Parser = struct { |
| 1023 | 1097 | .tag = InstType.base_tag, |
| 1024 | 1098 | }; |
| 1025 | 1099 | |
| 1100 | if (InstType == Inst.Block) { | |
| 1101 | try self.block_table.put(inst_name, inst_specific); | |
| 1102 | } | |
| 1103 | ||
| 1026 | 1104 | if (@hasField(InstType, "ty")) { |
| 1027 | 1105 | inst_specific.ty = opt_type orelse { |
| 1028 | 1106 | return self.fail("instruction '" ++ fn_name ++ "' requires type", .{}); |
| ... | ... | @@ -1128,6 +1206,10 @@ const Parser = struct { |
| 1128 | 1206 | }, |
| 1129 | 1207 | TypedValue => return self.fail("'const' is a special instruction; not legal in ZIR text", .{}), |
| 1130 | 1208 | *IrModule.Decl => return self.fail("'declval_in_module' is a special instruction; not legal in ZIR text", .{}), |
| 1209 | *Inst.Block => { | |
| 1210 | const name = try self.parseStringLiteral(); | |
| 1211 | return self.block_table.get(name).?; | |
| 1212 | }, | |
| 1131 | 1213 | else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), |
| 1132 | 1214 | } |
| 1133 | 1215 | return self.fail("TODO parse parameter {}", .{@typeName(T)}); |
| ... | ... | @@ -1191,7 +1273,10 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { |
| 1191 | 1273 | .next_auto_name = 0, |
| 1192 | 1274 | .names = std.StringHashMap(void).init(allocator), |
| 1193 | 1275 | .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), |
| 1276 | .indent = 0, | |
| 1277 | .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), | |
| 1194 | 1278 | }; |
| 1279 | defer ctx.block_table.deinit(); | |
| 1195 | 1280 | defer ctx.decls.deinit(allocator); |
| 1196 | 1281 | defer ctx.names.deinit(); |
| 1197 | 1282 | defer ctx.primitive_table.deinit(); |
| ... | ... | @@ -1213,6 +1298,8 @@ const EmitZIR = struct { |
| 1213 | 1298 | names: std.StringHashMap(void), |
| 1214 | 1299 | next_auto_name: usize, |
| 1215 | 1300 | primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl), |
| 1301 | indent: usize, | |
| 1302 | block_table: std.AutoHashMap(*ir.Inst.Block, *Inst.Block), | |
| 1216 | 1303 | |
| 1217 | 1304 | fn emit(self: *EmitZIR) !void { |
| 1218 | 1305 | // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced |
| ... | ... | @@ -1542,6 +1629,22 @@ const EmitZIR = struct { |
| 1542 | 1629 | }; |
| 1543 | 1630 | break :blk &new_inst.base; |
| 1544 | 1631 | }, |
| 1632 | .sub => blk: { | |
| 1633 | const old_inst = inst.cast(ir.Inst.Sub).?; | |
| 1634 | const new_inst = try self.arena.allocator.create(Inst.Sub); | |
| 1635 | new_inst.* = .{ | |
| 1636 | .base = .{ | |
| 1637 | .src = inst.src, | |
| 1638 | .tag = Inst.Sub.base_tag, | |
| 1639 | }, | |
| 1640 | .positionals = .{ | |
| 1641 | .lhs = try self.resolveInst(new_body, old_inst.args.lhs), | |
| 1642 | .rhs = try self.resolveInst(new_body, old_inst.args.rhs), | |
| 1643 | }, | |
| 1644 | .kw_args = .{}, | |
| 1645 | }; | |
| 1646 | break :blk &new_inst.base; | |
| 1647 | }, | |
| 1545 | 1648 | .arg => blk: { |
| 1546 | 1649 | const old_inst = inst.cast(ir.Inst.Arg).?; |
| 1547 | 1650 | const new_inst = try self.arena.allocator.create(Inst.Arg); |
| ... | ... | @@ -1559,6 +1662,8 @@ const EmitZIR = struct { |
| 1559 | 1662 | const old_inst = inst.cast(ir.Inst.Block).?; |
| 1560 | 1663 | const new_inst = try self.arena.allocator.create(Inst.Block); |
| 1561 | 1664 | |
| 1665 | try self.block_table.put(old_inst, new_inst); | |
| 1666 | ||
| 1562 | 1667 | var block_body = std.ArrayList(*Inst).init(self.allocator); |
| 1563 | 1668 | defer block_body.deinit(); |
| 1564 | 1669 | |
| ... | ... | @@ -1570,14 +1675,47 @@ const EmitZIR = struct { |
| 1570 | 1675 | .tag = Inst.Block.base_tag, |
| 1571 | 1676 | }, |
| 1572 | 1677 | .positionals = .{ |
| 1573 | .label = try self.autoName(), | |
| 1574 | 1678 | .body = .{ .instructions = block_body.toOwnedSlice() }, |
| 1575 | 1679 | }, |
| 1576 | 1680 | .kw_args = .{}, |
| 1577 | 1681 | }; |
| 1682 | ||
| 1683 | break :blk &new_inst.base; | |
| 1684 | }, | |
| 1685 | .br => blk: { | |
| 1686 | const old_inst = inst.cast(ir.Inst.Br).?; | |
| 1687 | const new_block = self.block_table.get(old_inst.args.block).?; | |
| 1688 | const new_inst = try self.arena.allocator.create(Inst.Break); | |
| 1689 | new_inst.* = .{ | |
| 1690 | .base = .{ | |
| 1691 | .src = inst.src, | |
| 1692 | .tag = Inst.Break.base_tag, | |
| 1693 | }, | |
| 1694 | .positionals = .{ | |
| 1695 | .block = new_block, | |
| 1696 | .operand = try self.resolveInst(new_body, old_inst.args.operand), | |
| 1697 | }, | |
| 1698 | .kw_args = .{}, | |
| 1699 | }; | |
| 1578 | 1700 | break :blk &new_inst.base; |
| 1579 | 1701 | }, |
| 1580 | 1702 | .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint), |
| 1703 | .brvoid => blk: { | |
| 1704 | const old_inst = inst.cast(ir.Inst.BrVoid).?; | |
| 1705 | const new_block = self.block_table.get(old_inst.args.block).?; | |
| 1706 | const new_inst = try self.arena.allocator.create(Inst.BreakVoid); | |
| 1707 | new_inst.* = .{ | |
| 1708 | .base = .{ | |
| 1709 | .src = inst.src, | |
| 1710 | .tag = Inst.BreakVoid.base_tag, | |
| 1711 | }, | |
| 1712 | .positionals = .{ | |
| 1713 | .block = new_block, | |
| 1714 | }, | |
| 1715 | .kw_args = .{}, | |
| 1716 | }; | |
| 1717 | break :blk &new_inst.base; | |
| 1718 | }, | |
| 1581 | 1719 | .call => blk: { |
| 1582 | 1720 | const old_inst = inst.cast(ir.Inst.Call).?; |
| 1583 | 1721 | const new_inst = try self.arena.allocator.create(Inst.Call); |
| ... | ... | @@ -1765,7 +1903,7 @@ const EmitZIR = struct { |
| 1765 | 1903 | }, |
| 1766 | 1904 | }; |
| 1767 | 1905 | try instructions.append(new_inst); |
| 1768 | try inst_table.putNoClobber(inst, new_inst); | |
| 1906 | try inst_table.put(inst, new_inst); | |
| 1769 | 1907 | } |
| 1770 | 1908 | } |
| 1771 | 1909 | |
| ... | ... | @@ -1829,6 +1967,26 @@ const EmitZIR = struct { |
| 1829 | 1967 | }; |
| 1830 | 1968 | return self.emitUnnamedDecl(&fntype_inst.base); |
| 1831 | 1969 | }, |
| 1970 | .Int => { | |
| 1971 | const info = ty.intInfo(self.old_module.target()); | |
| 1972 | const signed = try self.emitPrimitive(src, if (info.signed) .@"true" else .@"false"); | |
| 1973 | const bits_payload = try self.arena.allocator.create(Value.Payload.Int_u64); | |
| 1974 | bits_payload.* = .{ .int = info.bits }; | |
| 1975 | const bits = try self.emitComptimeIntVal(src, Value.initPayload(&bits_payload.base)); | |
| 1976 | const inttype_inst = try self.arena.allocator.create(Inst.IntType); | |
| 1977 | inttype_inst.* = .{ | |
| 1978 | .base = .{ | |
| 1979 | .src = src, | |
| 1980 | .tag = Inst.IntType.base_tag, | |
| 1981 | }, | |
| 1982 | .positionals = .{ | |
| 1983 | .signed = signed.inst, | |
| 1984 | .bits = bits.inst, | |
| 1985 | }, | |
| 1986 | .kw_args = .{}, | |
| 1987 | }; | |
| 1988 | return self.emitUnnamedDecl(&inttype_inst.base); | |
| 1989 | }, | |
| 1832 | 1990 | else => std.debug.panic("TODO implement emitType for {}", .{ty}), |
| 1833 | 1991 | }, |
| 1834 | 1992 | } |