| 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,9 +55,20 @@ failed_files: std.AutoHashMap(*Scope.ZIRModule, *ErrorMsg), |
| 55 | /// The ErrorMsg memory is owned by the `Export`, using Module's allocator. | 55 | /// The ErrorMsg memory is owned by the `Export`, using Module's allocator. |
| 56 | failed_exports: std.AutoHashMap(*Export, *ErrorMsg), | 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 | pub const WorkItem = union(enum) { | 67 | pub const WorkItem = union(enum) { |
| 59 | /// Write the machine code for a Decl to the output file. | 68 | /// Write the machine code for a Decl to the output file. |
| 60 | codegen_decl: *Decl, | 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 | pub const Export = struct { | 74 | pub const Export = struct { |
| ... | @@ -68,6 +79,8 @@ pub const Export = struct { | ... | @@ -68,6 +79,8 @@ pub const Export = struct { |
| 68 | link: link.ElfFile.Export, | 79 | link: link.ElfFile.Export, |
| 69 | /// The Decl that performs the export. Note that this is *not* the Decl being exported. | 80 | /// The Decl that performs the export. Note that this is *not* the Decl being exported. |
| 70 | owner_decl: *Decl, | 81 | owner_decl: *Decl, |
| 82 | /// The Decl being exported. Note this is *not* the Decl performing the export. | ||
| 83 | exported_decl: *Decl, | ||
| 71 | status: enum { | 84 | status: enum { |
| 72 | in_progress, | 85 | in_progress, |
| 73 | failed, | 86 | failed, |
| ... | @@ -94,8 +107,7 @@ pub const Decl = struct { | ... | @@ -94,8 +107,7 @@ pub const Decl = struct { |
| 94 | /// This is the base offset that src offsets within this Decl are relative to. | 107 | /// This is the base offset that src offsets within this Decl are relative to. |
| 95 | src: usize, | 108 | src: usize, |
| 96 | /// The most recent value of the Decl after a successful semantic analysis. | 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. | 110 | typed_value: union(enum) { |
| 98 | typed_value: union { | ||
| 99 | never_succeeded: void, | 111 | never_succeeded: void, |
| 100 | most_recent: TypedValue.Managed, | 112 | most_recent: TypedValue.Managed, |
| 101 | }, | 113 | }, |
| ... | @@ -104,50 +116,56 @@ pub const Decl = struct { | ... | @@ -104,50 +116,56 @@ pub const Decl = struct { |
| 104 | /// analysis of the function body is performed with this value set to `success`. Functions | 116 | /// analysis of the function body is performed with this value set to `success`. Functions |
| 105 | /// have their own analysis status field. | 117 | /// have their own analysis status field. |
| 106 | analysis: enum { | 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 | /// This Decl might be OK but it depends on another one which did not successfully complete | 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. | 122 | /// semantic analysis. |
| 110 | initial_dependency_failure, | 123 | dependency_failure, |
| 111 | /// Semantic analysis failure. This Decl never had a value computed. | 124 | /// Semantic analysis failure. |
| 112 | /// There will be a corresponding ErrorMsg in Module.failed_decls. | 125 | /// There will be a corresponding ErrorMsg in Module.failed_decls. |
| 113 | initial_sema_failure, | 126 | sema_failure, |
| 114 | /// In this case the `typed_value.most_recent` can still be accessed. | ||
| 115 | /// There will be a corresponding ErrorMsg in Module.failed_decls. | 127 | /// There will be a corresponding ErrorMsg in Module.failed_decls. |
| 116 | codegen_failure, | 128 | codegen_failure, |
| 117 | /// In this case the `typed_value.most_recent` can still be accessed. | ||
| 118 | /// There will be a corresponding ErrorMsg in Module.failed_decls. | 129 | /// There will be a corresponding ErrorMsg in Module.failed_decls. |
| 119 | /// This indicates the failure was something like running out of disk space, | 130 | /// This indicates the failure was something like running out of disk space, |
| 120 | /// and attempting codegen again may succeed. | 131 | /// and attempting codegen again may succeed. |
| 121 | codegen_failure_retryable, | 132 | codegen_failure_retryable, |
| 122 | /// This Decl might be OK but it depends on another one which did not successfully complete | 133 | /// Everything is done. During an update, this Decl may be out of date, depending |
| 123 | /// semantic analysis. There is a most recent value available. | 134 | /// on its dependencies. The `generation` field can be used to determine if this |
| 124 | repeat_dependency_failure, | 135 | /// completion status occurred before or after a given update. |
| 125 | /// Semantic anlaysis failure, but the `typed_value.most_recent` can be accessed. | ||
| 126 | /// There will be a corresponding ErrorMsg in Module.failed_decls. | ||
| 127 | repeat_sema_failure, | ||
| 128 | /// Completed successfully before; the `typed_value.most_recent` can be accessed, and | ||
| 129 | /// new semantic analysis is in progress. | ||
| 130 | repeat_in_progress, | ||
| 131 | /// Everything is done and updated. | ||
| 132 | complete, | 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 | /// Represents the position of the code in the output file. | 149 | /// Represents the position of the code in the output file. |
| 136 | /// This is populated regardless of semantic analysis and code generation. | 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 | /// The shallow set of other decls whose typed_value could possibly change if this Decl's | 155 | /// The shallow set of other decls whose typed_value could possibly change if this Decl's |
| 140 | /// typed_value is modified. | 156 | /// typed_value is modified. |
| 141 | /// TODO look into using a lightweight map/set data structure rather than a linear array. | ||
| 142 | dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, | 157 | dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, |
| 143 | 158 | /// The shallow set of other decls whose typed_value changing indicates that this Decl's | |
| 144 | contents_hash: Hash, | 159 | /// typed_value may need to be regenerated. |
| 160 | dependencies: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, | ||
| 145 | 161 | ||
| 146 | pub fn destroy(self: *Decl, allocator: *Allocator) void { | 162 | pub fn destroy(self: *Decl, allocator: *Allocator) void { |
| 147 | allocator.free(mem.spanZ(self.name)); | 163 | allocator.free(mem.spanZ(self.name)); |
| 148 | if (self.typedValueManaged()) |tvm| { | 164 | if (self.typedValueManaged()) |tvm| { |
| 149 | tvm.deinit(allocator); | 165 | tvm.deinit(allocator); |
| 150 | } | 166 | } |
| 167 | self.dependants.deinit(allocator); | ||
| 168 | self.dependencies.deinit(allocator); | ||
| 151 | allocator.destroy(self); | 169 | allocator.destroy(self); |
| 152 | } | 170 | } |
| 153 | 171 | ||
| ... | @@ -172,7 +190,7 @@ pub const Decl = struct { | ... | @@ -172,7 +190,7 @@ pub const Decl = struct { |
| 172 | pub fn fullyQualifiedNameHash(self: Decl) Hash { | 190 | pub fn fullyQualifiedNameHash(self: Decl) Hash { |
| 173 | // Right now we only have ZIRModule as the source. So this is simply the | 191 | // Right now we only have ZIRModule as the source. So this is simply the |
| 174 | // relative name of the decl. | 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 | pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue { | 196 | pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue { |
| ... | @@ -200,20 +218,31 @@ pub const Decl = struct { | ... | @@ -200,20 +218,31 @@ pub const Decl = struct { |
| 200 | } | 218 | } |
| 201 | 219 | ||
| 202 | fn typedValueManaged(self: *Decl) ?*TypedValue.Managed { | 220 | fn typedValueManaged(self: *Decl) ?*TypedValue.Managed { |
| 203 | switch (self.analysis) { | 221 | switch (self.typed_value) { |
| 204 | .initial_in_progress, | 222 | .most_recent => |*x| return x, |
| 205 | .initial_dependency_failure, | 223 | .never_succeeded => return null, |
| 206 | .initial_sema_failure, | ||
| 207 | => return null, | ||
| 208 | .codegen_failure, | ||
| 209 | .codegen_failure_retryable, | ||
| 210 | .repeat_dependency_failure, | ||
| 211 | .repeat_sema_failure, | ||
| 212 | .repeat_in_progress, | ||
| 213 | .complete, | ||
| 214 | => return &self.typed_value.most_recent, | ||
| 215 | } | 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 | /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. | 248 | /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. |
| ... | @@ -266,12 +295,12 @@ pub const Scope = struct { | ... | @@ -266,12 +295,12 @@ pub const Scope = struct { |
| 266 | 295 | ||
| 267 | /// Asserts the scope has a parent which is a DeclAnalysis and | 296 | /// Asserts the scope has a parent which is a DeclAnalysis and |
| 268 | /// returns the Decl. | 297 | /// returns the Decl. |
| 269 | pub fn decl(self: *Scope) *Decl { | 298 | pub fn decl(self: *Scope) ?*Decl { |
| 270 | switch (self.tag) { | 299 | return switch (self.tag) { |
| 271 | .block => return self.cast(Block).?.decl, | 300 | .block => self.cast(Block).?.decl, |
| 272 | .decl => return self.cast(DeclAnalysis).?.decl, | 301 | .decl => self.cast(DeclAnalysis).?.decl, |
| 273 | .zir_module => unreachable, | 302 | .zir_module => null, |
| 274 | } | 303 | }; |
| 275 | } | 304 | } |
| 276 | 305 | ||
| 277 | /// Asserts the scope has a parent which is a ZIRModule and | 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,6 +506,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 477 | pub fn deinit(self: *Module) void { | 506 | pub fn deinit(self: *Module) void { |
| 478 | self.bin_file.deinit(); | 507 | self.bin_file.deinit(); |
| 479 | const allocator = self.allocator; | 508 | const allocator = self.allocator; |
| 509 | self.deletion_set.deinit(allocator); | ||
| 480 | self.work_queue.deinit(); | 510 | self.work_queue.deinit(); |
| 481 | { | 511 | { |
| 482 | var it = self.decl_table.iterator(); | 512 | var it = self.decl_table.iterator(); |
| ... | @@ -517,11 +547,7 @@ pub fn deinit(self: *Module) void { | ... | @@ -517,11 +547,7 @@ pub fn deinit(self: *Module) void { |
| 517 | { | 547 | { |
| 518 | var it = self.export_owners.iterator(); | 548 | var it = self.export_owners.iterator(); |
| 519 | while (it.next()) |kv| { | 549 | while (it.next()) |kv| { |
| 520 | const export_list = kv.value; | 550 | freeExportList(allocator, kv.value); |
| 521 | for (export_list) |exp| { | ||
| 522 | allocator.destroy(exp); | ||
| 523 | } | ||
| 524 | allocator.free(export_list); | ||
| 525 | } | 551 | } |
| 526 | self.export_owners.deinit(); | 552 | self.export_owners.deinit(); |
| 527 | } | 553 | } |
| ... | @@ -532,12 +558,21 @@ pub fn deinit(self: *Module) void { | ... | @@ -532,12 +558,21 @@ pub fn deinit(self: *Module) void { |
| 532 | self.* = undefined; | 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 | pub fn target(self: Module) std.Target { | 568 | pub fn target(self: Module) std.Target { |
| 536 | return self.bin_file.options.target; | 569 | return self.bin_file.options.target; |
| 537 | } | 570 | } |
| 538 | 571 | ||
| 539 | /// Detect changes to source files, perform semantic analysis, and update the output files. | 572 | /// Detect changes to source files, perform semantic analysis, and update the output files. |
| 540 | pub fn update(self: *Module) !void { | 573 | pub fn update(self: *Module) !void { |
| 574 | self.generation += 1; | ||
| 575 | |||
| 541 | // TODO Use the cache hash file system to detect which source files changed. | 576 | // TODO Use the cache hash file system to detect which source files changed. |
| 542 | // Here we simulate a full cache miss. | 577 | // Here we simulate a full cache miss. |
| 543 | // Analyze the root source file now. | 578 | // Analyze the root source file now. |
| ... | @@ -550,6 +585,15 @@ pub fn update(self: *Module) !void { | ... | @@ -550,6 +585,15 @@ pub fn update(self: *Module) !void { |
| 550 | 585 | ||
| 551 | try self.performAllTheWork(); | 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 | // Unload all the source files from memory. | 597 | // Unload all the source files from memory. |
| 554 | self.root_scope.unload(self.allocator); | 598 | self.root_scope.unload(self.allocator); |
| 555 | 599 | ||
| ... | @@ -634,15 +678,12 @@ const InnerError = error{ OutOfMemory, AnalysisFail }; | ... | @@ -634,15 +678,12 @@ const InnerError = error{ OutOfMemory, AnalysisFail }; |
| 634 | pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { | 678 | pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 635 | while (self.work_queue.readItem()) |work_item| switch (work_item) { | 679 | while (self.work_queue.readItem()) |work_item| switch (work_item) { |
| 636 | .codegen_decl => |decl| switch (decl.analysis) { | 680 | .codegen_decl => |decl| switch (decl.analysis) { |
| 637 | .initial_in_progress, | 681 | .in_progress => unreachable, |
| 638 | .repeat_in_progress, | 682 | .outdated => unreachable, |
| 639 | => unreachable, | ||
| 640 | 683 | ||
| 641 | .initial_sema_failure, | 684 | .sema_failure, |
| 642 | .repeat_sema_failure, | ||
| 643 | .codegen_failure, | 685 | .codegen_failure, |
| 644 | .initial_dependency_failure, | 686 | .dependency_failure, |
| 645 | .repeat_dependency_failure, | ||
| 646 | => continue, | 687 | => continue, |
| 647 | 688 | ||
| 648 | .complete, .codegen_failure_retryable => { | 689 | .complete, .codegen_failure_retryable => { |
| ... | @@ -668,7 +709,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { | ... | @@ -668,7 +709,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 668 | self.bin_file.updateDecl(self, decl) catch |err| switch (err) { | 709 | self.bin_file.updateDecl(self, decl) catch |err| switch (err) { |
| 669 | error.OutOfMemory => return error.OutOfMemory, | 710 | error.OutOfMemory => return error.OutOfMemory, |
| 670 | error.AnalysisFail => { | 711 | error.AnalysisFail => { |
| 671 | decl.analysis = .repeat_dependency_failure; | 712 | decl.analysis = .dependency_failure; |
| 672 | }, | 713 | }, |
| 673 | else => { | 714 | else => { |
| 674 | try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); | 715 | try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); |
| ... | @@ -683,9 +724,60 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { | ... | @@ -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 | fn getSource(self: *Module, root_scope: *Scope.ZIRModule) ![:0]const u8 { | 781 | fn getSource(self: *Module, root_scope: *Scope.ZIRModule) ![:0]const u8 { |
| 690 | switch (root_scope.source) { | 782 | switch (root_scope.source) { |
| 691 | .unloaded => { | 783 | .unloaded => { |
| ... | @@ -742,13 +834,6 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { | ... | @@ -742,13 +834,6 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { |
| 742 | } | 834 | } |
| 743 | 835 | ||
| 744 | fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { | 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 | switch (root_scope.status) { | 837 | switch (root_scope.status) { |
| 753 | .never_loaded => { | 838 | .never_loaded => { |
| 754 | const src_module = try self.getSrcModule(root_scope); | 839 | const src_module = try self.getSrcModule(root_scope); |
| ... | @@ -759,7 +844,7 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { | ... | @@ -759,7 +844,7 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 759 | 844 | ||
| 760 | for (src_module.decls) |decl| { | 845 | for (src_module.decls) |decl| { |
| 761 | if (decl.cast(zir.Inst.Export)) |export_inst| { | 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,41 +857,112 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 772 | => { | 857 | => { |
| 773 | const src_module = try self.getSrcModule(root_scope); | 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 | for (src_module.decls) |src_decl| { | 875 | for (src_module.decls) |src_decl| { |
| 777 | const name_hash = Decl.hashSimpleName(src_decl.name); | 876 | const name_hash = Decl.hashSimpleName(src_decl.name); |
| 778 | if (self.decl_table.get(name_hash)) |kv| { | 877 | if (self.decl_table.get(name_hash)) |kv| { |
| 779 | const decl = kv.value; | 878 | const decl = kv.value; |
| 879 | deleted_decls.removeAssertDiscard(decl); | ||
| 780 | const new_contents_hash = Decl.hashSimpleName(src_decl.contents); | 880 | const new_contents_hash = Decl.hashSimpleName(src_decl.contents); |
| 781 | if (!mem.eql(u8, &new_contents_hash, &decl.contents_hash)) { | 881 | if (!mem.eql(u8, &new_contents_hash, &decl.contents_hash)) { |
| 782 | // TODO recursive dependency management | 882 | //std.debug.warn("noticed '{}' source changed\n", .{src_decl.name}); |
| 783 | //std.debug.warn("noticed that '{}' changed\n", .{src_decl.name}); | 883 | decl.analysis = .outdated; |
| 784 | self.decl_table.removeAssertDiscard(name_hash); | 884 | decl.contents_hash = new_contents_hash; |
| 785 | const saved_link = decl.link; | 885 | try self.work_queue.writeItem(.{ .re_analyze_decl = decl }); |
| 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 | } | ||
| 801 | } | 886 | } |
| 802 | } else if (src_decl.cast(zir.Inst.Export)) |export_inst| { | 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 | fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { | 966 | fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { |
| 811 | // Use the Decl's arena for function memory. | 967 | // Use the Decl's arena for function memory. |
| 812 | var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); | 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,15 +992,111 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { |
| 836 | }; | 992 | }; |
| 837 | } | 993 | } |
| 838 | 994 | ||
| 839 | fn resolveDecl( | 995 | fn reAnalyzeDecl(self: *Module, decl: *Decl, old_inst: *zir.Inst) InnerError!void { |
| 840 | self: *Module, | 996 | switch (decl.analysis) { |
| 841 | scope: *Scope, | 997 | .in_progress => unreachable, |
| 842 | old_inst: *zir.Inst, | 998 | .dependency_failure, |
| 843 | bin_file_link: link.ElfFile.Decl, | 999 | .sema_failure, |
| 844 | ) InnerError!*Decl { | 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 | const hash = Decl.hashSimpleName(old_inst.name); | 1095 | const hash = Decl.hashSimpleName(old_inst.name); |
| 846 | if (self.decl_table.get(hash)) |kv| { | 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 | } else { | 1100 | } else { |
| 849 | const new_decl = blk: { | 1101 | const new_decl = blk: { |
| 850 | try self.decl_table.ensureCapacity(self.decl_table.size + 1); | 1102 | try self.decl_table.ensureCapacity(self.decl_table.size + 1); |
| ... | @@ -857,9 +1109,11 @@ fn resolveDecl( | ... | @@ -857,9 +1109,11 @@ fn resolveDecl( |
| 857 | .scope = scope.namespace(), | 1109 | .scope = scope.namespace(), |
| 858 | .src = old_inst.src, | 1110 | .src = old_inst.src, |
| 859 | .typed_value = .{ .never_succeeded = {} }, | 1111 | .typed_value = .{ .never_succeeded = {} }, |
| 860 | .analysis = .initial_in_progress, | 1112 | .analysis = .in_progress, |
| 1113 | .deletion_flag = false, | ||
| 861 | .contents_hash = Decl.hashSimpleName(old_inst.contents), | 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 | self.decl_table.putAssumeCapacityNoClobber(hash, new_decl); | 1118 | self.decl_table.putAssumeCapacityNoClobber(hash, new_decl); |
| 865 | break :blk new_decl; | 1119 | break :blk new_decl; |
| ... | @@ -875,10 +1129,10 @@ fn resolveDecl( | ... | @@ -875,10 +1129,10 @@ fn resolveDecl( |
| 875 | error.OutOfMemory => return error.OutOfMemory, | 1129 | error.OutOfMemory => return error.OutOfMemory, |
| 876 | error.AnalysisFail => { | 1130 | error.AnalysisFail => { |
| 877 | switch (new_decl.analysis) { | 1131 | switch (new_decl.analysis) { |
| 878 | .initial_in_progress => new_decl.analysis = .initial_dependency_failure, | 1132 | .in_progress => new_decl.analysis = .dependency_failure, |
| 879 | .repeat_in_progress => new_decl.analysis = .repeat_dependency_failure, | ||
| 880 | else => {}, | 1133 | else => {}, |
| 881 | } | 1134 | } |
| 1135 | new_decl.generation = self.generation; | ||
| 882 | return error.AnalysisFail; | 1136 | return error.AnalysisFail; |
| 883 | }, | 1137 | }, |
| 884 | }; | 1138 | }; |
| ... | @@ -893,34 +1147,37 @@ fn resolveDecl( | ... | @@ -893,34 +1147,37 @@ fn resolveDecl( |
| 893 | }, | 1147 | }, |
| 894 | }; | 1148 | }; |
| 895 | new_decl.analysis = .complete; | 1149 | new_decl.analysis = .complete; |
| 1150 | new_decl.generation = self.generation; | ||
| 896 | if (typed_value.ty.hasCodeGenBits()) { | 1151 | if (typed_value.ty.hasCodeGenBits()) { |
| 897 | // We don't fully codegen the decl until later, but we do need to reserve a global | 1152 | // We don't fully codegen the decl until later, but we do need to reserve a global |
| 898 | // offset table index for it. This allows us to codegen decls out of dependency order, | 1153 | // offset table index for it. This allows us to codegen decls out of dependency order, |
| 899 | // increasing how many computations can be done in parallel. | 1154 | // increasing how many computations can be done in parallel. |
| 900 | try self.bin_file.allocateDeclIndexes(new_decl); | 1155 | try self.bin_file.allocateDeclIndexes(new_decl); |
| 901 | 1156 | try self.work_queue.writeItem(.{ .codegen_decl = new_decl }); | |
| 902 | // We ensureCapacity when scanning for decls. | ||
| 903 | self.work_queue.writeItemAssumeCapacity(.{ .codegen_decl = new_decl }); | ||
| 904 | } | 1157 | } |
| 905 | return new_decl; | 1158 | return new_decl; |
| 906 | } | 1159 | } |
| 907 | } | 1160 | } |
| 908 | 1161 | ||
| 1162 | /// Declares a dependency on the decl. | ||
| 909 | fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { | 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 | switch (decl.analysis) { | 1165 | switch (decl.analysis) { |
| 912 | .initial_in_progress => unreachable, | 1166 | .in_progress => unreachable, |
| 913 | .repeat_in_progress => unreachable, | 1167 | .outdated => unreachable, |
| 914 | .initial_dependency_failure, | 1168 | |
| 915 | .repeat_dependency_failure, | 1169 | .dependency_failure, |
| 916 | .initial_sema_failure, | 1170 | .sema_failure, |
| 917 | .repeat_sema_failure, | ||
| 918 | .codegen_failure, | 1171 | .codegen_failure, |
| 919 | .codegen_failure_retryable, | 1172 | .codegen_failure_retryable, |
| 920 | => return error.AnalysisFail, | 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 | fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { | 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,13 +1255,14 @@ fn analyzeExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) In |
| 998 | const new_export = try self.allocator.create(Export); | 1255 | const new_export = try self.allocator.create(Export); |
| 999 | errdefer self.allocator.destroy(new_export); | 1256 | errdefer self.allocator.destroy(new_export); |
| 1000 | 1257 | ||
| 1001 | const owner_decl = scope.decl(); | 1258 | const owner_decl = scope.decl().?; |
| 1002 | 1259 | ||
| 1003 | new_export.* = .{ | 1260 | new_export.* = .{ |
| 1004 | .options = .{ .name = symbol_name }, | 1261 | .options = .{ .name = symbol_name }, |
| 1005 | .src = export_inst.base.src, | 1262 | .src = export_inst.base.src, |
| 1006 | .link = .{}, | 1263 | .link = .{}, |
| 1007 | .owner_decl = owner_decl, | 1264 | .owner_decl = owner_decl, |
| 1265 | .exported_decl = exported_decl, | ||
| 1008 | .status = .in_progress, | 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,7 +1585,7 @@ fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError |
| 1327 | new_func.* = .{ | 1585 | new_func.* = .{ |
| 1328 | .fn_type = fn_type, | 1586 | .fn_type = fn_type, |
| 1329 | .analysis = .{ .queued = fn_inst }, | 1587 | .analysis = .{ .queued = fn_inst }, |
| 1330 | .owner_decl = scope.decl(), | 1588 | .owner_decl = scope.decl().?, |
| 1331 | }; | 1589 | }; |
| 1332 | const fn_payload = try scope.arena().create(Value.Payload.Function); | 1590 | const fn_payload = try scope.arena().create(Value.Payload.Function); |
| 1333 | fn_payload.* = .{ .func = new_func }; | 1591 | fn_payload.* = .{ .func = new_func }; |
| ... | @@ -2024,11 +2282,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err | ... | @@ -2024,11 +2282,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err |
| 2024 | switch (scope.tag) { | 2282 | switch (scope.tag) { |
| 2025 | .decl => { | 2283 | .decl => { |
| 2026 | const decl = scope.cast(Scope.DeclAnalysis).?.decl; | 2284 | const decl = scope.cast(Scope.DeclAnalysis).?.decl; |
| 2027 | switch (decl.analysis) { | 2285 | decl.analysis = .sema_failure; |
| 2028 | .initial_in_progress => decl.analysis = .initial_sema_failure, | ||
| 2029 | .repeat_in_progress => decl.analysis = .repeat_sema_failure, | ||
| 2030 | else => unreachable, | ||
| 2031 | } | ||
| 2032 | self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg); | 2286 | self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg); |
| 2033 | }, | 2287 | }, |
| 2034 | .block => { | 2288 | .block => { |
src-self-hosted/link.zig+330-147| ... | @@ -126,6 +126,10 @@ pub const ElfFile = struct { | ... | @@ -126,6 +126,10 @@ pub const ElfFile = struct { |
| 126 | local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, | 126 | local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, |
| 127 | global_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, | 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 | /// Same order as in the file. The value is the absolute vaddr value. | 133 | /// Same order as in the file. The value is the absolute vaddr value. |
| 130 | /// If the vaddr of the executable program header changes, the entire | 134 | /// If the vaddr of the executable program header changes, the entire |
| 131 | /// offset table needs to be rewritten. | 135 | /// offset table needs to be rewritten. |
| ... | @@ -138,11 +142,39 @@ pub const ElfFile = struct { | ... | @@ -138,11 +142,39 @@ pub const ElfFile = struct { |
| 138 | 142 | ||
| 139 | error_flags: ErrorFlags = ErrorFlags{}, | 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 | pub const ErrorFlags = struct { | 173 | pub const ErrorFlags = struct { |
| 142 | no_entry_point_found: bool = false, | 174 | no_entry_point_found: bool = false, |
| 143 | }; | 175 | }; |
| 144 | 176 | ||
| 145 | pub const Decl = struct { | 177 | pub const TextBlock = struct { |
| 146 | /// Each decl always gets a local symbol with the fully qualified name. | 178 | /// Each decl always gets a local symbol with the fully qualified name. |
| 147 | /// The vaddr and size are found here directly. | 179 | /// The vaddr and size are found here directly. |
| 148 | /// The file offset is found by computing the vaddr offset from the section vaddr | 180 | /// The file offset is found by computing the vaddr offset from the section vaddr |
| ... | @@ -152,11 +184,43 @@ pub const ElfFile = struct { | ... | @@ -152,11 +184,43 @@ pub const ElfFile = struct { |
| 152 | local_sym_index: u32, | 184 | local_sym_index: u32, |
| 153 | /// This field is undefined for symbols with size = 0. | 185 | /// This field is undefined for symbols with size = 0. |
| 154 | offset_table_index: u32, | 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 | .local_sym_index = 0, | 193 | .local_sym_index = 0, |
| 158 | .offset_table_index = undefined, | 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 | pub const Export = struct { | 226 | pub const Export = struct { |
| ... | @@ -169,6 +233,10 @@ pub const ElfFile = struct { | ... | @@ -169,6 +233,10 @@ pub const ElfFile = struct { |
| 169 | self.shstrtab.deinit(self.allocator); | 233 | self.shstrtab.deinit(self.allocator); |
| 170 | self.local_symbols.deinit(self.allocator); | 234 | self.local_symbols.deinit(self.allocator); |
| 171 | self.global_symbols.deinit(self.allocator); | 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 | self.offset_table.deinit(self.allocator); | 240 | self.offset_table.deinit(self.allocator); |
| 173 | if (self.owns_file_handle) { | 241 | if (self.owns_file_handle) { |
| 174 | if (self.file) |f| f.close(); | 242 | if (self.file) |f| f.close(); |
| ... | @@ -193,10 +261,6 @@ pub const ElfFile = struct { | ... | @@ -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 | /// Returns end pos of collision, if any. | 264 | /// Returns end pos of collision, if any. |
| 201 | fn detectAllocCollision(self: *ElfFile, start: u64, size: u64) ?u64 { | 265 | fn detectAllocCollision(self: *ElfFile, start: u64, size: u64) ?u64 { |
| 202 | const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32; | 266 | const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32; |
| ... | @@ -448,6 +512,13 @@ pub const ElfFile = struct { | ... | @@ -448,6 +512,13 @@ pub const ElfFile = struct { |
| 448 | self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign); | 512 | self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign); |
| 449 | self.phdr_table_dirty = true; | 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 | /// Commit pending changes and write headers. | 524 | /// Commit pending changes and write headers. |
| ... | @@ -577,7 +648,6 @@ pub const ElfFile = struct { | ... | @@ -577,7 +648,6 @@ pub const ElfFile = struct { |
| 577 | self.error_flags.no_entry_point_found = false; | 648 | self.error_flags.no_entry_point_found = false; |
| 578 | try self.writeElfHeader(); | 649 | try self.writeElfHeader(); |
| 579 | } | 650 | } |
| 580 | // TODO find end pos and truncate | ||
| 581 | 651 | ||
| 582 | // The point of flush() is to commit changes, so nothing should be dirty after this. | 652 | // The point of flush() is to commit changes, so nothing should be dirty after this. |
| 583 | assert(!self.phdr_table_dirty); | 653 | assert(!self.phdr_table_dirty); |
| ... | @@ -709,100 +779,222 @@ pub const ElfFile = struct { | ... | @@ -709,100 +779,222 @@ pub const ElfFile = struct { |
| 709 | try self.file.?.pwriteAll(hdr_buf[0..index], 0); | 779 | try self.file.?.pwriteAll(hdr_buf[0..index], 0); |
| 710 | } | 780 | } |
| 711 | 781 | ||
| 712 | const AllocatedBlock = struct { | 782 | fn freeTextBlock(self: *ElfFile, text_block: *TextBlock) void { |
| 713 | vaddr: u64, | 783 | var already_have_free_list_node = false; |
| 714 | file_offset: u64, | 784 | { |
| 715 | size_capacity: u64, | 785 | var i: usize = 0; |
| 716 | }; | 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 { | 798 | if (self.last_text_block == text_block) { |
| 719 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | 799 | // TODO shrink the .text section size here |
| 720 | const shdr = &self.sections.items[self.text_section_index.?]; | 800 | self.last_text_block = text_block.prev; |
| 801 | } | ||
| 721 | 802 | ||
| 722 | // TODO Also detect virtual address collisions. | 803 | if (text_block.prev) |prev| { |
| 723 | const text_capacity = self.allocatedSize(shdr.sh_offset); | 804 | prev.next = text_block.next; |
| 724 | // TODO instead of looping here, maintain a free list and a pointer to the end. | 805 | |
| 725 | var last_start: u64 = phdr.p_vaddr; | 806 | if (!already_have_free_list_node and prev.freeListEligible(self.*)) { |
| 726 | var last_size: u64 = 0; | 807 | // The free list is heuristics, it doesn't have to be perfect, so we can |
| 727 | for (self.local_symbols.items) |sym| { | 808 | // ignore the OOM here. |
| 728 | if (sym.st_value + sym.st_size > last_start + last_size) { | 809 | self.text_block_free_list.append(self.allocator, prev) catch {}; |
| 729 | last_start = sym.st_value; | ||
| 730 | last_size = sym.st_size; | ||
| 731 | } | 810 | } |
| 811 | } else { | ||
| 812 | text_block.prev = null; | ||
| 732 | } | 813 | } |
| 733 | const end_vaddr = last_start + (last_size * alloc_num / alloc_den); | 814 | |
| 734 | const aligned_start_vaddr = mem.alignForwardGeneric(u64, end_vaddr, alignment); | 815 | if (text_block.next) |next| { |
| 735 | const needed_size = (aligned_start_vaddr + new_block_size) - phdr.p_vaddr; | 816 | next.prev = text_block.prev; |
| 736 | if (needed_size > text_capacity) { | 817 | } else { |
| 737 | // Must move the entire text section. | 818 | text_block.next = null; |
| 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; | ||
| 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 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | 836 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; |
| 762 | const shdr = &self.sections.items[self.text_section_index.?]; | 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. | 917 | shdr.sh_size = needed_size; |
| 765 | // TODO look into using a hash map to speed up perf. | 918 | phdr.p_memsz = needed_size; |
| 766 | const text_capacity = self.allocatedSize(shdr.sh_offset); | 919 | phdr.p_filesz = needed_size; |
| 767 | var next_vaddr_start = phdr.p_vaddr + text_capacity; | 920 | |
| 768 | for (self.local_symbols.items) |elem| { | 921 | self.phdr_table_dirty = true; // TODO look into making only the one program header dirty |
| 769 | if (elem.st_value < sym.st_value) continue; | 922 | self.shdr_table_dirty = true; // TODO look into making only the one section dirty |
| 770 | if (elem.st_value < next_vaddr_start) next_vaddr_start = elem.st_value; | ||
| 771 | } | 923 | } |
| 772 | return .{ | 924 | |
| 773 | .vaddr = sym.st_value, | 925 | // This function can also reallocate a text block. |
| 774 | .file_offset = shdr.sh_offset + (sym.st_value - phdr.p_vaddr), | 926 | // In this case we need to "unplug" it from its previous location before |
| 775 | .size_capacity = next_vaddr_start - sym.st_value, | 927 | // plugging it in to its new location. |
| 776 | }; | 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 | pub fn allocateDeclIndexes(self: *ElfFile, decl: *Module.Decl) !void { | 949 | pub fn allocateDeclIndexes(self: *ElfFile, decl: *Module.Decl) !void { |
| 780 | if (decl.link.local_sym_index != 0) return; | 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 | try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1); | 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 | try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1); | 955 | try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1); |
| 784 | const local_sym_index = self.local_symbols.items.len; | 956 | try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); |
| 785 | const offset_table_index = self.offset_table.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 | const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; | 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 | .st_name = 0, | 978 | .st_name = 0, |
| 790 | .st_info = 0, | 979 | .st_info = 0, |
| 791 | .st_other = 0, | 980 | .st_other = 0, |
| 792 | .st_shndx = 0, | 981 | .st_shndx = 0, |
| 793 | .st_value = phdr.p_vaddr, | 982 | .st_value = phdr.p_vaddr, |
| 794 | .st_size = 0, | 983 | .st_size = 0, |
| 795 | }); | 984 | }; |
| 796 | errdefer self.local_symbols.shrink(self.allocator, self.local_symbols.items.len - 1); | 985 | self.offset_table.items[decl.link.offset_table_index] = 0; |
| 797 | self.offset_table.appendAssumeCapacity(0); | 986 | } |
| 798 | errdefer self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1); | ||
| 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 = .{ | 994 | self.local_symbols.items[decl.link.local_sym_index].st_info = 0; |
| 803 | .local_sym_index = @intCast(u32, local_sym_index), | 995 | |
| 804 | .offset_table_index = @intCast(u32, offset_table_index), | 996 | decl.link.local_sym_index = 0; |
| 805 | }; | 997 | } |
| 806 | } | 998 | } |
| 807 | 999 | ||
| 808 | pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void { | 1000 | pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void { |
| ... | @@ -822,80 +1014,60 @@ pub const ElfFile = struct { | ... | @@ -822,80 +1014,60 @@ pub const ElfFile = struct { |
| 822 | 1014 | ||
| 823 | const required_alignment = typed_value.ty.abiAlignment(self.options.target); | 1015 | const required_alignment = typed_value.ty.abiAlignment(self.options.target); |
| 824 | 1016 | ||
| 825 | const file_offset = blk: { | 1017 | const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) { |
| 826 | const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) { | 1018 | .Fn => elf.STT_FUNC, |
| 827 | .Fn => elf.STT_FUNC, | 1019 | else => elf.STT_OBJECT, |
| 828 | else => elf.STT_OBJECT, | 1020 | }; |
| 829 | }; | ||
| 830 | 1021 | ||
| 831 | if (decl.link.local_sym_index != 0) { | 1022 | assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes() |
| 832 | const local_sym = &self.local_symbols.items[decl.link.local_sym_index]; | 1023 | const local_sym = &self.local_symbols.items[decl.link.local_sym_index]; |
| 833 | const existing_block = self.findAllocatedTextBlock(local_sym.*); | 1024 | if (local_sym.st_size != 0) { |
| 834 | const need_realloc = local_sym.st_size == 0 or | 1025 | const capacity = decl.link.capacity(self.*); |
| 835 | code.len > existing_block.size_capacity or | 1026 | const need_realloc = code.len > capacity or |
| 836 | !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); | 1027 | !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); |
| 837 | // TODO check for collision with another symbol | 1028 | if (need_realloc) { |
| 838 | const file_offset = if (need_realloc) fo: { | 1029 | const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); |
| 839 | const new_block = try self.allocateTextBlock(code.len, required_alignment); | 1030 | //std.debug.warn("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); |
| 840 | local_sym.st_value = new_block.vaddr; | 1031 | if (vaddr != local_sym.st_value) { |
| 841 | self.offset_table.items[decl.link.offset_table_index] = new_block.vaddr; | 1032 | local_sym.st_value = vaddr; |
| 842 | 1033 | ||
| 843 | //std.debug.warn("{}: writing got index {}=0x{x}\n", .{ | 1034 | //std.debug.warn(" (writing new offset table entry)\n", .{}); |
| 844 | // decl.name, | 1035 | self.offset_table.items[decl.link.offset_table_index] = vaddr; |
| 845 | // decl.link.offset_table_index, | ||
| 846 | // self.offset_table.items[decl.link.offset_table_index], | ||
| 847 | //}); | ||
| 848 | try self.writeOffsetTableEntry(decl.link.offset_table_index); | 1036 | try self.writeOffsetTableEntry(decl.link.offset_table_index); |
| 849 | 1037 | } | |
| 850 | break :fo new_block.file_offset; | 1038 | } else if (code.len < local_sym.st_size) { |
| 851 | } else existing_block.file_offset; | 1039 | self.shrinkTextBlock(&decl.link, code.len); |
| 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; | ||
| 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 | try self.file.?.pwriteAll(code, file_offset); | 1071 | try self.file.?.pwriteAll(code, file_offset); |
| 900 | 1072 | ||
| 901 | // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. | 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,7 +1082,10 @@ pub const ElfFile = struct { |
| 910 | decl: *const Module.Decl, | 1082 | decl: *const Module.Decl, |
| 911 | exports: []const *Module.Export, | 1083 | exports: []const *Module.Export, |
| 912 | ) !void { | 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 | try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len); | 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 | const typed_value = decl.typed_value.most_recent.typed_value; | 1089 | const typed_value = decl.typed_value.most_recent.typed_value; |
| 915 | if (decl.link.local_sym_index == 0) return; | 1090 | if (decl.link.local_sym_index == 0) return; |
| 916 | const decl_sym = self.local_symbols.items[decl.link.local_sym_index]; | 1091 | const decl_sym = self.local_symbols.items[decl.link.local_sym_index]; |
| ... | @@ -957,22 +1132,30 @@ pub const ElfFile = struct { | ... | @@ -957,22 +1132,30 @@ pub const ElfFile = struct { |
| 957 | }; | 1132 | }; |
| 958 | } else { | 1133 | } else { |
| 959 | const name = try self.makeString(exp.options.name); | 1134 | const name = try self.makeString(exp.options.name); |
| 960 | const i = self.global_symbols.items.len; | 1135 | const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { |
| 961 | self.global_symbols.appendAssumeCapacity(.{ | 1136 | _ = self.global_symbols.addOneAssumeCapacity(); |
| 1137 | break :blk self.global_symbols.items.len - 1; | ||
| 1138 | }; | ||
| 1139 | self.global_symbols.items[i] = .{ | ||
| 962 | .st_name = name, | 1140 | .st_name = name, |
| 963 | .st_info = (stb_bits << 4) | stt_bits, | 1141 | .st_info = (stb_bits << 4) | stt_bits, |
| 964 | .st_other = 0, | 1142 | .st_other = 0, |
| 965 | .st_shndx = self.text_section_index.?, | 1143 | .st_shndx = self.text_section_index.?, |
| 966 | .st_value = decl_sym.st_value, | 1144 | .st_value = decl_sym.st_value, |
| 967 | .st_size = decl_sym.st_size, | 1145 | .st_size = decl_sym.st_size, |
| 968 | }); | 1146 | }; |
| 969 | errdefer self.global_symbols.shrink(self.allocator, self.global_symbols.items.len - 1); | ||
| 970 | 1147 | ||
| 971 | exp.link.sym_index = @intCast(u32, i); | 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 | fn writeProgHeader(self: *ElfFile, index: usize) !void { | 1159 | fn writeProgHeader(self: *ElfFile, index: usize) !void { |
| 977 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 1160 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 978 | const offset = self.program_headers.items[index].p_offset; | 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,13 +92,13 @@ pub const Type = extern union { |
| 92 | return @fieldParentPtr(T, "base", self.ptr_otherwise); | 92 | return @fieldParentPtr(T, "base", self.ptr_otherwise); |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | pub fn eql(self: Type, other: Type) bool { | 95 | pub fn eql(a: Type, b: Type) bool { |
| 96 | //std.debug.warn("test {} == {}\n", .{ self, other }); | 96 | //std.debug.warn("test {} == {}\n", .{ a, b }); |
| 97 | // As a shortcut, if the small tags / addresses match, we're done. | 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 | return true; | 99 | return true; |
| 100 | const zig_tag_a = self.zigTypeTag(); | 100 | const zig_tag_a = a.zigTypeTag(); |
| 101 | const zig_tag_b = self.zigTypeTag(); | 101 | const zig_tag_b = b.zigTypeTag(); |
| 102 | if (zig_tag_a != zig_tag_b) | 102 | if (zig_tag_a != zig_tag_b) |
| 103 | return false; | 103 | return false; |
| 104 | switch (zig_tag_a) { | 104 | switch (zig_tag_a) { |
| ... | @@ -111,24 +111,40 @@ pub const Type = extern union { | ... | @@ -111,24 +111,40 @@ pub const Type = extern union { |
| 111 | .Undefined => return true, | 111 | .Undefined => return true, |
| 112 | .Null => return true, | 112 | .Null => return true, |
| 113 | .Pointer => { | 113 | .Pointer => { |
| 114 | const is_slice_a = isSlice(self); | 114 | const is_slice_a = isSlice(a); |
| 115 | const is_slice_b = isSlice(other); | 115 | const is_slice_b = isSlice(b); |
| 116 | if (is_slice_a != is_slice_b) | 116 | if (is_slice_a != is_slice_b) |
| 117 | return false; | 117 | return false; |
| 118 | @panic("TODO implement more pointer Type equality comparison"); | 118 | @panic("TODO implement more pointer Type equality comparison"); |
| 119 | }, | 119 | }, |
| 120 | .Int => { | 120 | .Int => { |
| 121 | if (self.tag() != other.tag()) { | 121 | if (a.tag() != b.tag()) { |
| 122 | // Detect that e.g. u64 != usize, even if the bits match on a particular target. | 122 | // Detect that e.g. u64 != usize, even if the bits match on a particular target. |
| 123 | return false; | 123 | return false; |
| 124 | } | 124 | } |
| 125 | // The target will not be branched upon, because we handled target-dependent cases above. | 125 | // The target will not be branched upon, because we handled target-dependent cases above. |
| 126 | const info_a = self.intInfo(@as(Target, undefined)); | 126 | const info_a = a.intInfo(@as(Target, undefined)); |
| 127 | const info_b = self.intInfo(@as(Target, undefined)); | 127 | const info_b = b.intInfo(@as(Target, undefined)); |
| 128 | return info_a.signed == info_b.signed and info_a.bits == info_b.bits; | 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 | .Float, | 147 | .Float, |
| 131 | .Array, | ||
| 132 | .Struct, | 148 | .Struct, |
| 133 | .Optional, | 149 | .Optional, |
| 134 | .ErrorUnion, | 150 | .ErrorUnion, |
src-self-hosted/value.zig+5| ... | @@ -666,6 +666,11 @@ pub const Value = extern union { | ... | @@ -666,6 +666,11 @@ pub const Value = extern union { |
| 666 | return orderAgainstZero(lhs).compare(op); | 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 | pub fn toBool(self: Value) bool { | 674 | pub fn toBool(self: Value) bool { |
| 670 | return switch (self.tag()) { | 675 | return switch (self.tag()) { |
| 671 | .bool_true => true, | 676 | .bool_true => true, |
src-self-hosted/zir.zig+10| ... | @@ -442,6 +442,16 @@ pub const Module = struct { | ... | @@ -442,6 +442,16 @@ pub const Module = struct { |
| 442 | 442 | ||
| 443 | const InstPtrTable = std.AutoHashMap(*Inst, struct { index: usize, fn_body: ?*Module.Body }); | 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 | /// The allocator is used for temporary storage, but this function always returns | 455 | /// The allocator is used for temporary storage, but this function always returns |
| 446 | /// with no resources allocated. | 456 | /// with no resources allocated. |
| 447 | pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void { | 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,6 +200,73 @@ pub fn addCases(ctx: *TestContext) void { |
| 200 | \\@9 = str("_start") | 200 | \\@9 = str("_start") |
| 201 | \\@10 = ref(@9) | 201 | \\@10 = ref(@9) |
| 202 | \\@11 = export(@10, @start) | 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 | &[_][]const u8{ | 271 | &[_][]const u8{ |
| 205 | \\Hello, world! | 272 | \\Hello, world! |
| ... | @@ -207,6 +274,10 @@ pub fn addCases(ctx: *TestContext) void { | ... | @@ -207,6 +274,10 @@ pub fn addCases(ctx: *TestContext) void { |
| 207 | , | 274 | , |
| 208 | \\HELL WORLD | 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 |