| author | |
| committer | |
| log | dadc4327e109e1cb13ae7e6132db586f5f849396 |
| tree | b83c3d6b74fe3f299de9a1869e4b64e614d2fa5e |
| parent | 2ae9e06363b294f60b62e765d31fce4417e14a9d |
| parent | 5d77fede896ff7446e9946f91c74b7632b63253e |
6 files changed, 810 insertions(+), 271 deletions(-)
src-self-hosted/Module.zig+367-113| ... | ... | @@ -55,9 +55,20 @@ failed_files: std.AutoHashMap(*Scope.ZIRModule, *ErrorMsg), |
| 55 | 55 | /// The ErrorMsg memory is owned by the `Export`, using Module's allocator. |
| 56 | 56 | failed_exports: std.AutoHashMap(*Export, *ErrorMsg), |
| 57 | 57 | |
| 58 | /// Incrementing integer used to compare against the corresponding Decl | |
| 59 | /// field to determine whether a Decl's status applies to an ongoing update, or a | |
| 60 | /// previous analysis. | |
| 61 | generation: u32 = 0, | |
| 62 | ||
| 63 | /// Candidates for deletion. After a semantic analysis update completes, this list | |
| 64 | /// contains Decls that need to be deleted if they end up having no references to them. | |
| 65 | deletion_set: std.ArrayListUnmanaged(*Decl) = std.ArrayListUnmanaged(*Decl){}, | |
| 66 | ||
| 58 | 67 | pub const WorkItem = union(enum) { |
| 59 | 68 | /// Write the machine code for a Decl to the output file. |
| 60 | 69 | codegen_decl: *Decl, |
| 70 | /// Decl has been determined to be outdated; perform semantic analysis again. | |
| 71 | re_analyze_decl: *Decl, | |
| 61 | 72 | }; |
| 62 | 73 | |
| 63 | 74 | pub const Export = struct { |
| ... | ... | @@ -68,6 +79,8 @@ pub const Export = struct { |
| 68 | 79 | link: link.ElfFile.Export, |
| 69 | 80 | /// The Decl that performs the export. Note that this is *not* the Decl being exported. |
| 70 | 81 | owner_decl: *Decl, |
| 82 | /// The Decl being exported. Note this is *not* the Decl performing the export. | |
| 83 | exported_decl: *Decl, | |
| 71 | 84 | status: enum { |
| 72 | 85 | in_progress, |
| 73 | 86 | failed, |
| ... | ... | @@ -94,8 +107,7 @@ pub const Decl = struct { |
| 94 | 107 | /// This is the base offset that src offsets within this Decl are relative to. |
| 95 | 108 | src: usize, |
| 96 | 109 | /// The most recent value of the Decl after a successful semantic analysis. |
| 97 | /// The tag for this union is determined by the tag value of the analysis field. | |
| 98 | typed_value: union { | |
| 110 | typed_value: union(enum) { | |
| 99 | 111 | never_succeeded: void, |
| 100 | 112 | most_recent: TypedValue.Managed, |
| 101 | 113 | }, |
| ... | ... | @@ -104,50 +116,56 @@ pub const Decl = struct { |
| 104 | 116 | /// analysis of the function body is performed with this value set to `success`. Functions |
| 105 | 117 | /// have their own analysis status field. |
| 106 | 118 | analysis: enum { |
| 107 | initial_in_progress, | |
| 119 | /// Semantic analysis for this Decl is running right now. This state detects dependency loops. | |
| 120 | in_progress, | |
| 108 | 121 | /// This Decl might be OK but it depends on another one which did not successfully complete |
| 109 | /// semantic analysis. This Decl never had a value computed. | |
| 110 | initial_dependency_failure, | |
| 111 | /// Semantic analysis failure. This Decl never had a value computed. | |
| 122 | /// semantic analysis. | |
| 123 | dependency_failure, | |
| 124 | /// Semantic analysis failure. | |
| 112 | 125 | /// There will be a corresponding ErrorMsg in Module.failed_decls. |
| 113 | initial_sema_failure, | |
| 114 | /// In this case the `typed_value.most_recent` can still be accessed. | |
| 126 | sema_failure, | |
| 115 | 127 | /// There will be a corresponding ErrorMsg in Module.failed_decls. |
| 116 | 128 | codegen_failure, |
| 117 | /// In this case the `typed_value.most_recent` can still be accessed. | |
| 118 | 129 | /// There will be a corresponding ErrorMsg in Module.failed_decls. |
| 119 | 130 | /// This indicates the failure was something like running out of disk space, |
| 120 | 131 | /// and attempting codegen again may succeed. |
| 121 | 132 | codegen_failure_retryable, |
| 122 | /// This Decl might be OK but it depends on another one which did not successfully complete | |
| 123 | /// semantic analysis. There is a most recent value available. | |
| 124 | repeat_dependency_failure, | |
| 125 | /// Semantic anlaysis failure, but the `typed_value.most_recent` can be accessed. | |
| 126 | /// There will be a corresponding ErrorMsg in Module.failed_decls. | |
| 127 | repeat_sema_failure, | |
| 128 | /// Completed successfully before; the `typed_value.most_recent` can be accessed, and | |
| 129 | /// new semantic analysis is in progress. | |
| 130 | repeat_in_progress, | |
| 131 | /// Everything is done and updated. | |
| 133 | /// Everything is done. During an update, this Decl may be out of date, depending | |
| 134 | /// on its dependencies. The `generation` field can be used to determine if this | |
| 135 | /// completion status occurred before or after a given update. | |
| 132 | 136 | complete, |
| 137 | /// A Module update is in progress, and this Decl has been flagged as being known | |
| 138 | /// to require re-analysis. | |
| 139 | outdated, | |
| 133 | 140 | }, |
| 141 | /// This flag is set when this Decl is added to a check_for_deletion set, and cleared | |
| 142 | /// when removed. | |
| 143 | deletion_flag: bool, | |
| 144 | /// An integer that can be checked against the corresponding incrementing | |
| 145 | /// generation field of Module. This is used to determine whether `complete` status | |
| 146 | /// represents pre- or post- re-analysis. | |
| 147 | generation: u32, | |
| 134 | 148 | |
| 135 | 149 | /// Represents the position of the code in the output file. |
| 136 | 150 | /// This is populated regardless of semantic analysis and code generation. |
| 137 | link: link.ElfFile.Decl = link.ElfFile.Decl.empty, | |
| 151 | link: link.ElfFile.TextBlock = link.ElfFile.TextBlock.empty, | |
| 152 | ||
| 153 | contents_hash: Hash, | |
| 138 | 154 | |
| 139 | 155 | /// The shallow set of other decls whose typed_value could possibly change if this Decl's |
| 140 | 156 | /// typed_value is modified. |
| 141 | /// TODO look into using a lightweight map/set data structure rather than a linear array. | |
| 142 | 157 | dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, |
| 143 | ||
| 144 | contents_hash: Hash, | |
| 158 | /// The shallow set of other decls whose typed_value changing indicates that this Decl's | |
| 159 | /// typed_value may need to be regenerated. | |
| 160 | dependencies: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, | |
| 145 | 161 | |
| 146 | 162 | pub fn destroy(self: *Decl, allocator: *Allocator) void { |
| 147 | 163 | allocator.free(mem.spanZ(self.name)); |
| 148 | 164 | if (self.typedValueManaged()) |tvm| { |
| 149 | 165 | tvm.deinit(allocator); |
| 150 | 166 | } |
| 167 | self.dependants.deinit(allocator); | |
| 168 | self.dependencies.deinit(allocator); | |
| 151 | 169 | allocator.destroy(self); |
| 152 | 170 | } |
| 153 | 171 | |
| ... | ... | @@ -172,7 +190,7 @@ pub const Decl = struct { |
| 172 | 190 | pub fn fullyQualifiedNameHash(self: Decl) Hash { |
| 173 | 191 | // Right now we only have ZIRModule as the source. So this is simply the |
| 174 | 192 | // relative name of the decl. |
| 175 | return hashSimpleName(mem.spanZ(u8, self.name)); | |
| 193 | return hashSimpleName(mem.spanZ(self.name)); | |
| 176 | 194 | } |
| 177 | 195 | |
| 178 | 196 | pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue { |
| ... | ... | @@ -200,20 +218,31 @@ pub const Decl = struct { |
| 200 | 218 | } |
| 201 | 219 | |
| 202 | 220 | fn typedValueManaged(self: *Decl) ?*TypedValue.Managed { |
| 203 | switch (self.analysis) { | |
| 204 | .initial_in_progress, | |
| 205 | .initial_dependency_failure, | |
| 206 | .initial_sema_failure, | |
| 207 | => return null, | |
| 208 | .codegen_failure, | |
| 209 | .codegen_failure_retryable, | |
| 210 | .repeat_dependency_failure, | |
| 211 | .repeat_sema_failure, | |
| 212 | .repeat_in_progress, | |
| 213 | .complete, | |
| 214 | => return &self.typed_value.most_recent, | |
| 221 | switch (self.typed_value) { | |
| 222 | .most_recent => |*x| return x, | |
| 223 | .never_succeeded => return null, | |
| 215 | 224 | } |
| 216 | 225 | } |
| 226 | ||
| 227 | fn removeDependant(self: *Decl, other: *Decl) void { | |
| 228 | for (self.dependants.items) |item, i| { | |
| 229 | if (item == other) { | |
| 230 | _ = self.dependants.swapRemove(i); | |
| 231 | return; | |
| 232 | } | |
| 233 | } | |
| 234 | unreachable; | |
| 235 | } | |
| 236 | ||
| 237 | fn removeDependency(self: *Decl, other: *Decl) void { | |
| 238 | for (self.dependencies.items) |item, i| { | |
| 239 | if (item == other) { | |
| 240 | _ = self.dependencies.swapRemove(i); | |
| 241 | return; | |
| 242 | } | |
| 243 | } | |
| 244 | unreachable; | |
| 245 | } | |
| 217 | 246 | }; |
| 218 | 247 | |
| 219 | 248 | /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. |
| ... | ... | @@ -266,12 +295,12 @@ pub const Scope = struct { |
| 266 | 295 | |
| 267 | 296 | /// Asserts the scope has a parent which is a DeclAnalysis and |
| 268 | 297 | /// returns the Decl. |
| 269 | pub fn decl(self: *Scope) *Decl { | |
| 270 | switch (self.tag) { | |
| 271 | .block => return self.cast(Block).?.decl, | |
| 272 | .decl => return self.cast(DeclAnalysis).?.decl, | |
| 273 | .zir_module => unreachable, | |
| 274 | } | |
| 298 | pub fn decl(self: *Scope) ?*Decl { | |
| 299 | return switch (self.tag) { | |
| 300 | .block => self.cast(Block).?.decl, | |
| 301 | .decl => self.cast(DeclAnalysis).?.decl, | |
| 302 | .zir_module => null, | |
| 303 | }; | |
| 275 | 304 | } |
| 276 | 305 | |
| 277 | 306 | /// Asserts the scope has a parent which is a ZIRModule and |
| ... | ... | @@ -477,6 +506,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 477 | 506 | pub fn deinit(self: *Module) void { |
| 478 | 507 | self.bin_file.deinit(); |
| 479 | 508 | const allocator = self.allocator; |
| 509 | self.deletion_set.deinit(allocator); | |
| 480 | 510 | self.work_queue.deinit(); |
| 481 | 511 | { |
| 482 | 512 | var it = self.decl_table.iterator(); |
| ... | ... | @@ -517,11 +547,7 @@ pub fn deinit(self: *Module) void { |
| 517 | 547 | { |
| 518 | 548 | var it = self.export_owners.iterator(); |
| 519 | 549 | while (it.next()) |kv| { |
| 520 | const export_list = kv.value; | |
| 521 | for (export_list) |exp| { | |
| 522 | allocator.destroy(exp); | |
| 523 | } | |
| 524 | allocator.free(export_list); | |
| 550 | freeExportList(allocator, kv.value); | |
| 525 | 551 | } |
| 526 | 552 | self.export_owners.deinit(); |
| 527 | 553 | } |
| ... | ... | @@ -532,12 +558,21 @@ pub fn deinit(self: *Module) void { |
| 532 | 558 | self.* = undefined; |
| 533 | 559 | } |
| 534 | 560 | |
| 561 | fn freeExportList(allocator: *Allocator, export_list: []*Export) void { | |
| 562 | for (export_list) |exp| { | |
| 563 | allocator.destroy(exp); | |
| 564 | } | |
| 565 | allocator.free(export_list); | |
| 566 | } | |
| 567 | ||
| 535 | 568 | pub fn target(self: Module) std.Target { |
| 536 | 569 | return self.bin_file.options.target; |
| 537 | 570 | } |
| 538 | 571 | |
| 539 | 572 | /// Detect changes to source files, perform semantic analysis, and update the output files. |
| 540 | 573 | pub fn update(self: *Module) !void { |
| 574 | self.generation += 1; | |
| 575 | ||
| 541 | 576 | // TODO Use the cache hash file system to detect which source files changed. |
| 542 | 577 | // Here we simulate a full cache miss. |
| 543 | 578 | // Analyze the root source file now. |
| ... | ... | @@ -550,6 +585,15 @@ pub fn update(self: *Module) !void { |
| 550 | 585 | |
| 551 | 586 | try self.performAllTheWork(); |
| 552 | 587 | |
| 588 | // Process the deletion set. | |
| 589 | while (self.deletion_set.popOrNull()) |decl| { | |
| 590 | if (decl.dependants.items.len != 0) { | |
| 591 | decl.deletion_flag = false; | |
| 592 | continue; | |
| 593 | } | |
| 594 | try self.deleteDecl(decl); | |
| 595 | } | |
| 596 | ||
| 553 | 597 | // Unload all the source files from memory. |
| 554 | 598 | self.root_scope.unload(self.allocator); |
| 555 | 599 | |
| ... | ... | @@ -634,15 +678,12 @@ const InnerError = error{ OutOfMemory, AnalysisFail }; |
| 634 | 678 | pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 635 | 679 | while (self.work_queue.readItem()) |work_item| switch (work_item) { |
| 636 | 680 | .codegen_decl => |decl| switch (decl.analysis) { |
| 637 | .initial_in_progress, | |
| 638 | .repeat_in_progress, | |
| 639 | => unreachable, | |
| 681 | .in_progress => unreachable, | |
| 682 | .outdated => unreachable, | |
| 640 | 683 | |
| 641 | .initial_sema_failure, | |
| 642 | .repeat_sema_failure, | |
| 684 | .sema_failure, | |
| 643 | 685 | .codegen_failure, |
| 644 | .initial_dependency_failure, | |
| 645 | .repeat_dependency_failure, | |
| 686 | .dependency_failure, | |
| 646 | 687 | => continue, |
| 647 | 688 | |
| 648 | 689 | .complete, .codegen_failure_retryable => { |
| ... | ... | @@ -668,7 +709,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 668 | 709 | self.bin_file.updateDecl(self, decl) catch |err| switch (err) { |
| 669 | 710 | error.OutOfMemory => return error.OutOfMemory, |
| 670 | 711 | error.AnalysisFail => { |
| 671 | decl.analysis = .repeat_dependency_failure; | |
| 712 | decl.analysis = .dependency_failure; | |
| 672 | 713 | }, |
| 673 | 714 | else => { |
| 674 | 715 | try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); |
| ... | ... | @@ -683,9 +724,60 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 683 | 724 | }; |
| 684 | 725 | }, |
| 685 | 726 | }, |
| 727 | .re_analyze_decl => |decl| switch (decl.analysis) { | |
| 728 | .in_progress => unreachable, | |
| 729 | ||
| 730 | .sema_failure, | |
| 731 | .codegen_failure, | |
| 732 | .dependency_failure, | |
| 733 | .complete, | |
| 734 | .codegen_failure_retryable, | |
| 735 | => continue, | |
| 736 | ||
| 737 | .outdated => { | |
| 738 | const zir_module = self.getSrcModule(decl.scope) catch |err| switch (err) { | |
| 739 | error.OutOfMemory => return error.OutOfMemory, | |
| 740 | else => { | |
| 741 | try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); | |
| 742 | self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( | |
| 743 | self.allocator, | |
| 744 | decl.src, | |
| 745 | "unable to load source file '{}': {}", | |
| 746 | .{decl.scope.sub_file_path, @errorName(err)}, | |
| 747 | )); | |
| 748 | decl.analysis = .codegen_failure_retryable; | |
| 749 | continue; | |
| 750 | }, | |
| 751 | }; | |
| 752 | const decl_name = mem.spanZ(decl.name); | |
| 753 | // We already detected deletions, so we know this will be found. | |
| 754 | const src_decl = zir_module.findDecl(decl_name).?; | |
| 755 | self.reAnalyzeDecl(decl, src_decl) catch |err| switch (err) { | |
| 756 | error.OutOfMemory => return error.OutOfMemory, | |
| 757 | error.AnalysisFail => continue, | |
| 758 | }; | |
| 759 | } | |
| 760 | }, | |
| 686 | 761 | }; |
| 687 | 762 | } |
| 688 | 763 | |
| 764 | fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void { | |
| 765 | try depender.dependencies.ensureCapacity(self.allocator, depender.dependencies.items.len + 1); | |
| 766 | try dependee.dependants.ensureCapacity(self.allocator, dependee.dependants.items.len + 1); | |
| 767 | ||
| 768 | for (depender.dependencies.items) |item| { | |
| 769 | if (item == dependee) break; // Already in the set. | |
| 770 | } else { | |
| 771 | depender.dependencies.appendAssumeCapacity(dependee); | |
| 772 | } | |
| 773 | ||
| 774 | for (dependee.dependants.items) |item| { | |
| 775 | if (item == depender) break; // Already in the set. | |
| 776 | } else { | |
| 777 | dependee.dependants.appendAssumeCapacity(depender); | |
| 778 | } | |
| 779 | } | |
| 780 | ||
| 689 | 781 | fn getSource(self: *Module, root_scope: *Scope.ZIRModule) ![:0]const u8 { |
| 690 | 782 | switch (root_scope.source) { |
| 691 | 783 | .unloaded => { |
| ... | ... | @@ -742,13 +834,6 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { |
| 742 | 834 | } |
| 743 | 835 | |
| 744 | 836 | fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 745 | // TODO use the cache to identify, from the modified source files, the decls which have | |
| 746 | // changed based on the span of memory that represents the decl in the re-parsed source file. | |
| 747 | // Use the cached dependency graph to recursively determine the set of decls which need | |
| 748 | // regeneration. | |
| 749 | // Here we simulate adding a source file which was previously not part of the compilation, | |
| 750 | // which means scanning the decls looking for exports. | |
| 751 | // TODO also identify decls that need to be deleted. | |
| 752 | 837 | switch (root_scope.status) { |
| 753 | 838 | .never_loaded => { |
| 754 | 839 | const src_module = try self.getSrcModule(root_scope); |
| ... | ... | @@ -759,7 +844,7 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 759 | 844 | |
| 760 | 845 | for (src_module.decls) |decl| { |
| 761 | 846 | if (decl.cast(zir.Inst.Export)) |export_inst| { |
| 762 | _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.Decl.empty); | |
| 847 | _ = try self.resolveDecl(&root_scope.base, &export_inst.base); | |
| 763 | 848 | } |
| 764 | 849 | } |
| 765 | 850 | }, |
| ... | ... | @@ -772,41 +857,112 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 772 | 857 | => { |
| 773 | 858 | const src_module = try self.getSrcModule(root_scope); |
| 774 | 859 | |
| 775 | // Look for changed decls. | |
| 860 | var exports_to_resolve = std.ArrayList(*zir.Inst).init(self.allocator); | |
| 861 | defer exports_to_resolve.deinit(); | |
| 862 | ||
| 863 | // Keep track of the decls that we expect to see in this file so that | |
| 864 | // we know which ones have been deleted. | |
| 865 | var deleted_decls = std.AutoHashMap(*Decl, void).init(self.allocator); | |
| 866 | defer deleted_decls.deinit(); | |
| 867 | try deleted_decls.ensureCapacity(self.decl_table.size); | |
| 868 | { | |
| 869 | var it = self.decl_table.iterator(); | |
| 870 | while (it.next()) |kv| { | |
| 871 | deleted_decls.putAssumeCapacityNoClobber(kv.value, {}); | |
| 872 | } | |
| 873 | } | |
| 874 | ||
| 776 | 875 | for (src_module.decls) |src_decl| { |
| 777 | 876 | const name_hash = Decl.hashSimpleName(src_decl.name); |
| 778 | 877 | if (self.decl_table.get(name_hash)) |kv| { |
| 779 | 878 | const decl = kv.value; |
| 879 | deleted_decls.removeAssertDiscard(decl); | |
| 780 | 880 | const new_contents_hash = Decl.hashSimpleName(src_decl.contents); |
| 781 | 881 | if (!mem.eql(u8, &new_contents_hash, &decl.contents_hash)) { |
| 782 | // TODO recursive dependency management | |
| 783 | //std.debug.warn("noticed that '{}' changed\n", .{src_decl.name}); | |
| 784 | self.decl_table.removeAssertDiscard(name_hash); | |
| 785 | const saved_link = decl.link; | |
| 786 | decl.destroy(self.allocator); | |
| 787 | if (self.export_owners.getValue(decl)) |exports| { | |
| 788 | @panic("TODO handle updating a decl that does an export"); | |
| 789 | } | |
| 790 | const new_decl = self.resolveDecl( | |
| 791 | &root_scope.base, | |
| 792 | src_decl, | |
| 793 | saved_link, | |
| 794 | ) catch |err| switch (err) { | |
| 795 | error.OutOfMemory => return error.OutOfMemory, | |
| 796 | error.AnalysisFail => continue, | |
| 797 | }; | |
| 798 | if (self.decl_exports.remove(decl)) |entry| { | |
| 799 | self.decl_exports.putAssumeCapacityNoClobber(new_decl, entry.value); | |
| 800 | } | |
| 882 | //std.debug.warn("noticed '{}' source changed\n", .{src_decl.name}); | |
| 883 | decl.analysis = .outdated; | |
| 884 | decl.contents_hash = new_contents_hash; | |
| 885 | try self.work_queue.writeItem(.{ .re_analyze_decl = decl }); | |
| 801 | 886 | } |
| 802 | 887 | } else if (src_decl.cast(zir.Inst.Export)) |export_inst| { |
| 803 | _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.Decl.empty); | |
| 888 | try exports_to_resolve.append(&export_inst.base); | |
| 889 | } | |
| 890 | } | |
| 891 | { | |
| 892 | // Handle explicitly deleted decls from the source code. Not to be confused | |
| 893 | // with when we delete decls because they are no longer referenced. | |
| 894 | var it = deleted_decls.iterator(); | |
| 895 | while (it.next()) |kv| { | |
| 896 | //std.debug.warn("noticed '{}' deleted from source\n", .{kv.key.name}); | |
| 897 | try self.deleteDecl(kv.key); | |
| 804 | 898 | } |
| 805 | 899 | } |
| 900 | for (exports_to_resolve.items) |export_inst| { | |
| 901 | _ = try self.resolveDecl(&root_scope.base, export_inst); | |
| 902 | } | |
| 806 | 903 | }, |
| 807 | 904 | } |
| 808 | 905 | } |
| 809 | 906 | |
| 907 | fn deleteDecl(self: *Module, decl: *Decl) !void { | |
| 908 | //std.debug.warn("deleting decl '{}'\n", .{decl.name}); | |
| 909 | const name_hash = decl.fullyQualifiedNameHash(); | |
| 910 | self.decl_table.removeAssertDiscard(name_hash); | |
| 911 | // Remove itself from its dependencies, because we are about to destroy the decl pointer. | |
| 912 | for (decl.dependencies.items) |dep| { | |
| 913 | dep.removeDependant(decl); | |
| 914 | if (dep.dependants.items.len == 0) { | |
| 915 | // We don't recursively perform a deletion here, because during the update, | |
| 916 | // another reference to it may turn up. | |
| 917 | assert(!dep.deletion_flag); | |
| 918 | dep.deletion_flag = true; | |
| 919 | try self.deletion_set.append(self.allocator, dep); | |
| 920 | } | |
| 921 | } | |
| 922 | // Anything that depends on this deleted decl certainly needs to be re-analyzed. | |
| 923 | for (decl.dependants.items) |dep| { | |
| 924 | dep.removeDependency(decl); | |
| 925 | if (dep.analysis != .outdated) { | |
| 926 | dep.analysis = .outdated; | |
| 927 | try self.work_queue.writeItem(.{ .re_analyze_decl = dep }); | |
| 928 | } | |
| 929 | } | |
| 930 | self.deleteDeclExports(decl); | |
| 931 | self.bin_file.freeDecl(decl); | |
| 932 | decl.destroy(self.allocator); | |
| 933 | } | |
| 934 | ||
| 935 | /// Delete all the Export objects that are caused by this Decl. Re-analysis of | |
| 936 | /// this Decl will cause them to be re-created (or not). | |
| 937 | fn deleteDeclExports(self: *Module, decl: *Decl) void { | |
| 938 | const kv = self.export_owners.remove(decl) orelse return; | |
| 939 | ||
| 940 | for (kv.value) |exp| { | |
| 941 | if (self.decl_exports.get(exp.exported_decl)) |decl_exports_kv| { | |
| 942 | // Remove exports with owner_decl matching the regenerating decl. | |
| 943 | const list = decl_exports_kv.value; | |
| 944 | var i: usize = 0; | |
| 945 | var new_len = list.len; | |
| 946 | while (i < new_len) { | |
| 947 | if (list[i].owner_decl == decl) { | |
| 948 | mem.copyBackwards(*Export, list[i..], list[i + 1..new_len]); | |
| 949 | new_len -= 1; | |
| 950 | } else { | |
| 951 | i += 1; | |
| 952 | } | |
| 953 | } | |
| 954 | decl_exports_kv.value = self.allocator.shrink(list, new_len); | |
| 955 | if (new_len == 0) { | |
| 956 | self.decl_exports.removeAssertDiscard(exp.exported_decl); | |
| 957 | } | |
| 958 | } | |
| 959 | ||
| 960 | self.bin_file.deleteExport(exp.link); | |
| 961 | self.allocator.destroy(exp); | |
| 962 | } | |
| 963 | self.allocator.free(kv.value); | |
| 964 | } | |
| 965 | ||
| 810 | 966 | fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { |
| 811 | 967 | // Use the Decl's arena for function memory. |
| 812 | 968 | var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); |
| ... | ... | @@ -836,15 +992,111 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { |
| 836 | 992 | }; |
| 837 | 993 | } |
| 838 | 994 | |
| 839 | fn resolveDecl( | |
| 840 | self: *Module, | |
| 841 | scope: *Scope, | |
| 842 | old_inst: *zir.Inst, | |
| 843 | bin_file_link: link.ElfFile.Decl, | |
| 844 | ) InnerError!*Decl { | |
| 995 | fn reAnalyzeDecl(self: *Module, decl: *Decl, old_inst: *zir.Inst) InnerError!void { | |
| 996 | switch (decl.analysis) { | |
| 997 | .in_progress => unreachable, | |
| 998 | .dependency_failure, | |
| 999 | .sema_failure, | |
| 1000 | .codegen_failure, | |
| 1001 | .codegen_failure_retryable, | |
| 1002 | .complete, | |
| 1003 | => return, | |
| 1004 | ||
| 1005 | .outdated => {}, // Decl re-analysis | |
| 1006 | } | |
| 1007 | //std.debug.warn("re-analyzing {}\n", .{decl.name}); | |
| 1008 | decl.src = old_inst.src; | |
| 1009 | ||
| 1010 | // The exports this Decl performs will be re-discovered, so we remove them here | |
| 1011 | // prior to re-analysis. | |
| 1012 | self.deleteDeclExports(decl); | |
| 1013 | // Dependencies will be re-discovered, so we remove them here prior to re-analysis. | |
| 1014 | for (decl.dependencies.items) |dep| { | |
| 1015 | dep.removeDependant(decl); | |
| 1016 | if (dep.dependants.items.len == 0) { | |
| 1017 | // We don't perform a deletion here, because this Decl or another one | |
| 1018 | // may end up referencing it before the update is complete. | |
| 1019 | assert(!dep.deletion_flag); | |
| 1020 | dep.deletion_flag = true; | |
| 1021 | try self.deletion_set.append(self.allocator, dep); | |
| 1022 | } | |
| 1023 | } | |
| 1024 | decl.dependencies.shrink(self.allocator, 0); | |
| 1025 | var decl_scope: Scope.DeclAnalysis = .{ | |
| 1026 | .decl = decl, | |
| 1027 | .arena = std.heap.ArenaAllocator.init(self.allocator), | |
| 1028 | }; | |
| 1029 | errdefer decl_scope.arena.deinit(); | |
| 1030 | ||
| 1031 | const typed_value = self.analyzeInstConst(&decl_scope.base, old_inst) catch |err| switch (err) { | |
| 1032 | error.OutOfMemory => return error.OutOfMemory, | |
| 1033 | error.AnalysisFail => { | |
| 1034 | switch (decl.analysis) { | |
| 1035 | .in_progress => decl.analysis = .dependency_failure, | |
| 1036 | else => {}, | |
| 1037 | } | |
| 1038 | decl.generation = self.generation; | |
| 1039 | return error.AnalysisFail; | |
| 1040 | }, | |
| 1041 | }; | |
| 1042 | const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); | |
| 1043 | arena_state.* = decl_scope.arena.state; | |
| 1044 | ||
| 1045 | var prev_type_has_bits = false; | |
| 1046 | var type_changed = true; | |
| 1047 | ||
| 1048 | if (decl.typedValueManaged()) |tvm| { | |
| 1049 | prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); | |
| 1050 | type_changed = !tvm.typed_value.ty.eql(typed_value.ty); | |
| 1051 | ||
| 1052 | tvm.deinit(self.allocator); | |
| 1053 | } | |
| 1054 | decl.typed_value = .{ | |
| 1055 | .most_recent = .{ | |
| 1056 | .typed_value = typed_value, | |
| 1057 | .arena = arena_state, | |
| 1058 | }, | |
| 1059 | }; | |
| 1060 | decl.analysis = .complete; | |
| 1061 | decl.generation = self.generation; | |
| 1062 | if (typed_value.ty.hasCodeGenBits()) { | |
| 1063 | // We don't fully codegen the decl until later, but we do need to reserve a global | |
| 1064 | // offset table index for it. This allows us to codegen decls out of dependency order, | |
| 1065 | // increasing how many computations can be done in parallel. | |
| 1066 | try self.bin_file.allocateDeclIndexes(decl); | |
| 1067 | try self.work_queue.writeItem(.{ .codegen_decl = decl }); | |
| 1068 | } else if (prev_type_has_bits) { | |
| 1069 | self.bin_file.freeDecl(decl); | |
| 1070 | } | |
| 1071 | ||
| 1072 | // If the decl is a function, and the type is the same, we do not need | |
| 1073 | // to chase the dependants. | |
| 1074 | if (type_changed or typed_value.val.tag() != .function) { | |
| 1075 | for (decl.dependants.items) |dep| { | |
| 1076 | switch (dep.analysis) { | |
| 1077 | .in_progress => unreachable, | |
| 1078 | .outdated => continue, // already queued for update | |
| 1079 | ||
| 1080 | .dependency_failure, | |
| 1081 | .sema_failure, | |
| 1082 | .codegen_failure, | |
| 1083 | .codegen_failure_retryable, | |
| 1084 | .complete, | |
| 1085 | => if (dep.generation != self.generation) { | |
| 1086 | dep.analysis = .outdated; | |
| 1087 | try self.work_queue.writeItem(.{ .re_analyze_decl = dep }); | |
| 1088 | }, | |
| 1089 | } | |
| 1090 | } | |
| 1091 | } | |
| 1092 | } | |
| 1093 | ||
| 1094 | fn resolveDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { | |
| 845 | 1095 | const hash = Decl.hashSimpleName(old_inst.name); |
| 846 | 1096 | if (self.decl_table.get(hash)) |kv| { |
| 847 | return kv.value; | |
| 1097 | const decl = kv.value; | |
| 1098 | try self.reAnalyzeDecl(decl, old_inst); | |
| 1099 | return decl; | |
| 848 | 1100 | } else { |
| 849 | 1101 | const new_decl = blk: { |
| 850 | 1102 | try self.decl_table.ensureCapacity(self.decl_table.size + 1); |
| ... | ... | @@ -857,9 +1109,11 @@ fn resolveDecl( |
| 857 | 1109 | .scope = scope.namespace(), |
| 858 | 1110 | .src = old_inst.src, |
| 859 | 1111 | .typed_value = .{ .never_succeeded = {} }, |
| 860 | .analysis = .initial_in_progress, | |
| 1112 | .analysis = .in_progress, | |
| 1113 | .deletion_flag = false, | |
| 861 | 1114 | .contents_hash = Decl.hashSimpleName(old_inst.contents), |
| 862 | .link = bin_file_link, | |
| 1115 | .link = link.ElfFile.TextBlock.empty, | |
| 1116 | .generation = 0, | |
| 863 | 1117 | }; |
| 864 | 1118 | self.decl_table.putAssumeCapacityNoClobber(hash, new_decl); |
| 865 | 1119 | break :blk new_decl; |
| ... | ... | @@ -875,10 +1129,10 @@ fn resolveDecl( |
| 875 | 1129 | error.OutOfMemory => return error.OutOfMemory, |
| 876 | 1130 | error.AnalysisFail => { |
| 877 | 1131 | switch (new_decl.analysis) { |
| 878 | .initial_in_progress => new_decl.analysis = .initial_dependency_failure, | |
| 879 | .repeat_in_progress => new_decl.analysis = .repeat_dependency_failure, | |
| 1132 | .in_progress => new_decl.analysis = .dependency_failure, | |
| 880 | 1133 | else => {}, |
| 881 | 1134 | } |
| 1135 | new_decl.generation = self.generation; | |
| 882 | 1136 | return error.AnalysisFail; |
| 883 | 1137 | }, |
| 884 | 1138 | }; |
| ... | ... | @@ -893,34 +1147,37 @@ fn resolveDecl( |
| 893 | 1147 | }, |
| 894 | 1148 | }; |
| 895 | 1149 | new_decl.analysis = .complete; |
| 1150 | new_decl.generation = self.generation; | |
| 896 | 1151 | if (typed_value.ty.hasCodeGenBits()) { |
| 897 | 1152 | // We don't fully codegen the decl until later, but we do need to reserve a global |
| 898 | 1153 | // offset table index for it. This allows us to codegen decls out of dependency order, |
| 899 | 1154 | // increasing how many computations can be done in parallel. |
| 900 | 1155 | try self.bin_file.allocateDeclIndexes(new_decl); |
| 901 | ||
| 902 | // We ensureCapacity when scanning for decls. | |
| 903 | self.work_queue.writeItemAssumeCapacity(.{ .codegen_decl = new_decl }); | |
| 1156 | try self.work_queue.writeItem(.{ .codegen_decl = new_decl }); | |
| 904 | 1157 | } |
| 905 | 1158 | return new_decl; |
| 906 | 1159 | } |
| 907 | 1160 | } |
| 908 | 1161 | |
| 1162 | /// Declares a dependency on the decl. | |
| 909 | 1163 | fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { |
| 910 | const decl = try self.resolveDecl(scope, old_inst, link.ElfFile.Decl.empty); | |
| 1164 | const decl = try self.resolveDecl(scope, old_inst); | |
| 911 | 1165 | switch (decl.analysis) { |
| 912 | .initial_in_progress => unreachable, | |
| 913 | .repeat_in_progress => unreachable, | |
| 914 | .initial_dependency_failure, | |
| 915 | .repeat_dependency_failure, | |
| 916 | .initial_sema_failure, | |
| 917 | .repeat_sema_failure, | |
| 1166 | .in_progress => unreachable, | |
| 1167 | .outdated => unreachable, | |
| 1168 | ||
| 1169 | .dependency_failure, | |
| 1170 | .sema_failure, | |
| 918 | 1171 | .codegen_failure, |
| 919 | 1172 | .codegen_failure_retryable, |
| 920 | 1173 | => return error.AnalysisFail, |
| 921 | 1174 | |
| 922 | .complete => return decl, | |
| 1175 | .complete => {}, | |
| 923 | 1176 | } |
| 1177 | if (scope.decl()) |scope_decl| { | |
| 1178 | try self.declareDeclDependency(scope_decl, decl); | |
| 1179 | } | |
| 1180 | return decl; | |
| 924 | 1181 | } |
| 925 | 1182 | |
| 926 | 1183 | fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { |
| ... | ... | @@ -998,13 +1255,14 @@ fn analyzeExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) In |
| 998 | 1255 | const new_export = try self.allocator.create(Export); |
| 999 | 1256 | errdefer self.allocator.destroy(new_export); |
| 1000 | 1257 | |
| 1001 | const owner_decl = scope.decl(); | |
| 1258 | const owner_decl = scope.decl().?; | |
| 1002 | 1259 | |
| 1003 | 1260 | new_export.* = .{ |
| 1004 | 1261 | .options = .{ .name = symbol_name }, |
| 1005 | 1262 | .src = export_inst.base.src, |
| 1006 | 1263 | .link = .{}, |
| 1007 | 1264 | .owner_decl = owner_decl, |
| 1265 | .exported_decl = exported_decl, | |
| 1008 | 1266 | .status = .in_progress, |
| 1009 | 1267 | }; |
| 1010 | 1268 | |
| ... | ... | @@ -1327,7 +1585,7 @@ fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError |
| 1327 | 1585 | new_func.* = .{ |
| 1328 | 1586 | .fn_type = fn_type, |
| 1329 | 1587 | .analysis = .{ .queued = fn_inst }, |
| 1330 | .owner_decl = scope.decl(), | |
| 1588 | .owner_decl = scope.decl().?, | |
| 1331 | 1589 | }; |
| 1332 | 1590 | const fn_payload = try scope.arena().create(Value.Payload.Function); |
| 1333 | 1591 | fn_payload.* = .{ .func = new_func }; |
| ... | ... | @@ -2024,11 +2282,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err |
| 2024 | 2282 | switch (scope.tag) { |
| 2025 | 2283 | .decl => { |
| 2026 | 2284 | const decl = scope.cast(Scope.DeclAnalysis).?.decl; |
| 2027 | switch (decl.analysis) { | |
| 2028 | .initial_in_progress => decl.analysis = .initial_sema_failure, | |
| 2029 | .repeat_in_progress => decl.analysis = .repeat_sema_failure, | |
| 2030 | else => unreachable, | |
| 2031 | } | |
| 2285 | decl.analysis = .sema_failure; | |
| 2032 | 2286 | self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg); |
| 2033 | 2287 | }, |
| 2034 | 2288 | .block => { |
src-self-hosted/link.zig+330-147| ... | ... | @@ -126,6 +126,10 @@ pub const ElfFile = struct { |
| 126 | 126 | local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, |
| 127 | 127 | global_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, |
| 128 | 128 | |
| 129 | local_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){}, | |
| 130 | global_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){}, | |
| 131 | offset_table_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){}, | |
| 132 | ||
| 129 | 133 | /// Same order as in the file. The value is the absolute vaddr value. |
| 130 | 134 | /// If the vaddr of the executable program header changes, the entire |
| 131 | 135 | /// offset table needs to be rewritten. |
| ... | ... | @@ -138,11 +142,39 @@ pub const ElfFile = struct { |
| 138 | 142 | |
| 139 | 143 | error_flags: ErrorFlags = ErrorFlags{}, |
| 140 | 144 | |
| 145 | /// A list of text blocks that have surplus capacity. This list can have false | |
| 146 | /// positives, as functions grow and shrink over time, only sometimes being added | |
| 147 | /// or removed from the freelist. | |
| 148 | /// | |
| 149 | /// A text block has surplus capacity when its overcapacity value is greater than | |
| 150 | /// minimum_text_block_size * alloc_num / alloc_den. That is, when it has so | |
| 151 | /// much extra capacity, that we could fit a small new symbol in it, itself with | |
| 152 | /// ideal_capacity or more. | |
| 153 | /// | |
| 154 | /// Ideal capacity is defined by size * alloc_num / alloc_den. | |
| 155 | /// | |
| 156 | /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that | |
| 157 | /// overcapacity can be negative. A simple way to have negative overcapacity is to | |
| 158 | /// allocate a fresh text block, which will have ideal capacity, and then grow it | |
| 159 | /// by 1 byte. It will then have -1 overcapacity. | |
| 160 | text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){}, | |
| 161 | last_text_block: ?*TextBlock = null, | |
| 162 | ||
| 163 | /// `alloc_num / alloc_den` is the factor of padding when allocating. | |
| 164 | const alloc_num = 4; | |
| 165 | const alloc_den = 3; | |
| 166 | ||
| 167 | /// In order for a slice of bytes to be considered eligible to keep metadata pointing at | |
| 168 | /// it as a possible place to put new symbols, it must have enough room for this many bytes | |
| 169 | /// (plus extra for reserved capacity). | |
| 170 | const minimum_text_block_size = 64; | |
| 171 | const min_text_capacity = minimum_text_block_size * alloc_num / alloc_den; | |
| 172 | ||
| 141 | 173 | pub const ErrorFlags = struct { |
| 142 | 174 | no_entry_point_found: bool = false, |
| 143 | 175 | }; |
| 144 | 176 | |
| 145 | pub const Decl = struct { | |
| 177 | pub const TextBlock = struct { | |
| 146 | 178 | /// Each decl always gets a local symbol with the fully qualified name. |
| 147 | 179 | /// The vaddr and size are found here directly. |
| 148 | 180 | /// The file offset is found by computing the vaddr offset from the section vaddr |
| ... | ... | @@ -152,11 +184,43 @@ pub const ElfFile = struct { |
| 152 | 184 | local_sym_index: u32, |
| 153 | 185 | /// This field is undefined for symbols with size = 0. |
| 154 | 186 | offset_table_index: u32, |
| 187 | /// Points to the previous and next neighbors, based on the `text_offset`. | |
| 188 | /// This can be used to find, for example, the capacity of this `TextBlock`. | |
| 189 | prev: ?*TextBlock, | |
| 190 | next: ?*TextBlock, | |
| 155 | 191 | |
| 156 | pub const empty = Decl{ | |
| 192 | pub const empty = TextBlock{ | |
| 157 | 193 | .local_sym_index = 0, |
| 158 | 194 | .offset_table_index = undefined, |
| 195 | .prev = null, | |
| 196 | .next = null, | |
| 159 | 197 | }; |
| 198 | ||
| 199 | /// Returns how much room there is to grow in virtual address space. | |
| 200 | /// File offset relocation happens transparently, so it is not included in | |
| 201 | /// this calculation. | |
| 202 | fn capacity(self: TextBlock, elf_file: ElfFile) u64 { | |
| 203 | const self_sym = elf_file.local_symbols.items[self.local_sym_index]; | |
| 204 | if (self.next) |next| { | |
| 205 | const next_sym = elf_file.local_symbols.items[next.local_sym_index]; | |
| 206 | return next_sym.st_value - self_sym.st_value; | |
| 207 | } else { | |
| 208 | // We are the last block. The capacity is limited only by virtual address space. | |
| 209 | return std.math.maxInt(u32) - self_sym.st_value; | |
| 210 | } | |
| 211 | } | |
| 212 | ||
| 213 | fn freeListEligible(self: TextBlock, elf_file: ElfFile) bool { | |
| 214 | // No need to keep a free list node for the last block. | |
| 215 | const next = self.next orelse return false; | |
| 216 | const self_sym = elf_file.local_symbols.items[self.local_sym_index]; | |
| 217 | const next_sym = elf_file.local_symbols.items[next.local_sym_index]; | |
| 218 | const cap = next_sym.st_value - self_sym.st_value; | |
| 219 | const ideal_cap = self_sym.st_size * alloc_num / alloc_den; | |
| 220 | if (cap <= ideal_cap) return false; | |
| 221 | const surplus = cap - ideal_cap; | |
| 222 | return surplus >= min_text_capacity; | |
| 223 | } | |
| 160 | 224 | }; |
| 161 | 225 | |
| 162 | 226 | pub const Export = struct { |
| ... | ... | @@ -169,6 +233,10 @@ pub const ElfFile = struct { |
| 169 | 233 | self.shstrtab.deinit(self.allocator); |
| 170 | 234 | self.local_symbols.deinit(self.allocator); |
| 171 | 235 | self.global_symbols.deinit(self.allocator); |
| 236 | self.global_symbol_free_list.deinit(self.allocator); | |
| 237 | self.local_symbol_free_list.deinit(self.allocator); | |
| 238 | self.offset_table_free_list.deinit(self.allocator); | |
| 239 | self.text_block_free_list.deinit(self.allocator); | |
| 172 | 240 | self.offset_table.deinit(self.allocator); |
| 173 | 241 | if (self.owns_file_handle) { |
| 174 | 242 | if (self.file) |f| f.close(); |
| ... | ... | @@ -193,10 +261,6 @@ pub const ElfFile = struct { |
| 193 | 261 | }); |
| 194 | 262 | } |
| 195 | 263 | |
| 196 | // `alloc_num / alloc_den` is the factor of padding when allocation | |
| 197 | const alloc_num = 4; | |
| 198 | const alloc_den = 3; | |
| 199 | ||
| 200 | 264 | /// Returns end pos of collision, if any. |
| 201 | 265 | fn detectAllocCollision(self: *ElfFile, start: u64, size: u64) ?u64 { |
| 202 | 266 | const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32; |
| ... | ... | @@ -448,6 +512,13 @@ pub const ElfFile = struct { |
| 448 | 512 | self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign); |
| 449 | 513 | self.phdr_table_dirty = true; |
| 450 | 514 | } |
| 515 | { | |
| 516 | // Iterate over symbols, populating free_list and last_text_block. | |
| 517 | if (self.local_symbols.items.len != 1) { | |
| 518 | @panic("TODO implement setting up free_list and last_text_block from existing ELF file"); | |
| 519 | } | |
| 520 | // We are starting with an empty file. The default values are correct, null and empty list. | |
| 521 | } | |
| 451 | 522 | } |
| 452 | 523 | |
| 453 | 524 | /// Commit pending changes and write headers. |
| ... | ... | @@ -577,7 +648,6 @@ pub const ElfFile = struct { |
| 577 | 648 | self.error_flags.no_entry_point_found = false; |
| 578 | 649 | try self.writeElfHeader(); |
| 579 | 650 | } |
| 580 | // TODO find end pos and truncate | |
| 581 | 651 | |
| 582 | 652 | // The point of flush() is to commit changes, so nothing should be dirty after this. |
| 583 | 653 | assert(!self.phdr_table_dirty); |
| ... | ... | @@ -709,100 +779,222 @@ pub const ElfFile = struct { |
| 709 | 779 | try self.file.?.pwriteAll(hdr_buf[0..index], 0); |
| 710 | 780 | } |
| 711 | 781 | |
| 712 | const AllocatedBlock = struct { | |
| 713 | vaddr: u64, | |
| 714 | file_offset: u64, | |
| 715 | size_capacity: u64, | |
| 716 | }; | |
| 782 | fn freeTextBlock(self: *ElfFile, text_block: *TextBlock) void { | |
| 783 | var already_have_free_list_node = false; | |
| 784 | { | |
| 785 | var i: usize = 0; | |
| 786 | while (i < self.text_block_free_list.items.len) { | |
| 787 | if (self.text_block_free_list.items[i] == text_block) { | |
| 788 | _ = self.text_block_free_list.swapRemove(i); | |
| 789 | continue; | |
| 790 | } | |
| 791 | if (self.text_block_free_list.items[i] == text_block.prev) { | |
| 792 | already_have_free_list_node = true; | |
| 793 | } | |
| 794 | i += 1; | |
| 795 | } | |
| 796 | } | |
| 717 | 797 | |
| 718 | fn allocateTextBlock(self: *ElfFile, new_block_size: u64, alignment: u64) !AllocatedBlock { | |
| 719 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | |
| 720 | const shdr = &self.sections.items[self.text_section_index.?]; | |
| 798 | if (self.last_text_block == text_block) { | |
| 799 | // TODO shrink the .text section size here | |
| 800 | self.last_text_block = text_block.prev; | |
| 801 | } | |
| 721 | 802 | |
| 722 | // TODO Also detect virtual address collisions. | |
| 723 | const text_capacity = self.allocatedSize(shdr.sh_offset); | |
| 724 | // TODO instead of looping here, maintain a free list and a pointer to the end. | |
| 725 | var last_start: u64 = phdr.p_vaddr; | |
| 726 | var last_size: u64 = 0; | |
| 727 | for (self.local_symbols.items) |sym| { | |
| 728 | if (sym.st_value + sym.st_size > last_start + last_size) { | |
| 729 | last_start = sym.st_value; | |
| 730 | last_size = sym.st_size; | |
| 803 | if (text_block.prev) |prev| { | |
| 804 | prev.next = text_block.next; | |
| 805 | ||
| 806 | if (!already_have_free_list_node and prev.freeListEligible(self.*)) { | |
| 807 | // The free list is heuristics, it doesn't have to be perfect, so we can | |
| 808 | // ignore the OOM here. | |
| 809 | self.text_block_free_list.append(self.allocator, prev) catch {}; | |
| 731 | 810 | } |
| 811 | } else { | |
| 812 | text_block.prev = null; | |
| 732 | 813 | } |
| 733 | const end_vaddr = last_start + (last_size * alloc_num / alloc_den); | |
| 734 | const aligned_start_vaddr = mem.alignForwardGeneric(u64, end_vaddr, alignment); | |
| 735 | const needed_size = (aligned_start_vaddr + new_block_size) - phdr.p_vaddr; | |
| 736 | if (needed_size > text_capacity) { | |
| 737 | // Must move the entire text section. | |
| 738 | const new_offset = self.findFreeSpace(needed_size, 0x1000); | |
| 739 | const text_size = (last_start + last_size) - phdr.p_vaddr; | |
| 740 | const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, text_size); | |
| 741 | if (amt != text_size) return error.InputOutput; | |
| 742 | shdr.sh_offset = new_offset; | |
| 743 | phdr.p_offset = new_offset; | |
| 814 | ||
| 815 | if (text_block.next) |next| { | |
| 816 | next.prev = text_block.prev; | |
| 817 | } else { | |
| 818 | text_block.next = null; | |
| 744 | 819 | } |
| 745 | // Now that we know the code size, we need to update the program header for executable code | |
| 746 | shdr.sh_size = needed_size; | |
| 747 | phdr.p_memsz = needed_size; | |
| 748 | phdr.p_filesz = needed_size; | |
| 749 | ||
| 750 | self.phdr_table_dirty = true; // TODO look into making only the one program header dirty | |
| 751 | self.shdr_table_dirty = true; // TODO look into making only the one section dirty | |
| 752 | ||
| 753 | return AllocatedBlock{ | |
| 754 | .vaddr = aligned_start_vaddr, | |
| 755 | .file_offset = shdr.sh_offset + (aligned_start_vaddr - phdr.p_vaddr), | |
| 756 | .size_capacity = text_capacity - needed_size, | |
| 757 | }; | |
| 758 | 820 | } |
| 759 | 821 | |
| 760 | fn findAllocatedTextBlock(self: *ElfFile, sym: elf.Elf64_Sym) AllocatedBlock { | |
| 822 | fn shrinkTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64) void { | |
| 823 | // TODO check the new capacity, and if it crosses the size threshold into a big enough | |
| 824 | // capacity, insert a free list node for it. | |
| 825 | } | |
| 826 | ||
| 827 | fn growTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { | |
| 828 | const sym = self.local_symbols.items[text_block.local_sym_index]; | |
| 829 | const align_ok = mem.alignBackwardGeneric(u64, sym.st_value, alignment) == sym.st_value; | |
| 830 | const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*); | |
| 831 | if (!need_realloc) return sym.st_value; | |
| 832 | return self.allocateTextBlock(text_block, new_block_size, alignment); | |
| 833 | } | |
| 834 | ||
| 835 | fn allocateTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { | |
| 761 | 836 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; |
| 762 | 837 | const shdr = &self.sections.items[self.text_section_index.?]; |
| 838 | const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den; | |
| 839 | ||
| 840 | // We use these to indicate our intention to update metadata, placing the new block, | |
| 841 | // and possibly removing a free list node. | |
| 842 | // It would be simpler to do it inside the for loop below, but that would cause a | |
| 843 | // problem if an error was returned later in the function. So this action | |
| 844 | // is actually carried out at the end of the function, when errors are no longer possible. | |
| 845 | var block_placement: ?*TextBlock = null; | |
| 846 | var free_list_removal: ?usize = null; | |
| 847 | ||
| 848 | // First we look for an appropriately sized free list node. | |
| 849 | // The list is unordered. We'll just take the first thing that works. | |
| 850 | const vaddr = blk: { | |
| 851 | var i: usize = 0; | |
| 852 | while (i < self.text_block_free_list.items.len) { | |
| 853 | const big_block = self.text_block_free_list.items[i]; | |
| 854 | // We now have a pointer to a live text block that has too much capacity. | |
| 855 | // Is it enough that we could fit this new text block? | |
| 856 | const sym = self.local_symbols.items[big_block.local_sym_index]; | |
| 857 | const capacity = big_block.capacity(self.*); | |
| 858 | const ideal_capacity = capacity * alloc_num / alloc_den; | |
| 859 | const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity; | |
| 860 | const capacity_end_vaddr = sym.st_value + capacity; | |
| 861 | const new_start_vaddr_unaligned = capacity_end_vaddr - new_block_ideal_capacity; | |
| 862 | const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment); | |
| 863 | if (new_start_vaddr < ideal_capacity_end_vaddr) { | |
| 864 | // Additional bookkeeping here to notice if this free list node | |
| 865 | // should be deleted because the block that it points to has grown to take up | |
| 866 | // more of the extra capacity. | |
| 867 | if (!big_block.freeListEligible(self.*)) { | |
| 868 | _ = self.text_block_free_list.swapRemove(i); | |
| 869 | } else { | |
| 870 | i += 1; | |
| 871 | } | |
| 872 | continue; | |
| 873 | } | |
| 874 | // At this point we know that we will place the new block here. But the | |
| 875 | // remaining question is whether there is still yet enough capacity left | |
| 876 | // over for there to still be a free list node. | |
| 877 | const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr; | |
| 878 | const keep_free_list_node = remaining_capacity >= min_text_capacity; | |
| 879 | ||
| 880 | // Set up the metadata to be updated, after errors are no longer possible. | |
| 881 | block_placement = big_block; | |
| 882 | if (!keep_free_list_node) { | |
| 883 | free_list_removal = i; | |
| 884 | } | |
| 885 | break :blk new_start_vaddr; | |
| 886 | } else if (self.last_text_block) |last| { | |
| 887 | const sym = self.local_symbols.items[last.local_sym_index]; | |
| 888 | const ideal_capacity = sym.st_size * alloc_num / alloc_den; | |
| 889 | const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity; | |
| 890 | const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment); | |
| 891 | // Set up the metadata to be updated, after errors are no longer possible. | |
| 892 | block_placement = last; | |
| 893 | break :blk new_start_vaddr; | |
| 894 | } else { | |
| 895 | break :blk phdr.p_vaddr; | |
| 896 | } | |
| 897 | }; | |
| 898 | ||
| 899 | const expand_text_section = block_placement == null or block_placement.?.next == null; | |
| 900 | if (expand_text_section) { | |
| 901 | const text_capacity = self.allocatedSize(shdr.sh_offset); | |
| 902 | const needed_size = (vaddr + new_block_size) - phdr.p_vaddr; | |
| 903 | if (needed_size > text_capacity) { | |
| 904 | // Must move the entire text section. | |
| 905 | const new_offset = self.findFreeSpace(needed_size, 0x1000); | |
| 906 | const text_size = if (self.last_text_block) |last| blk: { | |
| 907 | const sym = self.local_symbols.items[last.local_sym_index]; | |
| 908 | break :blk (sym.st_value + sym.st_size) - phdr.p_vaddr; | |
| 909 | } else 0; | |
| 910 | const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, text_size); | |
| 911 | if (amt != text_size) return error.InputOutput; | |
| 912 | shdr.sh_offset = new_offset; | |
| 913 | phdr.p_offset = new_offset; | |
| 914 | } | |
| 915 | self.last_text_block = text_block; | |
| 763 | 916 | |
| 764 | // Find the next sym after this one. | |
| 765 | // TODO look into using a hash map to speed up perf. | |
| 766 | const text_capacity = self.allocatedSize(shdr.sh_offset); | |
| 767 | var next_vaddr_start = phdr.p_vaddr + text_capacity; | |
| 768 | for (self.local_symbols.items) |elem| { | |
| 769 | if (elem.st_value < sym.st_value) continue; | |
| 770 | if (elem.st_value < next_vaddr_start) next_vaddr_start = elem.st_value; | |
| 917 | shdr.sh_size = needed_size; | |
| 918 | phdr.p_memsz = needed_size; | |
| 919 | phdr.p_filesz = needed_size; | |
| 920 | ||
| 921 | self.phdr_table_dirty = true; // TODO look into making only the one program header dirty | |
| 922 | self.shdr_table_dirty = true; // TODO look into making only the one section dirty | |
| 771 | 923 | } |
| 772 | return .{ | |
| 773 | .vaddr = sym.st_value, | |
| 774 | .file_offset = shdr.sh_offset + (sym.st_value - phdr.p_vaddr), | |
| 775 | .size_capacity = next_vaddr_start - sym.st_value, | |
| 776 | }; | |
| 924 | ||
| 925 | // This function can also reallocate a text block. | |
| 926 | // In this case we need to "unplug" it from its previous location before | |
| 927 | // plugging it in to its new location. | |
| 928 | if (text_block.prev) |prev| { | |
| 929 | prev.next = text_block.next; | |
| 930 | } | |
| 931 | if (text_block.next) |next| { | |
| 932 | next.prev = text_block.prev; | |
| 933 | } | |
| 934 | ||
| 935 | if (block_placement) |big_block| { | |
| 936 | text_block.prev = big_block; | |
| 937 | text_block.next = big_block.next; | |
| 938 | big_block.next = text_block; | |
| 939 | } else { | |
| 940 | text_block.prev = null; | |
| 941 | text_block.next = null; | |
| 942 | } | |
| 943 | if (free_list_removal) |i| { | |
| 944 | _ = self.text_block_free_list.swapRemove(i); | |
| 945 | } | |
| 946 | return vaddr; | |
| 777 | 947 | } |
| 778 | 948 | |
| 779 | 949 | pub fn allocateDeclIndexes(self: *ElfFile, decl: *Module.Decl) !void { |
| 780 | 950 | if (decl.link.local_sym_index != 0) return; |
| 781 | 951 | |
| 952 | // Here we also ensure capacity for the free lists so that they can be appended to without fail. | |
| 782 | 953 | try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1); |
| 954 | try self.local_symbol_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); | |
| 783 | 955 | try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1); |
| 784 | const local_sym_index = self.local_symbols.items.len; | |
| 785 | const offset_table_index = self.offset_table.items.len; | |
| 956 | try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); | |
| 957 | ||
| 958 | if (self.local_symbol_free_list.popOrNull()) |i| { | |
| 959 | //std.debug.warn("reusing symbol index {} for {}\n", .{i, decl.name}); | |
| 960 | decl.link.local_sym_index = i; | |
| 961 | } else { | |
| 962 | //std.debug.warn("allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); | |
| 963 | decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); | |
| 964 | _ = self.local_symbols.addOneAssumeCapacity(); | |
| 965 | } | |
| 966 | ||
| 967 | if (self.offset_table_free_list.popOrNull()) |i| { | |
| 968 | decl.link.offset_table_index = i; | |
| 969 | } else { | |
| 970 | decl.link.offset_table_index = @intCast(u32, self.offset_table.items.len); | |
| 971 | _ = self.offset_table.addOneAssumeCapacity(); | |
| 972 | self.offset_table_count_dirty = true; | |
| 973 | } | |
| 974 | ||
| 786 | 975 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; |
| 787 | 976 | |
| 788 | self.local_symbols.appendAssumeCapacity(.{ | |
| 977 | self.local_symbols.items[decl.link.local_sym_index] = .{ | |
| 789 | 978 | .st_name = 0, |
| 790 | 979 | .st_info = 0, |
| 791 | 980 | .st_other = 0, |
| 792 | 981 | .st_shndx = 0, |
| 793 | 982 | .st_value = phdr.p_vaddr, |
| 794 | 983 | .st_size = 0, |
| 795 | }); | |
| 796 | errdefer self.local_symbols.shrink(self.allocator, self.local_symbols.items.len - 1); | |
| 797 | self.offset_table.appendAssumeCapacity(0); | |
| 798 | errdefer self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1); | |
| 984 | }; | |
| 985 | self.offset_table.items[decl.link.offset_table_index] = 0; | |
| 986 | } | |
| 799 | 987 | |
| 800 | self.offset_table_count_dirty = true; | |
| 988 | pub fn freeDecl(self: *ElfFile, decl: *Module.Decl) void { | |
| 989 | self.freeTextBlock(&decl.link); | |
| 990 | if (decl.link.local_sym_index != 0) { | |
| 991 | self.local_symbol_free_list.appendAssumeCapacity(decl.link.local_sym_index); | |
| 992 | self.offset_table_free_list.appendAssumeCapacity(decl.link.offset_table_index); | |
| 801 | 993 | |
| 802 | decl.link = .{ | |
| 803 | .local_sym_index = @intCast(u32, local_sym_index), | |
| 804 | .offset_table_index = @intCast(u32, offset_table_index), | |
| 805 | }; | |
| 994 | self.local_symbols.items[decl.link.local_sym_index].st_info = 0; | |
| 995 | ||
| 996 | decl.link.local_sym_index = 0; | |
| 997 | } | |
| 806 | 998 | } |
| 807 | 999 | |
| 808 | 1000 | pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void { |
| ... | ... | @@ -822,80 +1014,60 @@ pub const ElfFile = struct { |
| 822 | 1014 | |
| 823 | 1015 | const required_alignment = typed_value.ty.abiAlignment(self.options.target); |
| 824 | 1016 | |
| 825 | const file_offset = blk: { | |
| 826 | const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) { | |
| 827 | .Fn => elf.STT_FUNC, | |
| 828 | else => elf.STT_OBJECT, | |
| 829 | }; | |
| 1017 | const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) { | |
| 1018 | .Fn => elf.STT_FUNC, | |
| 1019 | else => elf.STT_OBJECT, | |
| 1020 | }; | |
| 830 | 1021 | |
| 831 | if (decl.link.local_sym_index != 0) { | |
| 832 | const local_sym = &self.local_symbols.items[decl.link.local_sym_index]; | |
| 833 | const existing_block = self.findAllocatedTextBlock(local_sym.*); | |
| 834 | const need_realloc = local_sym.st_size == 0 or | |
| 835 | code.len > existing_block.size_capacity or | |
| 836 | !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); | |
| 837 | // TODO check for collision with another symbol | |
| 838 | const file_offset = if (need_realloc) fo: { | |
| 839 | const new_block = try self.allocateTextBlock(code.len, required_alignment); | |
| 840 | local_sym.st_value = new_block.vaddr; | |
| 841 | self.offset_table.items[decl.link.offset_table_index] = new_block.vaddr; | |
| 842 | ||
| 843 | //std.debug.warn("{}: writing got index {}=0x{x}\n", .{ | |
| 844 | // decl.name, | |
| 845 | // decl.link.offset_table_index, | |
| 846 | // self.offset_table.items[decl.link.offset_table_index], | |
| 847 | //}); | |
| 1022 | assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes() | |
| 1023 | const local_sym = &self.local_symbols.items[decl.link.local_sym_index]; | |
| 1024 | if (local_sym.st_size != 0) { | |
| 1025 | const capacity = decl.link.capacity(self.*); | |
| 1026 | const need_realloc = code.len > capacity or | |
| 1027 | !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); | |
| 1028 | if (need_realloc) { | |
| 1029 | const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); | |
| 1030 | //std.debug.warn("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); | |
| 1031 | if (vaddr != local_sym.st_value) { | |
| 1032 | local_sym.st_value = vaddr; | |
| 1033 | ||
| 1034 | //std.debug.warn(" (writing new offset table entry)\n", .{}); | |
| 1035 | self.offset_table.items[decl.link.offset_table_index] = vaddr; | |
| 848 | 1036 | try self.writeOffsetTableEntry(decl.link.offset_table_index); |
| 849 | ||
| 850 | break :fo new_block.file_offset; | |
| 851 | } else existing_block.file_offset; | |
| 852 | local_sym.st_size = code.len; | |
| 853 | local_sym.st_name = try self.updateString(local_sym.st_name, mem.spanZ(decl.name)); | |
| 854 | local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits; | |
| 855 | local_sym.st_other = 0; | |
| 856 | local_sym.st_shndx = self.text_section_index.?; | |
| 857 | // TODO this write could be avoided if no fields of the symbol were changed. | |
| 858 | try self.writeSymbol(decl.link.local_sym_index); | |
| 859 | ||
| 860 | //std.debug.warn("updating {} at vaddr 0x{x}\n", .{ decl.name, local_sym.st_value }); | |
| 861 | break :blk file_offset; | |
| 862 | } else { | |
| 863 | try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1); | |
| 864 | try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1); | |
| 865 | const decl_name = mem.spanZ(decl.name); | |
| 866 | const name_str_index = try self.makeString(decl_name); | |
| 867 | const new_block = try self.allocateTextBlock(code.len, required_alignment); | |
| 868 | const local_sym_index = self.local_symbols.items.len; | |
| 869 | const offset_table_index = self.offset_table.items.len; | |
| 870 | ||
| 871 | //std.debug.warn("add symbol for {} at vaddr 0x{x}, size {}\n", .{ decl.name, new_block.vaddr, code.len }); | |
| 872 | self.local_symbols.appendAssumeCapacity(.{ | |
| 873 | .st_name = name_str_index, | |
| 874 | .st_info = (elf.STB_LOCAL << 4) | stt_bits, | |
| 875 | .st_other = 0, | |
| 876 | .st_shndx = self.text_section_index.?, | |
| 877 | .st_value = new_block.vaddr, | |
| 878 | .st_size = code.len, | |
| 879 | }); | |
| 880 | errdefer self.local_symbols.shrink(self.allocator, self.local_symbols.items.len - 1); | |
| 881 | self.offset_table.appendAssumeCapacity(new_block.vaddr); | |
| 882 | errdefer self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1); | |
| 883 | ||
| 884 | self.offset_table_count_dirty = true; | |
| 885 | ||
| 886 | try self.writeSymbol(local_sym_index); | |
| 887 | try self.writeOffsetTableEntry(offset_table_index); | |
| 888 | ||
| 889 | decl.link = .{ | |
| 890 | .local_sym_index = @intCast(u32, local_sym_index), | |
| 891 | .offset_table_index = @intCast(u32, offset_table_index), | |
| 892 | }; | |
| 893 | ||
| 894 | //std.debug.warn("writing new {} at vaddr 0x{x}\n", .{ decl.name, new_block.vaddr }); | |
| 895 | break :blk new_block.file_offset; | |
| 1037 | } | |
| 1038 | } else if (code.len < local_sym.st_size) { | |
| 1039 | self.shrinkTextBlock(&decl.link, code.len); | |
| 896 | 1040 | } |
| 897 | }; | |
| 1041 | local_sym.st_size = code.len; | |
| 1042 | local_sym.st_name = try self.updateString(local_sym.st_name, mem.spanZ(decl.name)); | |
| 1043 | local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits; | |
| 1044 | local_sym.st_other = 0; | |
| 1045 | local_sym.st_shndx = self.text_section_index.?; | |
| 1046 | // TODO this write could be avoided if no fields of the symbol were changed. | |
| 1047 | try self.writeSymbol(decl.link.local_sym_index); | |
| 1048 | } else { | |
| 1049 | const decl_name = mem.spanZ(decl.name); | |
| 1050 | const name_str_index = try self.makeString(decl_name); | |
| 1051 | const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); | |
| 1052 | //std.debug.warn("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); | |
| 1053 | errdefer self.freeTextBlock(&decl.link); | |
| 1054 | ||
| 1055 | local_sym.* = .{ | |
| 1056 | .st_name = name_str_index, | |
| 1057 | .st_info = (elf.STB_LOCAL << 4) | stt_bits, | |
| 1058 | .st_other = 0, | |
| 1059 | .st_shndx = self.text_section_index.?, | |
| 1060 | .st_value = vaddr, | |
| 1061 | .st_size = code.len, | |
| 1062 | }; | |
| 1063 | self.offset_table.items[decl.link.offset_table_index] = vaddr; | |
| 898 | 1064 | |
| 1065 | try self.writeSymbol(decl.link.local_sym_index); | |
| 1066 | try self.writeOffsetTableEntry(decl.link.offset_table_index); | |
| 1067 | } | |
| 1068 | ||
| 1069 | const section_offset = local_sym.st_value - self.program_headers.items[self.phdr_load_re_index.?].p_vaddr; | |
| 1070 | const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset; | |
| 899 | 1071 | try self.file.?.pwriteAll(code, file_offset); |
| 900 | 1072 | |
| 901 | 1073 | // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. |
| ... | ... | @@ -910,7 +1082,10 @@ pub const ElfFile = struct { |
| 910 | 1082 | decl: *const Module.Decl, |
| 911 | 1083 | exports: []const *Module.Export, |
| 912 | 1084 | ) !void { |
| 1085 | // In addition to ensuring capacity for global_symbols, we also ensure capacity for freeing all of | |
| 1086 | // them, so that deleting exports is guaranteed to succeed. | |
| 913 | 1087 | try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len); |
| 1088 | try self.global_symbol_free_list.ensureCapacity(self.allocator, self.global_symbols.items.len); | |
| 914 | 1089 | const typed_value = decl.typed_value.most_recent.typed_value; |
| 915 | 1090 | if (decl.link.local_sym_index == 0) return; |
| 916 | 1091 | const decl_sym = self.local_symbols.items[decl.link.local_sym_index]; |
| ... | ... | @@ -957,22 +1132,30 @@ pub const ElfFile = struct { |
| 957 | 1132 | }; |
| 958 | 1133 | } else { |
| 959 | 1134 | const name = try self.makeString(exp.options.name); |
| 960 | const i = self.global_symbols.items.len; | |
| 961 | self.global_symbols.appendAssumeCapacity(.{ | |
| 1135 | const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { | |
| 1136 | _ = self.global_symbols.addOneAssumeCapacity(); | |
| 1137 | break :blk self.global_symbols.items.len - 1; | |
| 1138 | }; | |
| 1139 | self.global_symbols.items[i] = .{ | |
| 962 | 1140 | .st_name = name, |
| 963 | 1141 | .st_info = (stb_bits << 4) | stt_bits, |
| 964 | 1142 | .st_other = 0, |
| 965 | 1143 | .st_shndx = self.text_section_index.?, |
| 966 | 1144 | .st_value = decl_sym.st_value, |
| 967 | 1145 | .st_size = decl_sym.st_size, |
| 968 | }); | |
| 969 | errdefer self.global_symbols.shrink(self.allocator, self.global_symbols.items.len - 1); | |
| 1146 | }; | |
| 970 | 1147 | |
| 971 | 1148 | exp.link.sym_index = @intCast(u32, i); |
| 972 | 1149 | } |
| 973 | 1150 | } |
| 974 | 1151 | } |
| 975 | 1152 | |
| 1153 | pub fn deleteExport(self: *ElfFile, exp: Export) void { | |
| 1154 | const sym_index = exp.sym_index orelse return; | |
| 1155 | self.global_symbol_free_list.appendAssumeCapacity(sym_index); | |
| 1156 | self.global_symbols.items[sym_index].st_info = 0; | |
| 1157 | } | |
| 1158 | ||
| 976 | 1159 | fn writeProgHeader(self: *ElfFile, index: usize) !void { |
| 977 | 1160 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 978 | 1161 | const offset = self.program_headers.items[index].p_offset; |
src-self-hosted/type.zig+27-11| ... | ... | @@ -92,13 +92,13 @@ pub const Type = extern union { |
| 92 | 92 | return @fieldParentPtr(T, "base", self.ptr_otherwise); |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | pub fn eql(self: Type, other: Type) bool { | |
| 96 | //std.debug.warn("test {} == {}\n", .{ self, other }); | |
| 95 | pub fn eql(a: Type, b: Type) bool { | |
| 96 | //std.debug.warn("test {} == {}\n", .{ a, b }); | |
| 97 | 97 | // As a shortcut, if the small tags / addresses match, we're done. |
| 98 | if (self.tag_if_small_enough == other.tag_if_small_enough) | |
| 98 | if (a.tag_if_small_enough == b.tag_if_small_enough) | |
| 99 | 99 | return true; |
| 100 | const zig_tag_a = self.zigTypeTag(); | |
| 101 | const zig_tag_b = self.zigTypeTag(); | |
| 100 | const zig_tag_a = a.zigTypeTag(); | |
| 101 | const zig_tag_b = b.zigTypeTag(); | |
| 102 | 102 | if (zig_tag_a != zig_tag_b) |
| 103 | 103 | return false; |
| 104 | 104 | switch (zig_tag_a) { |
| ... | ... | @@ -111,24 +111,40 @@ pub const Type = extern union { |
| 111 | 111 | .Undefined => return true, |
| 112 | 112 | .Null => return true, |
| 113 | 113 | .Pointer => { |
| 114 | const is_slice_a = isSlice(self); | |
| 115 | const is_slice_b = isSlice(other); | |
| 114 | const is_slice_a = isSlice(a); | |
| 115 | const is_slice_b = isSlice(b); | |
| 116 | 116 | if (is_slice_a != is_slice_b) |
| 117 | 117 | return false; |
| 118 | 118 | @panic("TODO implement more pointer Type equality comparison"); |
| 119 | 119 | }, |
| 120 | 120 | .Int => { |
| 121 | if (self.tag() != other.tag()) { | |
| 121 | if (a.tag() != b.tag()) { | |
| 122 | 122 | // Detect that e.g. u64 != usize, even if the bits match on a particular target. |
| 123 | 123 | return false; |
| 124 | 124 | } |
| 125 | 125 | // The target will not be branched upon, because we handled target-dependent cases above. |
| 126 | const info_a = self.intInfo(@as(Target, undefined)); | |
| 127 | const info_b = self.intInfo(@as(Target, undefined)); | |
| 126 | const info_a = a.intInfo(@as(Target, undefined)); | |
| 127 | const info_b = b.intInfo(@as(Target, undefined)); | |
| 128 | 128 | return info_a.signed == info_b.signed and info_a.bits == info_b.bits; |
| 129 | 129 | }, |
| 130 | .Array => { | |
| 131 | if (a.arrayLen() != b.arrayLen()) | |
| 132 | return false; | |
| 133 | if (a.elemType().eql(b.elemType())) | |
| 134 | return false; | |
| 135 | const sentinel_a = a.arraySentinel(); | |
| 136 | const sentinel_b = b.arraySentinel(); | |
| 137 | if (sentinel_a) |sa| { | |
| 138 | if (sentinel_b) |sb| { | |
| 139 | return sa.eql(sb); | |
| 140 | } else { | |
| 141 | return false; | |
| 142 | } | |
| 143 | } else { | |
| 144 | return sentinel_b == null; | |
| 145 | } | |
| 146 | }, | |
| 130 | 147 | .Float, |
| 131 | .Array, | |
| 132 | 148 | .Struct, |
| 133 | 149 | .Optional, |
| 134 | 150 | .ErrorUnion, |
src-self-hosted/value.zig+5| ... | ... | @@ -666,6 +666,11 @@ pub const Value = extern union { |
| 666 | 666 | return orderAgainstZero(lhs).compare(op); |
| 667 | 667 | } |
| 668 | 668 | |
| 669 | pub fn eql(a: Value, b: Value) bool { | |
| 670 | // TODO non numerical comparisons | |
| 671 | return compare(a, .eq, b); | |
| 672 | } | |
| 673 | ||
| 669 | 674 | pub fn toBool(self: Value) bool { |
| 670 | 675 | return switch (self.tag()) { |
| 671 | 676 | .bool_true => true, |
src-self-hosted/zir.zig+10| ... | ... | @@ -442,6 +442,16 @@ pub const Module = struct { |
| 442 | 442 | |
| 443 | 443 | const InstPtrTable = std.AutoHashMap(*Inst, struct { index: usize, fn_body: ?*Module.Body }); |
| 444 | 444 | |
| 445 | /// TODO Look into making a table to speed this up. | |
| 446 | pub fn findDecl(self: Module, name: []const u8) ?*Inst { | |
| 447 | for (self.decls) |decl| { | |
| 448 | if (mem.eql(u8, decl.name, name)) { | |
| 449 | return decl; | |
| 450 | } | |
| 451 | } | |
| 452 | return null; | |
| 453 | } | |
| 454 | ||
| 445 | 455 | /// The allocator is used for temporary storage, but this function always returns |
| 446 | 456 | /// with no resources allocated. |
| 447 | 457 | pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void { |
test/stage2/zir.zig+71| ... | ... | @@ -200,6 +200,73 @@ pub fn addCases(ctx: *TestContext) void { |
| 200 | 200 | \\@9 = str("_start") |
| 201 | 201 | \\@10 = ref(@9) |
| 202 | 202 | \\@11 = export(@10, @start) |
| 203 | , | |
| 204 | \\@noreturn = primitive(noreturn) | |
| 205 | \\@void = primitive(void) | |
| 206 | \\@usize = primitive(usize) | |
| 207 | \\@0 = int(0) | |
| 208 | \\@1 = int(1) | |
| 209 | \\@2 = int(2) | |
| 210 | \\@3 = int(3) | |
| 211 | \\ | |
| 212 | \\@syscall_array = str("syscall") | |
| 213 | \\@sysoutreg_array = str("={rax}") | |
| 214 | \\@rax_array = str("{rax}") | |
| 215 | \\@rdi_array = str("{rdi}") | |
| 216 | \\@rcx_array = str("rcx") | |
| 217 | \\@r11_array = str("r11") | |
| 218 | \\@rdx_array = str("{rdx}") | |
| 219 | \\@rsi_array = str("{rsi}") | |
| 220 | \\@memory_array = str("memory") | |
| 221 | \\@len_array = str("len") | |
| 222 | \\ | |
| 223 | \\@msg = str("Hello, world!\n") | |
| 224 | \\@msg2 = str("Editing the same msg2 decl but this time with a much longer message which will\ncause the data to need to be relocated in virtual address space.\n") | |
| 225 | \\ | |
| 226 | \\@start_fnty = fntype([], @noreturn, cc=Naked) | |
| 227 | \\@start = fn(@start_fnty, { | |
| 228 | \\ %SYS_exit_group = int(231) | |
| 229 | \\ %exit_code = as(@usize, @0) | |
| 230 | \\ | |
| 231 | \\ %syscall = ref(@syscall_array) | |
| 232 | \\ %sysoutreg = ref(@sysoutreg_array) | |
| 233 | \\ %rax = ref(@rax_array) | |
| 234 | \\ %rdi = ref(@rdi_array) | |
| 235 | \\ %rcx = ref(@rcx_array) | |
| 236 | \\ %rdx = ref(@rdx_array) | |
| 237 | \\ %rsi = ref(@rsi_array) | |
| 238 | \\ %r11 = ref(@r11_array) | |
| 239 | \\ %memory = ref(@memory_array) | |
| 240 | \\ | |
| 241 | \\ %SYS_write = as(@usize, @1) | |
| 242 | \\ %STDOUT_FILENO = as(@usize, @1) | |
| 243 | \\ | |
| 244 | \\ %msg_ptr = ref(@msg2) | |
| 245 | \\ %msg_addr = ptrtoint(%msg_ptr) | |
| 246 | \\ | |
| 247 | \\ %len_name = ref(@len_array) | |
| 248 | \\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name) | |
| 249 | \\ %msg_len = deref(%msg_len_ptr) | |
| 250 | \\ %rc_write = asm(%syscall, @usize, | |
| 251 | \\ volatile=1, | |
| 252 | \\ output=%sysoutreg, | |
| 253 | \\ inputs=[%rax, %rdi, %rsi, %rdx], | |
| 254 | \\ clobbers=[%rcx, %r11, %memory], | |
| 255 | \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len]) | |
| 256 | \\ | |
| 257 | \\ %rc_exit = asm(%syscall, @usize, | |
| 258 | \\ volatile=1, | |
| 259 | \\ output=%sysoutreg, | |
| 260 | \\ inputs=[%rax, %rdi], | |
| 261 | \\ clobbers=[%rcx, %r11, %memory], | |
| 262 | \\ args=[%SYS_exit_group, %exit_code]) | |
| 263 | \\ | |
| 264 | \\ %99 = unreachable() | |
| 265 | \\}); | |
| 266 | \\ | |
| 267 | \\@9 = str("_start") | |
| 268 | \\@10 = ref(@9) | |
| 269 | \\@11 = export(@10, @start) | |
| 203 | 270 | }, |
| 204 | 271 | &[_][]const u8{ |
| 205 | 272 | \\Hello, world! |
| ... | ... | @@ -207,6 +274,10 @@ pub fn addCases(ctx: *TestContext) void { |
| 207 | 274 | , |
| 208 | 275 | \\HELL WORLD |
| 209 | 276 | \\ |
| 277 | , | |
| 278 | \\Editing the same msg2 decl but this time with a much longer message which will | |
| 279 | \\cause the data to need to be relocated in virtual address space. | |
| 280 | \\ | |
| 210 | 281 | }, |
| 211 | 282 | ); |
| 212 | 283 |