| author | |
| committer | |
| log | 3eed7a4dea3b66bf236278caba7f96228b13214f |
| tree | 81ea8de9e0c2f066f78a2b53c0efa89287bfd4b3 |
| parent | c7ca1fe6f7b8796a42de908faeaa6ec24e8eb118 |
4 files changed, 290 insertions(+), 129 deletions(-)
src-self-hosted/Module.zig+169-40| ... | @@ -128,6 +128,9 @@ pub const Decl = struct { | ... | @@ -128,6 +128,9 @@ pub const Decl = struct { |
| 128 | /// Completed successfully before; the `typed_value.most_recent` can be accessed, and | 128 | /// Completed successfully before; the `typed_value.most_recent` can be accessed, and |
| 129 | /// new semantic analysis is in progress. | 129 | /// new semantic analysis is in progress. |
| 130 | repeat_in_progress, | 130 | repeat_in_progress, |
| 131 | /// Failed before; the `typed_value.most_recent` is not available, and | ||
| 132 | /// new semantic analysis is in progress. | ||
| 133 | repeat_in_progress_novalue, | ||
| 131 | /// Everything is done and updated. | 134 | /// Everything is done and updated. |
| 132 | complete, | 135 | complete, |
| 133 | }, | 136 | }, |
| ... | @@ -136,18 +139,24 @@ pub const Decl = struct { | ... | @@ -136,18 +139,24 @@ pub const Decl = struct { |
| 136 | /// This is populated regardless of semantic analysis and code generation. | 139 | /// This is populated regardless of semantic analysis and code generation. |
| 137 | link: link.ElfFile.TextBlock = link.ElfFile.TextBlock.empty, | 140 | link: link.ElfFile.TextBlock = link.ElfFile.TextBlock.empty, |
| 138 | 141 | ||
| 142 | contents_hash: Hash, | ||
| 143 | |||
| 139 | /// The shallow set of other decls whose typed_value could possibly change if this Decl's | 144 | /// The shallow set of other decls whose typed_value could possibly change if this Decl's |
| 140 | /// typed_value is modified. | 145 | /// typed_value is modified. |
| 141 | /// TODO look into using a lightweight map/set data structure rather than a linear array. | 146 | /// TODO look into using a lightweight map/set data structure rather than a linear array. |
| 142 | dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, | 147 | dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, |
| 143 | 148 | /// The shallow set of other decls whose typed_value changing indicates that this Decl's | |
| 144 | contents_hash: Hash, | 149 | /// typed_value may need to be regenerated. |
| 150 | /// TODO look into using a lightweight map/set data structure rather than a linear array. | ||
| 151 | dependencies: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, | ||
| 145 | 152 | ||
| 146 | pub fn destroy(self: *Decl, allocator: *Allocator) void { | 153 | pub fn destroy(self: *Decl, allocator: *Allocator) void { |
| 147 | allocator.free(mem.spanZ(self.name)); | 154 | allocator.free(mem.spanZ(self.name)); |
| 148 | if (self.typedValueManaged()) |tvm| { | 155 | if (self.typedValueManaged()) |tvm| { |
| 149 | tvm.deinit(allocator); | 156 | tvm.deinit(allocator); |
| 150 | } | 157 | } |
| 158 | self.dependants.deinit(allocator); | ||
| 159 | self.dependencies.deinit(allocator); | ||
| 151 | allocator.destroy(self); | 160 | allocator.destroy(self); |
| 152 | } | 161 | } |
| 153 | 162 | ||
| ... | @@ -204,6 +213,7 @@ pub const Decl = struct { | ... | @@ -204,6 +213,7 @@ pub const Decl = struct { |
| 204 | .initial_in_progress, | 213 | .initial_in_progress, |
| 205 | .initial_dependency_failure, | 214 | .initial_dependency_failure, |
| 206 | .initial_sema_failure, | 215 | .initial_sema_failure, |
| 216 | .repeat_in_progress_novalue, | ||
| 207 | => return null, | 217 | => return null, |
| 208 | .codegen_failure, | 218 | .codegen_failure, |
| 209 | .codegen_failure_retryable, | 219 | .codegen_failure_retryable, |
| ... | @@ -214,6 +224,31 @@ pub const Decl = struct { | ... | @@ -214,6 +224,31 @@ pub const Decl = struct { |
| 214 | => return &self.typed_value.most_recent, | 224 | => return &self.typed_value.most_recent, |
| 215 | } | 225 | } |
| 216 | } | 226 | } |
| 227 | |||
| 228 | fn flagForRegeneration(self: *Decl) void { | ||
| 229 | if (self.typedValueManaged() == null) { | ||
| 230 | self.analysis = .repeat_in_progress_novalue; | ||
| 231 | } else { | ||
| 232 | self.analysis = .repeat_in_progress; | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | fn isFlaggedForRegeneration(self: *Decl) bool { | ||
| 237 | return switch (self.analysis) { | ||
| 238 | .repeat_in_progress, .repeat_in_progress_novalue => true, | ||
| 239 | else => false, | ||
| 240 | }; | ||
| 241 | } | ||
| 242 | |||
| 243 | fn removeDependant(self: *Decl, other: *Decl) void { | ||
| 244 | for (self.dependants.items) |item, i| { | ||
| 245 | if (item == other) { | ||
| 246 | _ = self.dependants.swapRemove(i); | ||
| 247 | return; | ||
| 248 | } | ||
| 249 | } | ||
| 250 | unreachable; | ||
| 251 | } | ||
| 217 | }; | 252 | }; |
| 218 | 253 | ||
| 219 | /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. | 254 | /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. |
| ... | @@ -266,12 +301,12 @@ pub const Scope = struct { | ... | @@ -266,12 +301,12 @@ pub const Scope = struct { |
| 266 | 301 | ||
| 267 | /// Asserts the scope has a parent which is a DeclAnalysis and | 302 | /// Asserts the scope has a parent which is a DeclAnalysis and |
| 268 | /// returns the Decl. | 303 | /// returns the Decl. |
| 269 | pub fn decl(self: *Scope) *Decl { | 304 | pub fn decl(self: *Scope) ?*Decl { |
| 270 | switch (self.tag) { | 305 | return switch (self.tag) { |
| 271 | .block => return self.cast(Block).?.decl, | 306 | .block => self.cast(Block).?.decl, |
| 272 | .decl => return self.cast(DeclAnalysis).?.decl, | 307 | .decl => self.cast(DeclAnalysis).?.decl, |
| 273 | .zir_module => unreachable, | 308 | .zir_module => null, |
| 274 | } | 309 | }; |
| 275 | } | 310 | } |
| 276 | 311 | ||
| 277 | /// Asserts the scope has a parent which is a ZIRModule and | 312 | /// Asserts the scope has a parent which is a ZIRModule and |
| ... | @@ -517,11 +552,7 @@ pub fn deinit(self: *Module) void { | ... | @@ -517,11 +552,7 @@ pub fn deinit(self: *Module) void { |
| 517 | { | 552 | { |
| 518 | var it = self.export_owners.iterator(); | 553 | var it = self.export_owners.iterator(); |
| 519 | while (it.next()) |kv| { | 554 | while (it.next()) |kv| { |
| 520 | const export_list = kv.value; | 555 | freeExportList(allocator, kv.value); |
| 521 | for (export_list) |exp| { | ||
| 522 | allocator.destroy(exp); | ||
| 523 | } | ||
| 524 | allocator.free(export_list); | ||
| 525 | } | 556 | } |
| 526 | self.export_owners.deinit(); | 557 | self.export_owners.deinit(); |
| 527 | } | 558 | } |
| ... | @@ -532,6 +563,13 @@ pub fn deinit(self: *Module) void { | ... | @@ -532,6 +563,13 @@ pub fn deinit(self: *Module) void { |
| 532 | self.* = undefined; | 563 | self.* = undefined; |
| 533 | } | 564 | } |
| 534 | 565 | ||
| 566 | fn freeExportList(allocator: *Allocator, export_list: []*Export) void { | ||
| 567 | for (export_list) |exp| { | ||
| 568 | allocator.destroy(exp); | ||
| 569 | } | ||
| 570 | allocator.free(export_list); | ||
| 571 | } | ||
| 572 | |||
| 535 | pub fn target(self: Module) std.Target { | 573 | pub fn target(self: Module) std.Target { |
| 536 | return self.bin_file.options.target; | 574 | return self.bin_file.options.target; |
| 537 | } | 575 | } |
| ... | @@ -634,9 +672,9 @@ const InnerError = error{ OutOfMemory, AnalysisFail }; | ... | @@ -634,9 +672,9 @@ const InnerError = error{ OutOfMemory, AnalysisFail }; |
| 634 | pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { | 672 | pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 635 | while (self.work_queue.readItem()) |work_item| switch (work_item) { | 673 | while (self.work_queue.readItem()) |work_item| switch (work_item) { |
| 636 | .codegen_decl => |decl| switch (decl.analysis) { | 674 | .codegen_decl => |decl| switch (decl.analysis) { |
| 637 | .initial_in_progress, | 675 | .initial_in_progress => unreachable, |
| 638 | .repeat_in_progress, | 676 | .repeat_in_progress => unreachable, |
| 639 | => unreachable, | 677 | .repeat_in_progress_novalue => unreachable, |
| 640 | 678 | ||
| 641 | .initial_sema_failure, | 679 | .initial_sema_failure, |
| 642 | .repeat_sema_failure, | 680 | .repeat_sema_failure, |
| ... | @@ -686,6 +724,23 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { | ... | @@ -686,6 +724,23 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { |
| 686 | }; | 724 | }; |
| 687 | } | 725 | } |
| 688 | 726 | ||
| 727 | fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void { | ||
| 728 | try depender.dependencies.ensureCapacity(self.allocator, depender.dependencies.items.len + 1); | ||
| 729 | try dependee.dependants.ensureCapacity(self.allocator, dependee.dependants.items.len + 1); | ||
| 730 | |||
| 731 | for (depender.dependencies.items) |item| { | ||
| 732 | if (item == dependee) break; // Already in the set. | ||
| 733 | } else { | ||
| 734 | depender.dependencies.appendAssumeCapacity(dependee); | ||
| 735 | } | ||
| 736 | |||
| 737 | for (dependee.dependants.items) |item| { | ||
| 738 | if (item == depender) break; // Already in the set. | ||
| 739 | } else { | ||
| 740 | dependee.dependants.appendAssumeCapacity(depender); | ||
| 741 | } | ||
| 742 | } | ||
| 743 | |||
| 689 | fn getSource(self: *Module, root_scope: *Scope.ZIRModule) ![:0]const u8 { | 744 | fn getSource(self: *Module, root_scope: *Scope.ZIRModule) ![:0]const u8 { |
| 690 | switch (root_scope.source) { | 745 | switch (root_scope.source) { |
| 691 | .unloaded => { | 746 | .unloaded => { |
| ... | @@ -772,37 +827,105 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { | ... | @@ -772,37 +827,105 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 772 | => { | 827 | => { |
| 773 | const src_module = try self.getSrcModule(root_scope); | 828 | const src_module = try self.getSrcModule(root_scope); |
| 774 | 829 | ||
| 775 | // Look for changed decls. | 830 | // Look for changed decls. First we add all the decls that changed |
| 831 | // into the set. | ||
| 832 | var regen_decl_set = std.ArrayList(*Decl).init(self.allocator); | ||
| 833 | defer regen_decl_set.deinit(); | ||
| 834 | try regen_decl_set.ensureCapacity(src_module.decls.len); | ||
| 835 | |||
| 836 | var exports_to_resolve = std.ArrayList(*zir.Inst).init(self.allocator); | ||
| 837 | defer exports_to_resolve.deinit(); | ||
| 838 | |||
| 776 | for (src_module.decls) |src_decl| { | 839 | for (src_module.decls) |src_decl| { |
| 777 | const name_hash = Decl.hashSimpleName(src_decl.name); | 840 | const name_hash = Decl.hashSimpleName(src_decl.name); |
| 778 | if (self.decl_table.get(name_hash)) |kv| { | 841 | if (self.decl_table.get(name_hash)) |kv| { |
| 779 | const decl = kv.value; | 842 | const decl = kv.value; |
| 780 | const new_contents_hash = Decl.hashSimpleName(src_decl.contents); | 843 | const new_contents_hash = Decl.hashSimpleName(src_decl.contents); |
| 781 | if (!mem.eql(u8, &new_contents_hash, &decl.contents_hash)) { | 844 | if (!mem.eql(u8, &new_contents_hash, &decl.contents_hash)) { |
| 782 | // TODO recursive dependency management | 845 | std.debug.warn("noticed that '{}' changed\n", .{src_decl.name}); |
| 783 | //std.debug.warn("noticed that '{}' changed\n", .{src_decl.name}); | 846 | regen_decl_set.appendAssumeCapacity(decl); |
| 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 | } | ||
| 801 | } | 847 | } |
| 802 | } else if (src_decl.cast(zir.Inst.Export)) |export_inst| { | 848 | } else if (src_decl.cast(zir.Inst.Export)) |export_inst| { |
| 803 | _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.TextBlock.empty); | 849 | try exports_to_resolve.append(&export_inst.base); |
| 804 | } | 850 | } |
| 805 | } | 851 | } |
| 852 | |||
| 853 | // Next, recursively chase the dependency graph, to populate the set. | ||
| 854 | { | ||
| 855 | var i: usize = 0; | ||
| 856 | while (i < regen_decl_set.items.len) : (i += 1) { | ||
| 857 | const decl = regen_decl_set.items[i]; | ||
| 858 | if (decl.isFlaggedForRegeneration()) { | ||
| 859 | // We already looked at this decl's dependency graph. | ||
| 860 | continue; | ||
| 861 | } | ||
| 862 | decl.flagForRegeneration(); | ||
| 863 | // Remove itself from its dependencies, because we are about to destroy the | ||
| 864 | // decl pointer. | ||
| 865 | for (decl.dependencies.items) |dep| { | ||
| 866 | dep.removeDependant(decl); | ||
| 867 | } | ||
| 868 | // Populate the set with decls that need to get regenerated because they | ||
| 869 | // depend on this one. | ||
| 870 | // TODO If it is only a function body that is modified, it should break the chain | ||
| 871 | // and not cause its dependants to be regenerated. | ||
| 872 | for (decl.dependants.items) |dep| { | ||
| 873 | if (!dep.isFlaggedForRegeneration()) { | ||
| 874 | regen_decl_set.appendAssumeCapacity(dep); | ||
| 875 | } | ||
| 876 | } | ||
| 877 | } | ||
| 878 | } | ||
| 879 | |||
| 880 | // Remove them all from the decl_table. | ||
| 881 | for (regen_decl_set.items) |decl| { | ||
| 882 | const decl_name = mem.spanZ(decl.name); | ||
| 883 | const old_name_hash = Decl.hashSimpleName(decl_name); | ||
| 884 | self.decl_table.removeAssertDiscard(old_name_hash); | ||
| 885 | |||
| 886 | if (self.export_owners.remove(decl)) |kv| { | ||
| 887 | for (kv.value) |exp| { | ||
| 888 | self.bin_file.deleteExport(exp.link); | ||
| 889 | } | ||
| 890 | freeExportList(self.allocator, kv.value); | ||
| 891 | } | ||
| 892 | } | ||
| 893 | |||
| 894 | // Regenerate the decls in the set. | ||
| 895 | const zir_module = try self.getSrcModule(root_scope); | ||
| 896 | |||
| 897 | while (regen_decl_set.popOrNull()) |decl| { | ||
| 898 | const decl_name = mem.spanZ(decl.name); | ||
| 899 | std.debug.warn("regenerating {}\n", .{decl_name}); | ||
| 900 | const saved_link = decl.link; | ||
| 901 | const decl_exports_entry = if (self.decl_exports.remove(decl)) |kv| kv.value else null; | ||
| 902 | const src_decl = zir_module.findDecl(decl_name) orelse { | ||
| 903 | @panic("TODO treat this as a deleted decl"); | ||
| 904 | }; | ||
| 905 | |||
| 906 | decl.destroy(self.allocator); | ||
| 907 | |||
| 908 | const new_decl = self.resolveDecl( | ||
| 909 | &root_scope.base, | ||
| 910 | src_decl, | ||
| 911 | saved_link, | ||
| 912 | ) catch |err| switch (err) { | ||
| 913 | error.OutOfMemory => return error.OutOfMemory, | ||
| 914 | error.AnalysisFail => continue, | ||
| 915 | }; | ||
| 916 | if (decl_exports_entry) |entry| { | ||
| 917 | const gop = try self.decl_exports.getOrPut(new_decl); | ||
| 918 | if (gop.found_existing) { | ||
| 919 | self.allocator.free(entry); | ||
| 920 | } else { | ||
| 921 | gop.kv.value = entry; | ||
| 922 | } | ||
| 923 | } | ||
| 924 | } | ||
| 925 | |||
| 926 | for (exports_to_resolve.items) |export_inst| { | ||
| 927 | _ = try self.resolveDecl(&root_scope.base, export_inst, link.ElfFile.TextBlock.empty); | ||
| 928 | } | ||
| 806 | }, | 929 | }, |
| 807 | } | 930 | } |
| 808 | } | 931 | } |
| ... | @@ -906,11 +1029,13 @@ fn resolveDecl( | ... | @@ -906,11 +1029,13 @@ fn resolveDecl( |
| 906 | } | 1029 | } |
| 907 | } | 1030 | } |
| 908 | 1031 | ||
| 1032 | /// Declares a dependency on the decl. | ||
| 909 | fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { | 1033 | fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { |
| 910 | const decl = try self.resolveDecl(scope, old_inst, link.ElfFile.TextBlock.empty); | 1034 | const decl = try self.resolveDecl(scope, old_inst, link.ElfFile.TextBlock.empty); |
| 911 | switch (decl.analysis) { | 1035 | switch (decl.analysis) { |
| 912 | .initial_in_progress => unreachable, | 1036 | .initial_in_progress => unreachable, |
| 913 | .repeat_in_progress => unreachable, | 1037 | .repeat_in_progress => unreachable, |
| 1038 | .repeat_in_progress_novalue => unreachable, | ||
| 914 | .initial_dependency_failure, | 1039 | .initial_dependency_failure, |
| 915 | .repeat_dependency_failure, | 1040 | .repeat_dependency_failure, |
| 916 | .initial_sema_failure, | 1041 | .initial_sema_failure, |
| ... | @@ -919,8 +1044,12 @@ fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerE | ... | @@ -919,8 +1044,12 @@ fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerE |
| 919 | .codegen_failure_retryable, | 1044 | .codegen_failure_retryable, |
| 920 | => return error.AnalysisFail, | 1045 | => return error.AnalysisFail, |
| 921 | 1046 | ||
| 922 | .complete => return decl, | 1047 | .complete => {}, |
| 1048 | } | ||
| 1049 | if (scope.decl()) |scope_decl| { | ||
| 1050 | try self.declareDeclDependency(scope_decl, decl); | ||
| 923 | } | 1051 | } |
| 1052 | return decl; | ||
| 924 | } | 1053 | } |
| 925 | 1054 | ||
| 926 | fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { | 1055 | fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { |
| ... | @@ -998,7 +1127,7 @@ fn analyzeExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) In | ... | @@ -998,7 +1127,7 @@ fn analyzeExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) In |
| 998 | const new_export = try self.allocator.create(Export); | 1127 | const new_export = try self.allocator.create(Export); |
| 999 | errdefer self.allocator.destroy(new_export); | 1128 | errdefer self.allocator.destroy(new_export); |
| 1000 | 1129 | ||
| 1001 | const owner_decl = scope.decl(); | 1130 | const owner_decl = scope.decl().?; |
| 1002 | 1131 | ||
| 1003 | new_export.* = .{ | 1132 | new_export.* = .{ |
| 1004 | .options = .{ .name = symbol_name }, | 1133 | .options = .{ .name = symbol_name }, |
| ... | @@ -1327,7 +1456,7 @@ fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError | ... | @@ -1327,7 +1456,7 @@ fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError |
| 1327 | new_func.* = .{ | 1456 | new_func.* = .{ |
| 1328 | .fn_type = fn_type, | 1457 | .fn_type = fn_type, |
| 1329 | .analysis = .{ .queued = fn_inst }, | 1458 | .analysis = .{ .queued = fn_inst }, |
| 1330 | .owner_decl = scope.decl(), | 1459 | .owner_decl = scope.decl().?, |
| 1331 | }; | 1460 | }; |
| 1332 | const fn_payload = try scope.arena().create(Value.Payload.Function); | 1461 | const fn_payload = try scope.arena().create(Value.Payload.Function); |
| 1333 | fn_payload.* = .{ .func = new_func }; | 1462 | fn_payload.* = .{ .func = new_func }; |
src-self-hosted/link.zig+40-18| ... | @@ -126,6 +126,8 @@ pub const ElfFile = struct { | ... | @@ -126,6 +126,8 @@ 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 | global_symbol_free_list: std.ArrayListUnmanaged(usize) = std.ArrayListUnmanaged(usize){}, | ||
| 130 | |||
| 129 | /// Same order as in the file. The value is the absolute vaddr value. | 131 | /// 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 | 132 | /// If the vaddr of the executable program header changes, the entire |
| 131 | /// offset table needs to be rewritten. | 133 | /// offset table needs to be rewritten. |
| ... | @@ -153,7 +155,7 @@ pub const ElfFile = struct { | ... | @@ -153,7 +155,7 @@ pub const ElfFile = struct { |
| 153 | /// overcapacity can be negative. A simple way to have negative overcapacity is to | 155 | /// overcapacity can be negative. A simple way to have negative overcapacity is to |
| 154 | /// allocate a fresh text block, which will have ideal capacity, and then grow it | 156 | /// allocate a fresh text block, which will have ideal capacity, and then grow it |
| 155 | /// by 1 byte. It will then have -1 overcapacity. | 157 | /// by 1 byte. It will then have -1 overcapacity. |
| 156 | free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){}, | 158 | text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){}, |
| 157 | last_text_block: ?*TextBlock = null, | 159 | last_text_block: ?*TextBlock = null, |
| 158 | 160 | ||
| 159 | /// `alloc_num / alloc_den` is the factor of padding when allocating. | 161 | /// `alloc_num / alloc_den` is the factor of padding when allocating. |
| ... | @@ -229,6 +231,8 @@ pub const ElfFile = struct { | ... | @@ -229,6 +231,8 @@ pub const ElfFile = struct { |
| 229 | self.shstrtab.deinit(self.allocator); | 231 | self.shstrtab.deinit(self.allocator); |
| 230 | self.local_symbols.deinit(self.allocator); | 232 | self.local_symbols.deinit(self.allocator); |
| 231 | self.global_symbols.deinit(self.allocator); | 233 | self.global_symbols.deinit(self.allocator); |
| 234 | self.global_symbol_free_list.deinit(self.allocator); | ||
| 235 | self.text_block_free_list.deinit(self.allocator); | ||
| 232 | self.offset_table.deinit(self.allocator); | 236 | self.offset_table.deinit(self.allocator); |
| 233 | if (self.owns_file_handle) { | 237 | if (self.owns_file_handle) { |
| 234 | if (self.file) |f| f.close(); | 238 | if (self.file) |f| f.close(); |
| ... | @@ -775,12 +779,12 @@ pub const ElfFile = struct { | ... | @@ -775,12 +779,12 @@ pub const ElfFile = struct { |
| 775 | var already_have_free_list_node = false; | 779 | var already_have_free_list_node = false; |
| 776 | { | 780 | { |
| 777 | var i: usize = 0; | 781 | var i: usize = 0; |
| 778 | while (i < self.free_list.items.len) { | 782 | while (i < self.text_block_free_list.items.len) { |
| 779 | if (self.free_list.items[i] == text_block) { | 783 | if (self.text_block_free_list.items[i] == text_block) { |
| 780 | _ = self.free_list.swapRemove(i); | 784 | _ = self.text_block_free_list.swapRemove(i); |
| 781 | continue; | 785 | continue; |
| 782 | } | 786 | } |
| 783 | if (self.free_list.items[i] == text_block.prev) { | 787 | if (self.text_block_free_list.items[i] == text_block.prev) { |
| 784 | already_have_free_list_node = true; | 788 | already_have_free_list_node = true; |
| 785 | } | 789 | } |
| 786 | i += 1; | 790 | i += 1; |
| ... | @@ -797,7 +801,7 @@ pub const ElfFile = struct { | ... | @@ -797,7 +801,7 @@ pub const ElfFile = struct { |
| 797 | if (!already_have_free_list_node and prev.freeListEligible(self.*)) { | 801 | if (!already_have_free_list_node and prev.freeListEligible(self.*)) { |
| 798 | // The free list is heuristics, it doesn't have to be perfect, so we can | 802 | // The free list is heuristics, it doesn't have to be perfect, so we can |
| 799 | // ignore the OOM here. | 803 | // ignore the OOM here. |
| 800 | self.free_list.append(self.allocator, prev) catch {}; | 804 | self.text_block_free_list.append(self.allocator, prev) catch {}; |
| 801 | } | 805 | } |
| 802 | } else { | 806 | } else { |
| 803 | text_block.prev = null; | 807 | text_block.prev = null; |
| ... | @@ -840,8 +844,8 @@ pub const ElfFile = struct { | ... | @@ -840,8 +844,8 @@ pub const ElfFile = struct { |
| 840 | // The list is unordered. We'll just take the first thing that works. | 844 | // The list is unordered. We'll just take the first thing that works. |
| 841 | const vaddr = blk: { | 845 | const vaddr = blk: { |
| 842 | var i: usize = 0; | 846 | var i: usize = 0; |
| 843 | while (i < self.free_list.items.len) { | 847 | while (i < self.text_block_free_list.items.len) { |
| 844 | const big_block = self.free_list.items[i]; | 848 | const big_block = self.text_block_free_list.items[i]; |
| 845 | // We now have a pointer to a live text block that has too much capacity. | 849 | // We now have a pointer to a live text block that has too much capacity. |
| 846 | // Is it enough that we could fit this new text block? | 850 | // Is it enough that we could fit this new text block? |
| 847 | const sym = self.local_symbols.items[big_block.local_sym_index]; | 851 | const sym = self.local_symbols.items[big_block.local_sym_index]; |
| ... | @@ -856,7 +860,7 @@ pub const ElfFile = struct { | ... | @@ -856,7 +860,7 @@ pub const ElfFile = struct { |
| 856 | // should be deleted because the block that it points to has grown to take up | 860 | // should be deleted because the block that it points to has grown to take up |
| 857 | // more of the extra capacity. | 861 | // more of the extra capacity. |
| 858 | if (!big_block.freeListEligible(self.*)) { | 862 | if (!big_block.freeListEligible(self.*)) { |
| 859 | _ = self.free_list.swapRemove(i); | 863 | _ = self.text_block_free_list.swapRemove(i); |
| 860 | } else { | 864 | } else { |
| 861 | i += 1; | 865 | i += 1; |
| 862 | } | 866 | } |
| ... | @@ -932,7 +936,7 @@ pub const ElfFile = struct { | ... | @@ -932,7 +936,7 @@ pub const ElfFile = struct { |
| 932 | text_block.next = null; | 936 | text_block.next = null; |
| 933 | } | 937 | } |
| 934 | if (free_list_removal) |i| { | 938 | if (free_list_removal) |i| { |
| 935 | _ = self.free_list.swapRemove(i); | 939 | _ = self.text_block_free_list.swapRemove(i); |
| 936 | } | 940 | } |
| 937 | return vaddr; | 941 | return vaddr; |
| 938 | } | 942 | } |
| ... | @@ -958,11 +962,18 @@ pub const ElfFile = struct { | ... | @@ -958,11 +962,18 @@ pub const ElfFile = struct { |
| 958 | 962 | ||
| 959 | self.offset_table_count_dirty = true; | 963 | self.offset_table_count_dirty = true; |
| 960 | 964 | ||
| 961 | //std.debug.warn("allocating symbol index {}\n", .{local_sym_index}); | 965 | std.debug.warn("allocating symbol index {} for {}\n", .{local_sym_index, decl.name}); |
| 962 | decl.link.local_sym_index = @intCast(u32, local_sym_index); | 966 | decl.link.local_sym_index = @intCast(u32, local_sym_index); |
| 963 | decl.link.offset_table_index = @intCast(u32, offset_table_index); | 967 | decl.link.offset_table_index = @intCast(u32, offset_table_index); |
| 964 | } | 968 | } |
| 965 | 969 | ||
| 970 | pub fn freeDecl(self: *ElfFile, decl: *Module.Decl) void { | ||
| 971 | self.freeTextBlock(&decl.link); | ||
| 972 | if (decl.link.local_sym_index != 0) { | ||
| 973 | @panic("TODO free the symbol entry and offset table entry"); | ||
| 974 | } | ||
| 975 | } | ||
| 976 | |||
| 966 | pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void { | 977 | pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void { |
| 967 | var code_buffer = std.ArrayList(u8).init(self.allocator); | 978 | var code_buffer = std.ArrayList(u8).init(self.allocator); |
| 968 | defer code_buffer.deinit(); | 979 | defer code_buffer.deinit(); |
| ... | @@ -993,11 +1004,11 @@ pub const ElfFile = struct { | ... | @@ -993,11 +1004,11 @@ pub const ElfFile = struct { |
| 993 | !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); | 1004 | !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); |
| 994 | if (need_realloc) { | 1005 | if (need_realloc) { |
| 995 | const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); | 1006 | const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); |
| 996 | //std.debug.warn("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); | 1007 | std.debug.warn("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); |
| 997 | if (vaddr != local_sym.st_value) { | 1008 | if (vaddr != local_sym.st_value) { |
| 998 | local_sym.st_value = vaddr; | 1009 | local_sym.st_value = vaddr; |
| 999 | 1010 | ||
| 1000 | //std.debug.warn(" (writing new offset table entry)\n", .{}); | 1011 | std.debug.warn(" (writing new offset table entry)\n", .{}); |
| 1001 | self.offset_table.items[decl.link.offset_table_index] = vaddr; | 1012 | self.offset_table.items[decl.link.offset_table_index] = vaddr; |
| 1002 | try self.writeOffsetTableEntry(decl.link.offset_table_index); | 1013 | try self.writeOffsetTableEntry(decl.link.offset_table_index); |
| 1003 | } | 1014 | } |
| ... | @@ -1015,7 +1026,7 @@ pub const ElfFile = struct { | ... | @@ -1015,7 +1026,7 @@ pub const ElfFile = struct { |
| 1015 | const decl_name = mem.spanZ(decl.name); | 1026 | const decl_name = mem.spanZ(decl.name); |
| 1016 | const name_str_index = try self.makeString(decl_name); | 1027 | const name_str_index = try self.makeString(decl_name); |
| 1017 | const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); | 1028 | const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); |
| 1018 | //std.debug.warn("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); | 1029 | std.debug.warn("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); |
| 1019 | errdefer self.freeTextBlock(&decl.link); | 1030 | errdefer self.freeTextBlock(&decl.link); |
| 1020 | 1031 | ||
| 1021 | local_sym.* = .{ | 1032 | local_sym.* = .{ |
| ... | @@ -1048,7 +1059,10 @@ pub const ElfFile = struct { | ... | @@ -1048,7 +1059,10 @@ pub const ElfFile = struct { |
| 1048 | decl: *const Module.Decl, | 1059 | decl: *const Module.Decl, |
| 1049 | exports: []const *Module.Export, | 1060 | exports: []const *Module.Export, |
| 1050 | ) !void { | 1061 | ) !void { |
| 1062 | // In addition to ensuring capacity for global_symbols, we also ensure capacity for freeing all of | ||
| 1063 | // them, so that deleting exports is guaranteed to succeed. | ||
| 1051 | try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len); | 1064 | try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len); |
| 1065 | try self.global_symbol_free_list.ensureCapacity(self.allocator, self.global_symbols.items.len); | ||
| 1052 | const typed_value = decl.typed_value.most_recent.typed_value; | 1066 | const typed_value = decl.typed_value.most_recent.typed_value; |
| 1053 | if (decl.link.local_sym_index == 0) return; | 1067 | if (decl.link.local_sym_index == 0) return; |
| 1054 | const decl_sym = self.local_symbols.items[decl.link.local_sym_index]; | 1068 | const decl_sym = self.local_symbols.items[decl.link.local_sym_index]; |
| ... | @@ -1095,22 +1109,30 @@ pub const ElfFile = struct { | ... | @@ -1095,22 +1109,30 @@ pub const ElfFile = struct { |
| 1095 | }; | 1109 | }; |
| 1096 | } else { | 1110 | } else { |
| 1097 | const name = try self.makeString(exp.options.name); | 1111 | const name = try self.makeString(exp.options.name); |
| 1098 | const i = self.global_symbols.items.len; | 1112 | const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { |
| 1099 | self.global_symbols.appendAssumeCapacity(.{ | 1113 | _ = self.global_symbols.addOneAssumeCapacity(); |
| 1114 | break :blk self.global_symbols.items.len - 1; | ||
| 1115 | }; | ||
| 1116 | self.global_symbols.items[i] = .{ | ||
| 1100 | .st_name = name, | 1117 | .st_name = name, |
| 1101 | .st_info = (stb_bits << 4) | stt_bits, | 1118 | .st_info = (stb_bits << 4) | stt_bits, |
| 1102 | .st_other = 0, | 1119 | .st_other = 0, |
| 1103 | .st_shndx = self.text_section_index.?, | 1120 | .st_shndx = self.text_section_index.?, |
| 1104 | .st_value = decl_sym.st_value, | 1121 | .st_value = decl_sym.st_value, |
| 1105 | .st_size = decl_sym.st_size, | 1122 | .st_size = decl_sym.st_size, |
| 1106 | }); | 1123 | }; |
| 1107 | errdefer self.global_symbols.shrink(self.allocator, self.global_symbols.items.len - 1); | ||
| 1108 | 1124 | ||
| 1109 | exp.link.sym_index = @intCast(u32, i); | 1125 | exp.link.sym_index = @intCast(u32, i); |
| 1110 | } | 1126 | } |
| 1111 | } | 1127 | } |
| 1112 | } | 1128 | } |
| 1113 | 1129 | ||
| 1130 | pub fn deleteExport(self: *ElfFile, exp: Export) void { | ||
| 1131 | const sym_index = exp.sym_index orelse return; | ||
| 1132 | self.global_symbol_free_list.appendAssumeCapacity(sym_index); | ||
| 1133 | self.global_symbols.items[sym_index].st_info = 0; | ||
| 1134 | } | ||
| 1135 | |||
| 1114 | fn writeProgHeader(self: *ElfFile, index: usize) !void { | 1136 | fn writeProgHeader(self: *ElfFile, index: usize) !void { |
| 1115 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); | 1137 | const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); |
| 1116 | const offset = self.program_headers.items[index].p_offset; | 1138 | const offset = self.program_headers.items[index].p_offset; |
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-71| ... | @@ -200,73 +200,73 @@ pub fn addCases(ctx: *TestContext) void { | ... | @@ -200,73 +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 | // , | 203 | , |
| 204 | // \\@noreturn = primitive(noreturn) | 204 | \\@noreturn = primitive(noreturn) |
| 205 | // \\@void = primitive(void) | 205 | \\@void = primitive(void) |
| 206 | // \\@usize = primitive(usize) | 206 | \\@usize = primitive(usize) |
| 207 | // \\@0 = int(0) | 207 | \\@0 = int(0) |
| 208 | // \\@1 = int(1) | 208 | \\@1 = int(1) |
| 209 | // \\@2 = int(2) | 209 | \\@2 = int(2) |
| 210 | // \\@3 = int(3) | 210 | \\@3 = int(3) |
| 211 | // \\ | 211 | \\ |
| 212 | // \\@syscall_array = str("syscall") | 212 | \\@syscall_array = str("syscall") |
| 213 | // \\@sysoutreg_array = str("={rax}") | 213 | \\@sysoutreg_array = str("={rax}") |
| 214 | // \\@rax_array = str("{rax}") | 214 | \\@rax_array = str("{rax}") |
| 215 | // \\@rdi_array = str("{rdi}") | 215 | \\@rdi_array = str("{rdi}") |
| 216 | // \\@rcx_array = str("rcx") | 216 | \\@rcx_array = str("rcx") |
| 217 | // \\@r11_array = str("r11") | 217 | \\@r11_array = str("r11") |
| 218 | // \\@rdx_array = str("{rdx}") | 218 | \\@rdx_array = str("{rdx}") |
| 219 | // \\@rsi_array = str("{rsi}") | 219 | \\@rsi_array = str("{rsi}") |
| 220 | // \\@memory_array = str("memory") | 220 | \\@memory_array = str("memory") |
| 221 | // \\@len_array = str("len") | 221 | \\@len_array = str("len") |
| 222 | // \\ | 222 | \\ |
| 223 | // \\@msg = str("Hello, world!\n") | 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") | 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 | // \\ | 225 | \\ |
| 226 | // \\@start_fnty = fntype([], @noreturn, cc=Naked) | 226 | \\@start_fnty = fntype([], @noreturn, cc=Naked) |
| 227 | // \\@start = fn(@start_fnty, { | 227 | \\@start = fn(@start_fnty, { |
| 228 | // \\ %SYS_exit_group = int(231) | 228 | \\ %SYS_exit_group = int(231) |
| 229 | // \\ %exit_code = as(@usize, @0) | 229 | \\ %exit_code = as(@usize, @0) |
| 230 | // \\ | 230 | \\ |
| 231 | // \\ %syscall = ref(@syscall_array) | 231 | \\ %syscall = ref(@syscall_array) |
| 232 | // \\ %sysoutreg = ref(@sysoutreg_array) | 232 | \\ %sysoutreg = ref(@sysoutreg_array) |
| 233 | // \\ %rax = ref(@rax_array) | 233 | \\ %rax = ref(@rax_array) |
| 234 | // \\ %rdi = ref(@rdi_array) | 234 | \\ %rdi = ref(@rdi_array) |
| 235 | // \\ %rcx = ref(@rcx_array) | 235 | \\ %rcx = ref(@rcx_array) |
| 236 | // \\ %rdx = ref(@rdx_array) | 236 | \\ %rdx = ref(@rdx_array) |
| 237 | // \\ %rsi = ref(@rsi_array) | 237 | \\ %rsi = ref(@rsi_array) |
| 238 | // \\ %r11 = ref(@r11_array) | 238 | \\ %r11 = ref(@r11_array) |
| 239 | // \\ %memory = ref(@memory_array) | 239 | \\ %memory = ref(@memory_array) |
| 240 | // \\ | 240 | \\ |
| 241 | // \\ %SYS_write = as(@usize, @1) | 241 | \\ %SYS_write = as(@usize, @1) |
| 242 | // \\ %STDOUT_FILENO = as(@usize, @1) | 242 | \\ %STDOUT_FILENO = as(@usize, @1) |
| 243 | // \\ | 243 | \\ |
| 244 | // \\ %msg_ptr = ref(@msg2) | 244 | \\ %msg_ptr = ref(@msg2) |
| 245 | // \\ %msg_addr = ptrtoint(%msg_ptr) | 245 | \\ %msg_addr = ptrtoint(%msg_ptr) |
| 246 | // \\ | 246 | \\ |
| 247 | // \\ %len_name = ref(@len_array) | 247 | \\ %len_name = ref(@len_array) |
| 248 | // \\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name) | 248 | \\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name) |
| 249 | // \\ %msg_len = deref(%msg_len_ptr) | 249 | \\ %msg_len = deref(%msg_len_ptr) |
| 250 | // \\ %rc_write = asm(%syscall, @usize, | 250 | \\ %rc_write = asm(%syscall, @usize, |
| 251 | // \\ volatile=1, | 251 | \\ volatile=1, |
| 252 | // \\ output=%sysoutreg, | 252 | \\ output=%sysoutreg, |
| 253 | // \\ inputs=[%rax, %rdi, %rsi, %rdx], | 253 | \\ inputs=[%rax, %rdi, %rsi, %rdx], |
| 254 | // \\ clobbers=[%rcx, %r11, %memory], | 254 | \\ clobbers=[%rcx, %r11, %memory], |
| 255 | // \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len]) | 255 | \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len]) |
| 256 | // \\ | 256 | \\ |
| 257 | // \\ %rc_exit = asm(%syscall, @usize, | 257 | \\ %rc_exit = asm(%syscall, @usize, |
| 258 | // \\ volatile=1, | 258 | \\ volatile=1, |
| 259 | // \\ output=%sysoutreg, | 259 | \\ output=%sysoutreg, |
| 260 | // \\ inputs=[%rax, %rdi], | 260 | \\ inputs=[%rax, %rdi], |
| 261 | // \\ clobbers=[%rcx, %r11, %memory], | 261 | \\ clobbers=[%rcx, %r11, %memory], |
| 262 | // \\ args=[%SYS_exit_group, %exit_code]) | 262 | \\ args=[%SYS_exit_group, %exit_code]) |
| 263 | // \\ | 263 | \\ |
| 264 | // \\ %99 = unreachable() | 264 | \\ %99 = unreachable() |
| 265 | // \\}); | 265 | \\}); |
| 266 | // \\ | 266 | \\ |
| 267 | // \\@9 = str("_start") | 267 | \\@9 = str("_start") |
| 268 | // \\@10 = ref(@9) | 268 | \\@10 = ref(@9) |
| 269 | // \\@11 = export(@10, @start) | 269 | \\@11 = export(@10, @start) |
| 270 | }, | 270 | }, |
| 271 | &[_][]const u8{ | 271 | &[_][]const u8{ |
| 272 | \\Hello, world! | 272 | \\Hello, world! |
| ... | @@ -274,10 +274,10 @@ pub fn addCases(ctx: *TestContext) void { | ... | @@ -274,10 +274,10 @@ pub fn addCases(ctx: *TestContext) void { |
| 274 | , | 274 | , |
| 275 | \\HELL WORLD | 275 | \\HELL WORLD |
| 276 | \\ | 276 | \\ |
| 277 | // , | 277 | , |
| 278 | // \\Editing the same msg2 decl but this time with a much longer message which will | 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. | 279 | \\cause the data to need to be relocated in virtual address space. |
| 280 | // \\ | 280 | \\ |
| 281 | }, | 281 | }, |
| 282 | ); | 282 | ); |
| 283 | 283 |